Compare commits

..

No commits in common. "23a3f59f48a6bdc0f301a43323fc32102ed2c1f7" and "c209ba6197c0cc86d970955a1317b6e62836069d" have entirely different histories.

25 changed files with 150 additions and 682 deletions

BIN
dist/plan vendored

Binary file not shown.

View File

@ -91,6 +91,12 @@ func (t Task) Valid() bool {
if t.Title == "" { if t.Title == "" {
return false return false
} }
if t.Date.IsZero() {
return false
}
if t.Duration.Seconds() < 1 {
return false
}
return true return true
} }

View File

@ -167,6 +167,28 @@ func TestTaskValidate(t *testing.T) {
}, },
}, },
}, },
{
name: "no date",
tsk: item.Task{
ID: "a",
TaskBody: item.TaskBody{
Title: "title",
Time: item.NewTime(8, 0),
Duration: oneHour,
},
},
},
{
name: "no duration",
tsk: item.Task{
ID: "a",
Date: item.NewDate(2024, 9, 20),
TaskBody: item.TaskBody{
Title: "title",
Time: item.NewTime(8, 0),
},
},
},
{ {
name: "valid", name: "valid",
tsk: item.Task{ tsk: item.Task{

View File

@ -11,16 +11,16 @@ import (
type AddArgs struct { type AddArgs struct {
fieldTPL map[string][]string fieldTPL map[string][]string
Task item.Task task item.Task
} }
func NewAddArgs() AddArgs { func NewAddArgs() AddArgs {
return AddArgs{ return AddArgs{
fieldTPL: map[string][]string{ fieldTPL: map[string][]string{
"date": {"d", "date", "on"}, "date": []string{"d", "date", "on"},
"time": {"t", "time", "at"}, "time": []string{"t", "time", "at"},
"duration": {"dur", "duration", "for"}, "duration": []string{"dur", "duration", "for"},
"recurrer": {"rec", "recurrer"}, "recurrer": []string{"rec", "recurrer"},
}, },
} }
} }
@ -41,7 +41,7 @@ func (aa AddArgs) Parse(main []string, fields map[string]string) (Command, error
tsk := item.Task{ tsk := item.Task{
ID: uuid.New().String(), ID: uuid.New().String(),
TaskBody: item.TaskBody{ TaskBody: item.TaskBody{
Title: strings.Join(main, " "), Title: strings.Join(main, ","),
}, },
} }
@ -76,43 +76,36 @@ func (aa AddArgs) Parse(main []string, fields map[string]string) (Command, error
} }
return &Add{ return &Add{
Args: AddArgs{ args: AddArgs{
Task: tsk, task: tsk,
}, },
}, nil }, nil
} }
type Add struct { type Add struct {
Args AddArgs args AddArgs
} }
func (a *Add) Do(deps Dependencies) (CommandResult, error) { func (a *Add) Do(deps Dependencies) error {
if err := deps.TaskRepo.Store(a.Args.Task); err != nil { if err := deps.TaskRepo.Store(a.args.task); err != nil {
return nil, fmt.Errorf("could not store event: %v", err) return fmt.Errorf("could not store event: %v", err)
} }
localID, err := deps.LocalIDRepo.Next() localID, err := deps.LocalIDRepo.Next()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not create next local id: %v", err) return fmt.Errorf("could not create next local id: %v", err)
} }
if err := deps.LocalIDRepo.Store(a.Args.Task.ID, localID); err != nil { if err := deps.LocalIDRepo.Store(a.args.task.ID, localID); err != nil {
return nil, fmt.Errorf("could not store local id: %v", err) return fmt.Errorf("could not store local id: %v", err)
} }
it, err := a.Args.Task.Item() it, err := a.args.task.Item()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not convert event to sync item: %v", err) return fmt.Errorf("could not convert event to sync item: %v", err)
} }
if err := deps.SyncRepo.Store(it); err != nil { if err := deps.SyncRepo.Store(it); err != nil {
return nil, fmt.Errorf("could not store sync item: %v", err) return fmt.Errorf("could not store sync item: %v", err)
} }
return nil, nil return nil
}
type AddRender struct {
}
func (ar AddRender) Render() string {
return "stored task"
} }

View File

@ -6,7 +6,6 @@ import (
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/command"
"go-mod.ewintr.nl/planner/plan/storage"
"go-mod.ewintr.nl/planner/plan/storage/memory" "go-mod.ewintr.nl/planner/plan/storage/memory"
) )
@ -57,12 +56,9 @@ func TestAdd(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// setup
taskRepo := memory.NewTask() taskRepo := memory.NewTask()
localIDRepo := memory.NewLocalID() localIDRepo := memory.NewLocalID()
syncRepo := memory.NewSync() syncRepo := memory.NewSync()
// parse
cmd, actParseErr := command.NewAddArgs().Parse(tc.main, tc.fields) cmd, actParseErr := command.NewAddArgs().Parse(tc.main, tc.fields)
if tc.expErr != (actParseErr != nil) { if tc.expErr != (actParseErr != nil) {
t.Errorf("exp %v, got %v", tc.expErr, actParseErr) t.Errorf("exp %v, got %v", tc.expErr, actParseErr)
@ -70,9 +66,7 @@ func TestAdd(t *testing.T) {
if tc.expErr { if tc.expErr {
return return
} }
if err := cmd.Do(command.Dependencies{
// do
if _, err := cmd.Do(command.Dependencies{
TaskRepo: taskRepo, TaskRepo: taskRepo,
LocalIDRepo: localIDRepo, LocalIDRepo: localIDRepo,
SyncRepo: syncRepo, SyncRepo: syncRepo,
@ -80,8 +74,7 @@ func TestAdd(t *testing.T) {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
// check actTasks, err := taskRepo.FindAll()
actTasks, err := taskRepo.FindMany(storage.TaskListParams{})
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }

View File

@ -32,11 +32,7 @@ type CommandArgs interface {
} }
type Command interface { type Command interface {
Do(deps Dependencies) (CommandResult, error) Do(deps Dependencies) error
}
type CommandResult interface {
Render() string
} }
type CLI struct { type CLI struct {
@ -48,7 +44,6 @@ func NewCLI(deps Dependencies) *CLI {
return &CLI{ return &CLI{
deps: deps, deps: deps,
cmdArgs: []CommandArgs{ cmdArgs: []CommandArgs{
NewShowArgs(),
NewAddArgs(), NewDeleteArgs(), NewListArgs(), NewAddArgs(), NewDeleteArgs(), NewListArgs(),
NewSyncArgs(), NewUpdateArgs(), NewSyncArgs(), NewUpdateArgs(),
}, },
@ -56,23 +51,17 @@ func NewCLI(deps Dependencies) *CLI {
} }
func (cli *CLI) Run(args []string) error { func (cli *CLI) Run(args []string) error {
main, fields := FindFields(args) main, flags := FindFields(args)
for _, ca := range cli.cmdArgs { for _, ca := range cli.cmdArgs {
cmd, err := ca.Parse(main, fields) cmd, err := ca.Parse(main, flags)
switch { switch {
case errors.Is(err, ErrWrongCommand): case errors.Is(err, ErrWrongCommand):
continue continue
case err != nil: case err != nil:
return err return err
default:
return cmd.Do(cli.deps)
} }
result, err := cmd.Do(cli.deps)
if err != nil {
return err
}
fmt.Println(result.Render())
return nil
} }
return fmt.Errorf("could not find matching command") return fmt.Errorf("could not find matching command")

View File

@ -24,58 +24,52 @@ func (da DeleteArgs) Parse(main []string, flags map[string]string) (Command, err
} }
return &Delete{ return &Delete{
Args: DeleteArgs{ args: DeleteArgs{
LocalID: localID, LocalID: localID,
}, },
}, nil }, nil
} }
type Delete struct { type Delete struct {
Args DeleteArgs args DeleteArgs
} }
func (del *Delete) Do(deps Dependencies) (CommandResult, error) { func (del *Delete) Do(deps Dependencies) error {
var id string var id string
idMap, err := deps.LocalIDRepo.FindAll() idMap, err := deps.LocalIDRepo.FindAll()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get local ids: %v", err) return fmt.Errorf("could not get local ids: %v", err)
} }
for tskID, lid := range idMap { for tskID, lid := range idMap {
if del.Args.LocalID == lid { if del.args.LocalID == lid {
id = tskID id = tskID
} }
} }
if id == "" { if id == "" {
return nil, fmt.Errorf("could not find local id") return fmt.Errorf("could not find local id")
} }
tsk, err := deps.TaskRepo.FindOne(id) tsk, err := deps.TaskRepo.Find(id)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get task: %v", err) return fmt.Errorf("could not get task: %v", err)
} }
it, err := tsk.Item() it, err := tsk.Item()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not convert task to sync item: %v", err) return fmt.Errorf("could not convert task to sync item: %v", err)
} }
it.Deleted = true it.Deleted = true
if err := deps.SyncRepo.Store(it); err != nil { if err := deps.SyncRepo.Store(it); err != nil {
return nil, fmt.Errorf("could not store sync item: %v", err) return fmt.Errorf("could not store sync item: %v", err)
} }
if err := deps.LocalIDRepo.Delete(id); err != nil { if err := deps.LocalIDRepo.Delete(id); err != nil {
return nil, fmt.Errorf("could not delete local id: %v", err) return fmt.Errorf("could not delete local id: %v", err)
} }
if err := deps.TaskRepo.Delete(id); err != nil { if err := deps.TaskRepo.Delete(id); err != nil {
return nil, fmt.Errorf("could not delete task: %v", err) return fmt.Errorf("could not delete task: %v", err)
} }
return nil, nil return nil
}
type DeleteResult struct{}
func (dr DeleteResult) Render() string {
return "task deleted"
} }

View File

@ -44,7 +44,6 @@ func TestDelete(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// setup
taskRepo := memory.NewTask() taskRepo := memory.NewTask()
syncRepo := memory.NewSync() syncRepo := memory.NewSync()
if err := taskRepo.Store(e); err != nil { if err := taskRepo.Store(e); err != nil {
@ -55,7 +54,6 @@ func TestDelete(t *testing.T) {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
// parse
cmd, actParseErr := command.NewDeleteArgs().Parse(tc.main, tc.flags) cmd, actParseErr := command.NewDeleteArgs().Parse(tc.main, tc.flags)
if tc.expParseErr != (actParseErr != nil) { if tc.expParseErr != (actParseErr != nil) {
t.Errorf("exp %v, got %v", tc.expParseErr, actParseErr) t.Errorf("exp %v, got %v", tc.expParseErr, actParseErr)
@ -63,22 +61,19 @@ func TestDelete(t *testing.T) {
if tc.expParseErr { if tc.expParseErr {
return return
} }
actDoErr := cmd.Do(command.Dependencies{
// do
_, actDoErr := cmd.Do(command.Dependencies{
TaskRepo: taskRepo, TaskRepo: taskRepo,
LocalIDRepo: localIDRepo, LocalIDRepo: localIDRepo,
SyncRepo: syncRepo, SyncRepo: syncRepo,
}) }) != nil
if tc.expDoErr != (actDoErr != nil) { if tc.expDoErr != actDoErr {
t.Errorf("exp false, got %v", actDoErr) t.Errorf("exp false, got %v", actDoErr)
} }
if tc.expDoErr { if tc.expDoErr {
return return
} }
// check _, repoErr := taskRepo.Find(e.ID)
_, repoErr := taskRepo.FindOne(e.ID)
if !errors.Is(repoErr, storage.ErrNotFound) { if !errors.Is(repoErr, storage.ErrNotFound) {
t.Errorf("exp %v, got %v", storage.ErrNotFound, repoErr) t.Errorf("exp %v, got %v", storage.ErrNotFound, repoErr)
} }

View File

@ -2,16 +2,9 @@ package command
import ( import (
"fmt" "fmt"
"slices"
"time"
"go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/format"
"go-mod.ewintr.nl/planner/plan/storage"
) )
type ListArgs struct { type ListArgs struct {
params storage.TaskListParams
} }
func NewListArgs() ListArgs { func NewListArgs() ListArgs {
@ -19,96 +12,32 @@ func NewListArgs() ListArgs {
} }
func (la ListArgs) Parse(main []string, flags map[string]string) (Command, error) { func (la ListArgs) Parse(main []string, flags map[string]string) (Command, error) {
if len(main) > 2 { if len(main) > 0 && main[0] != "list" {
return nil, ErrWrongCommand return nil, ErrWrongCommand
} }
now := time.Now() return &List{}, nil
today := item.NewDate(now.Year(), int(now.Month()), now.Day())
tomorrow := item.NewDate(now.Year(), int(now.Month()), now.Day()+1)
var date item.Date
var includeBefore, recurrer bool
switch len(main) {
case 0:
date = today
includeBefore = true
case 1:
switch {
case slices.Contains([]string{"today", "tod"}, main[0]):
date = today
includeBefore = true
case slices.Contains([]string{"tomorrow", "tom"}, main[0]):
date = tomorrow
case main[0] == "list":
default:
return nil, ErrWrongCommand
}
case 2:
if main[0] == "list" && main[1] == "recur" {
recurrer = true
} else {
return nil, ErrWrongCommand
}
default:
return nil, ErrWrongCommand
}
return &List{
args: ListArgs{
params: storage.TaskListParams{
Date: date,
IncludeBefore: includeBefore,
Recurrer: recurrer,
},
},
}, nil
} }
type List struct { type List struct {
args ListArgs
} }
func (list *List) Do(deps Dependencies) (CommandResult, error) { func (list *List) Do(deps Dependencies) error {
localIDs, err := deps.LocalIDRepo.FindAll() localIDs, err := deps.LocalIDRepo.FindAll()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get local ids: %v", err) return fmt.Errorf("could not get local ids: %v", err)
} }
all, err := deps.TaskRepo.FindMany(list.args.params) all, err := deps.TaskRepo.FindAll()
if err != nil { if err != nil {
return nil, err return err
} }
for _, e := range all {
res := make([]TaskWithLID, 0, len(all)) lid, ok := localIDs[e.ID]
for _, tsk := range all {
lid, ok := localIDs[tsk.ID]
if !ok { if !ok {
return nil, fmt.Errorf("could not find local id for %s", tsk.ID) return fmt.Errorf("could not find local id for %s", e.ID)
} }
res = append(res, TaskWithLID{ fmt.Printf("%s\t%d\t%s\t%s\t%s\n", e.ID, lid, e.Title, e.Date.String(), e.Duration.String())
LocalID: lid,
Task: tsk,
})
}
return ListResult{
Tasks: res,
}, nil
} }
type TaskWithLID struct { return nil
LocalID int
Task item.Task
}
type ListResult struct {
Tasks []TaskWithLID
}
func (lr ListResult) Render() string {
data := [][]string{{"id", "date", "dur", "title"}}
for _, tl := range lr.Tasks {
data = append(data, []string{fmt.Sprintf("%d", tl.LocalID), tl.Task.Date.String(), tl.Task.Duration.String(), tl.Task.Title})
}
return fmt.Sprintf("\n%s\n", format.Table(data))
} }

View File

@ -30,22 +30,15 @@ func TestList(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
main []string main []string
expRes bool
expErr bool expErr bool
}{ }{
{ {
name: "empty", name: "empty",
main: []string{}, main: []string{},
expRes: true,
}, },
{ {
name: "list", name: "list",
main: []string{"list"}, main: []string{"list"},
expRes: true,
},
{
name: "empty list",
main: []string{"list", "recur"},
}, },
{ {
name: "wrong", name: "wrong",
@ -54,7 +47,6 @@ func TestList(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// parse
cmd, actErr := command.NewListArgs().Parse(tc.main, nil) cmd, actErr := command.NewListArgs().Parse(tc.main, nil)
if tc.expErr != (actErr != nil) { if tc.expErr != (actErr != nil) {
t.Errorf("exp %v, got %v", tc.expErr, actErr) t.Errorf("exp %v, got %v", tc.expErr, actErr)
@ -62,22 +54,12 @@ func TestList(t *testing.T) {
if tc.expErr { if tc.expErr {
return return
} }
if err := cmd.Do(command.Dependencies{
// do
res, err := cmd.Do(command.Dependencies{
TaskRepo: taskRepo, TaskRepo: taskRepo,
LocalIDRepo: localRepo, LocalIDRepo: localRepo,
}) }); err != nil {
if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
// check
listRes := res.(command.ListResult)
actRes := len(listRes.Tasks) > 0
if tc.expRes != actRes {
t.Errorf("exp %v, got %v", tc.expRes, actRes)
}
}) })
} }
} }

View File

@ -1,83 +1 @@
package command package command
import (
"errors"
"fmt"
"strconv"
"go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/format"
"go-mod.ewintr.nl/planner/plan/storage"
)
type ShowArgs struct {
localID int
}
func NewShowArgs() ShowArgs {
return ShowArgs{}
}
func (sa ShowArgs) Parse(main []string, fields map[string]string) (Command, error) {
if len(main) != 1 {
return nil, ErrWrongCommand
}
lid, err := strconv.Atoi(main[0])
if err != nil {
return nil, ErrWrongCommand
}
return &Show{
args: ShowArgs{
localID: lid,
},
}, nil
}
type Show struct {
args ShowArgs
}
func (s *Show) Do(deps Dependencies) (CommandResult, error) {
id, err := deps.LocalIDRepo.FindOne(s.args.localID)
switch {
case errors.Is(err, storage.ErrNotFound):
return nil, fmt.Errorf("could not find local id")
case err != nil:
return nil, err
}
tsk, err := deps.TaskRepo.FindOne(id)
if err != nil {
return nil, fmt.Errorf("could not find task")
}
return ShowResult{
LocalID: s.args.localID,
Task: tsk,
}, nil
}
type ShowResult struct {
LocalID int
Task item.Task
}
func (sr ShowResult) Render() string {
var recurStr string
if sr.Task.Recurrer != nil {
recurStr = sr.Task.Recurrer.String()
}
data := [][]string{
{"title", sr.Task.Title},
{"local id", fmt.Sprintf("%d", sr.LocalID)},
{"date", sr.Task.Date.String()},
{"time", sr.Task.Time.String()},
{"duration", sr.Task.Duration.String()},
{"recur", recurStr},
// {"id", s.Task.ID},
}
return fmt.Sprintf("\n%s\n", format.Table(data))
}

View File

@ -1,88 +0,0 @@
package command_test
import (
"fmt"
"testing"
"go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/command"
"go-mod.ewintr.nl/planner/plan/storage/memory"
)
func TestShow(t *testing.T) {
t.Parallel()
taskRepo := memory.NewTask()
localRepo := memory.NewLocalID()
tsk := item.Task{
ID: "id",
Date: item.NewDate(2024, 10, 7),
TaskBody: item.TaskBody{
Title: "name",
},
}
if err := taskRepo.Store(tsk); err != nil {
t.Errorf("exp nil, got %v", err)
}
if err := localRepo.Store(tsk.ID, 1); err != nil {
t.Errorf("exp nil, got %v", err)
}
for _, tc := range []struct {
name string
main []string
expData [][]string
expParseErr bool
expDoErr bool
}{
{
name: "empty",
main: []string{},
expParseErr: true,
},
{
name: "wrong",
main: []string{"delete"},
expParseErr: true,
},
{
name: "local id",
main: []string{"1"},
expData: [][]string{
{"title", tsk.Title},
{"local id", fmt.Sprintf("%d", 1)},
{"date", tsk.Date.String()},
{"time", tsk.Time.String()},
{"duration", tsk.Duration.String()},
{"recur", ""},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
// parse
cmd, actParseErr := command.NewShowArgs().Parse(tc.main, nil)
if tc.expParseErr != (actParseErr != nil) {
t.Errorf("exp %v, got %v", tc.expParseErr, actParseErr != nil)
}
if tc.expParseErr {
return
}
// do
_, actDoErr := cmd.Do(command.Dependencies{
TaskRepo: taskRepo,
LocalIDRepo: localRepo,
})
if tc.expDoErr != (actDoErr != nil) {
t.Errorf("exp %v, got %v", tc.expDoErr, actDoErr != nil)
}
if tc.expDoErr {
return
}
// if diff := cmp.Diff(tc.expData, actData); diff != "" {
// t.Errorf("(+exp, -got)%s\n", diff)
// }
})
}
}

View File

@ -25,37 +25,37 @@ func (sa SyncArgs) Parse(main []string, flags map[string]string) (Command, error
type Sync struct{} type Sync struct{}
func (s *Sync) Do(deps Dependencies) (CommandResult, error) { func (s *Sync) Do(deps Dependencies) error {
// local new and updated // local new and updated
sendItems, err := deps.SyncRepo.FindAll() sendItems, err := deps.SyncRepo.FindAll()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get updated items: %v", err) return fmt.Errorf("could not get updated items: %v", err)
} }
if err := deps.SyncClient.Update(sendItems); err != nil { if err := deps.SyncClient.Update(sendItems); err != nil {
return nil, fmt.Errorf("could not send updated items: %v", err) return fmt.Errorf("could not send updated items: %v", err)
} }
if err := deps.SyncRepo.DeleteAll(); err != nil { if err := deps.SyncRepo.DeleteAll(); err != nil {
return nil, fmt.Errorf("could not clear updated items: %v", err) return fmt.Errorf("could not clear updated items: %v", err)
} }
// get new/updated items // get new/updated items
ts, err := deps.SyncRepo.LastUpdate() ts, err := deps.SyncRepo.LastUpdate()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not find timestamp of last update: %v", err) return fmt.Errorf("could not find timestamp of last update: %v", err)
} }
recItems, err := deps.SyncClient.Updated([]item.Kind{item.KindTask}, ts) recItems, err := deps.SyncClient.Updated([]item.Kind{item.KindTask}, ts)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not receive updates: %v", err) return fmt.Errorf("could not receive updates: %v", err)
} }
updated := make([]item.Item, 0) updated := make([]item.Item, 0)
for _, ri := range recItems { for _, ri := range recItems {
if ri.Deleted { if ri.Deleted {
if err := deps.LocalIDRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) { if err := deps.LocalIDRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) {
return nil, fmt.Errorf("could not delete local id: %v", err) return fmt.Errorf("could not delete local id: %v", err)
} }
if err := deps.TaskRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) { if err := deps.TaskRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) {
return nil, fmt.Errorf("could not delete task: %v", err) return fmt.Errorf("could not delete task: %v", err)
} }
continue continue
} }
@ -64,12 +64,12 @@ func (s *Sync) Do(deps Dependencies) (CommandResult, error) {
lidMap, err := deps.LocalIDRepo.FindAll() lidMap, err := deps.LocalIDRepo.FindAll()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get local ids: %v", err) return fmt.Errorf("could not get local ids: %v", err)
} }
for _, u := range updated { for _, u := range updated {
var tskBody item.TaskBody var tskBody item.TaskBody
if err := json.Unmarshal([]byte(u.Body), &tskBody); err != nil { if err := json.Unmarshal([]byte(u.Body), &tskBody); err != nil {
return nil, fmt.Errorf("could not unmarshal task body: %v", err) return fmt.Errorf("could not unmarshal task body: %v", err)
} }
tsk := item.Task{ tsk := item.Task{
ID: u.ID, ID: u.ID,
@ -79,24 +79,20 @@ func (s *Sync) Do(deps Dependencies) (CommandResult, error) {
TaskBody: tskBody, TaskBody: tskBody,
} }
if err := deps.TaskRepo.Store(tsk); err != nil { if err := deps.TaskRepo.Store(tsk); err != nil {
return nil, fmt.Errorf("could not store task: %v", err) return fmt.Errorf("could not store task: %v", err)
} }
lid, ok := lidMap[u.ID] lid, ok := lidMap[u.ID]
if !ok { if !ok {
lid, err = deps.LocalIDRepo.Next() lid, err = deps.LocalIDRepo.Next()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not get next local id: %v", err) return fmt.Errorf("could not get next local id: %v", err)
} }
if err := deps.LocalIDRepo.Store(u.ID, lid); err != nil { if err := deps.LocalIDRepo.Store(u.ID, lid); err != nil {
return nil, fmt.Errorf("could not store local id: %v", err) return fmt.Errorf("could not store local id: %v", err)
} }
} }
} }
return SyncResult{}, nil return nil
} }
type SyncResult struct{}
func (sr SyncResult) Render() string { return "tasks synced" }

View File

@ -7,7 +7,6 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/command"
"go-mod.ewintr.nl/planner/plan/storage"
"go-mod.ewintr.nl/planner/plan/storage/memory" "go-mod.ewintr.nl/planner/plan/storage/memory"
"go-mod.ewintr.nl/planner/sync/client" "go-mod.ewintr.nl/planner/sync/client"
) )
@ -81,7 +80,7 @@ func TestSyncSend(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
if _, err := cmd.Do(command.Dependencies{ if err := cmd.Do(command.Dependencies{
TaskRepo: taskRepo, TaskRepo: taskRepo,
LocalIDRepo: localIDRepo, LocalIDRepo: localIDRepo,
SyncRepo: syncRepo, SyncRepo: syncRepo,
@ -207,7 +206,7 @@ func TestSyncReceive(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
if _, err := cmd.Do(command.Dependencies{ if err := cmd.Do(command.Dependencies{
TaskRepo: taskRepo, TaskRepo: taskRepo,
LocalIDRepo: localIDRepo, LocalIDRepo: localIDRepo,
SyncRepo: syncRepo, SyncRepo: syncRepo,
@ -217,7 +216,7 @@ func TestSyncReceive(t *testing.T) {
} }
// check result // check result
actTasks, err := taskRepo.FindMany(storage.TaskListParams{}) actTasks, err := taskRepo.FindAll()
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }

View File

@ -1,14 +1,12 @@
package command package command
import ( import (
"errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/storage"
) )
type UpdateArgs struct { type UpdateArgs struct {
@ -24,10 +22,10 @@ type UpdateArgs struct {
func NewUpdateArgs() UpdateArgs { func NewUpdateArgs() UpdateArgs {
return UpdateArgs{ return UpdateArgs{
fieldTPL: map[string][]string{ fieldTPL: map[string][]string{
"date": {"d", "date", "on"}, "date": []string{"d", "date", "on"},
"time": {"t", "time", "at"}, "time": []string{"t", "time", "at"},
"duration": {"dur", "duration", "for"}, "duration": []string{"dur", "duration", "for"},
"recurrer": {"rec", "recurrer"}, "recurrer": []string{"rec", "recurrer"},
}, },
} }
} }
@ -85,18 +83,24 @@ type Update struct {
args UpdateArgs args UpdateArgs
} }
func (u *Update) Do(deps Dependencies) (CommandResult, error) { func (u *Update) Do(deps Dependencies) error {
id, err := deps.LocalIDRepo.FindOne(u.args.LocalID) var id string
switch { idMap, err := deps.LocalIDRepo.FindAll()
case errors.Is(err, storage.ErrNotFound): if err != nil {
return nil, fmt.Errorf("could not find local id") return fmt.Errorf("could not get local ids: %v", err)
case err != nil: }
return nil, err for tid, lid := range idMap {
if u.args.LocalID == lid {
id = tid
}
}
if id == "" {
return fmt.Errorf("could not find local id")
} }
tsk, err := deps.TaskRepo.FindOne(id) tsk, err := deps.TaskRepo.Find(id)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not find task") return fmt.Errorf("could not find task")
} }
if u.args.Title != "" { if u.args.Title != "" {
@ -117,26 +121,20 @@ func (u *Update) Do(deps Dependencies) (CommandResult, error) {
} }
if !tsk.Valid() { if !tsk.Valid() {
return nil, fmt.Errorf("task is unvalid") return fmt.Errorf("task is unvalid")
} }
if err := deps.TaskRepo.Store(tsk); err != nil { if err := deps.TaskRepo.Store(tsk); err != nil {
return nil, fmt.Errorf("could not store task: %v", err) return fmt.Errorf("could not store task: %v", err)
} }
it, err := tsk.Item() it, err := tsk.Item()
if err != nil { if err != nil {
return nil, fmt.Errorf("could not convert task to sync item: %v", err) return fmt.Errorf("could not convert task to sync item: %v", err)
} }
if err := deps.SyncRepo.Store(it); err != nil { if err := deps.SyncRepo.Store(it); err != nil {
return nil, fmt.Errorf("could not store sync item: %v", err) return fmt.Errorf("could not store sync item: %v", err)
} }
return UpdateResult{}, nil return nil
}
type UpdateResult struct{}
func (ur UpdateResult) Render() string {
return "task updated"
} }

View File

@ -164,7 +164,6 @@ func TestUpdateExecute(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// setup
taskRepo := memory.NewTask() taskRepo := memory.NewTask()
localIDRepo := memory.NewLocalID() localIDRepo := memory.NewLocalID()
syncRepo := memory.NewSync() syncRepo := memory.NewSync()
@ -183,7 +182,6 @@ func TestUpdateExecute(t *testing.T) {
t.Errorf("exp nil, ,got %v", err) t.Errorf("exp nil, ,got %v", err)
} }
// parse
cmd, actErr := command.NewUpdateArgs().Parse(tc.main, tc.fields) cmd, actErr := command.NewUpdateArgs().Parse(tc.main, tc.fields)
if tc.expParseErr != (actErr != nil) { if tc.expParseErr != (actErr != nil) {
t.Errorf("exp %v, got %v", tc.expParseErr, actErr) t.Errorf("exp %v, got %v", tc.expParseErr, actErr)
@ -191,22 +189,19 @@ func TestUpdateExecute(t *testing.T) {
if tc.expParseErr { if tc.expParseErr {
return return
} }
actDoErr := cmd.Do(command.Dependencies{
// do
_, actDoErr := cmd.Do(command.Dependencies{
TaskRepo: taskRepo, TaskRepo: taskRepo,
LocalIDRepo: localIDRepo, LocalIDRepo: localIDRepo,
SyncRepo: syncRepo, SyncRepo: syncRepo,
}) }) != nil
if tc.expDoErr != (actDoErr != nil) { if tc.expDoErr != actDoErr {
t.Errorf("exp %v, got %v", tc.expDoErr, actDoErr) t.Errorf("exp %v, got %v", tc.expDoErr, actDoErr)
} }
if tc.expDoErr { if tc.expDoErr {
return return
} }
// check actTask, err := taskRepo.Find(tskID)
actTask, err := taskRepo.FindOne(tskID)
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }

View File

@ -1,97 +0,0 @@
package format
import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
)
func Table(data [][]string) string {
if len(data) == 0 {
return ""
}
// make all cells in a column the same width
max := make([]int, len(data[0]))
for _, row := range data {
for c, cell := range row {
if len(cell) > max[c] {
max[c] = len(cell)
}
}
}
for r, row := range data {
for c, cell := range row {
for s := len(cell); s < max[c]; s++ {
data[r][c] += " "
}
}
}
// make it smaller if the result is too wide
// only by making the widest column smaller for now
maxWidth := findTermWidth()
if maxWidth != 0 {
width := len(max) - 1
for _, m := range max {
width += m
}
shortenWith := width - maxWidth
widestColNo, widestColLen := 0, 0
for i, m := range max {
if m > widestColLen {
widestColNo, widestColLen = i, m
}
}
newTaskColWidth := max[widestColNo] - shortenWith
if newTaskColWidth < 0 {
return "table is too wide to display\n"
}
if newTaskColWidth < max[widestColNo] {
for r, row := range data {
data[r][widestColNo] = row[widestColNo][:newTaskColWidth]
}
}
}
// print the rows
var output string
for r, row := range data {
if r%3 == 0 {
output += fmt.Sprintf("%s", "\x1b[48;5;237m")
}
for c, col := range row {
output += col
if c != len(row)-1 {
output += " "
}
}
if r%3 == 0 {
output += fmt.Sprintf("%s", "\x1b[49m")
}
output += "\n"
}
return output
}
func findTermWidth() int {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err := cmd.Output()
if err != nil {
return 0
}
s := string(out)
s = strings.TrimSpace(s)
sArr := strings.Split(s, " ")
width, err := strconv.Atoi(sArr[1])
if err != nil {
return 0
}
return width
}

View File

@ -18,19 +18,6 @@ func NewLocalID() *LocalID {
} }
} }
func (ml *LocalID) FindOne(lid int) (string, error) {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
for id, l := range ml.ids {
if lid == l {
return id, nil
}
}
return "", storage.ErrNotFound
}
func (ml *LocalID) FindAll() (map[string]int, error) { func (ml *LocalID) FindAll() (map[string]int, error) {
ml.mutex.RLock() ml.mutex.RLock()
defer ml.mutex.RUnlock() defer ml.mutex.RUnlock()

View File

@ -54,21 +54,6 @@ func TestLocalID(t *testing.T) {
t.Errorf("exp 2, got %v", actLid) t.Errorf("exp 2, got %v", actLid)
} }
t.Log("find by local id")
actID, actErr := repo.FindOne(1)
if actErr != nil {
t.Errorf("exp nil, got %v", actErr)
}
if actID != "test" {
t.Errorf("exp test, got %v", actID)
}
t.Log("unknown local id")
actID, actErr = repo.FindOne(2)
if !errors.Is(actErr, storage.ErrNotFound) {
t.Errorf("exp ErrNotFound, got %v", actErr)
}
actIDs, actErr = repo.FindAll() actIDs, actErr = repo.FindAll()
if actErr != nil { if actErr != nil {
t.Errorf("exp nil, got %v", actErr) t.Errorf("exp nil, got %v", actErr)

View File

@ -19,7 +19,7 @@ func NewTask() *Task {
} }
} }
func (t *Task) FindOne(id string) (item.Task, error) { func (t *Task) Find(id string) (item.Task, error) {
t.mutex.RLock() t.mutex.RLock()
defer t.mutex.RUnlock() defer t.mutex.RUnlock()
@ -30,16 +30,14 @@ func (t *Task) FindOne(id string) (item.Task, error) {
return task, nil return task, nil
} }
func (t *Task) FindMany(params storage.TaskListParams) ([]item.Task, error) { func (t *Task) FindAll() ([]item.Task, error) {
t.mutex.RLock() t.mutex.RLock()
defer t.mutex.RUnlock() defer t.mutex.RUnlock()
tasks := make([]item.Task, 0, len(t.tasks)) tasks := make([]item.Task, 0, len(t.tasks))
for _, tsk := range t.tasks { for _, tsk := range t.tasks {
if storage.Match(tsk, params) {
tasks = append(tasks, tsk) tasks = append(tasks, tsk)
} }
}
sort.Slice(tasks, func(i, j int) bool { sort.Slice(tasks, func(i, j int) bool {
return tasks[i].ID < tasks[j].ID return tasks[i].ID < tasks[j].ID
}) })

View File

@ -4,7 +4,6 @@ import (
"testing" "testing"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/storage"
) )
func TestTask(t *testing.T) { func TestTask(t *testing.T) {
@ -13,7 +12,7 @@ func TestTask(t *testing.T) {
mem := NewTask() mem := NewTask()
t.Log("empty") t.Log("empty")
actTasks, actErr := mem.FindMany(storage.TaskListParams{}) actTasks, actErr := mem.FindAll()
if actErr != nil { if actErr != nil {
t.Errorf("exp nil, got %v", actErr) t.Errorf("exp nil, got %v", actErr)
} }
@ -24,7 +23,6 @@ func TestTask(t *testing.T) {
t.Log("store") t.Log("store")
tsk1 := item.Task{ tsk1 := item.Task{
ID: "id-1", ID: "id-1",
Date: item.NewDate(2024, 12, 29),
} }
if err := mem.Store(tsk1); err != nil { if err := mem.Store(tsk1); err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
@ -38,7 +36,7 @@ func TestTask(t *testing.T) {
} }
t.Log("find one") t.Log("find one")
actTask, actErr := mem.FindOne(tsk1.ID) actTask, actErr := mem.Find(tsk1.ID)
if actErr != nil { if actErr != nil {
t.Errorf("exp nil, got %v", actErr) t.Errorf("exp nil, got %v", actErr)
} }
@ -47,23 +45,11 @@ func TestTask(t *testing.T) {
} }
t.Log("find all") t.Log("find all")
actTasks, actErr = mem.FindMany(storage.TaskListParams{}) actTasks, actErr = mem.FindAll()
if actErr != nil { if actErr != nil {
t.Errorf("exp nil, got %v", actErr) t.Errorf("exp nil, got %v", actErr)
} }
if diff := item.TaskDiffs([]item.Task{tsk1, tsk2}, actTasks); diff != "" { if diff := item.TaskDiffs([]item.Task{tsk1, tsk2}, actTasks); diff != "" {
t.Errorf("(exp -, got +)\n%s", diff) t.Errorf("(exp -, got +)\n%s", diff)
} }
t.Log("fond some")
actTasks, actErr = mem.FindMany(storage.TaskListParams{
Date: item.NewDate(2024, 12, 29),
})
if actErr != nil {
t.Errorf("exp nil, got %v", actErr)
}
if diff := item.TaskDiffs([]item.Task{tsk1}, actTasks); diff != "" {
t.Errorf("(exp -, got +)\n%s", diff)
}
} }

View File

@ -2,7 +2,6 @@ package sqlite
import ( import (
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"go-mod.ewintr.nl/planner/plan/storage" "go-mod.ewintr.nl/planner/plan/storage"
@ -12,23 +11,6 @@ type LocalID struct {
db *sql.DB db *sql.DB
} }
func (l *LocalID) FindOne(lid int) (string, error) {
var id string
err := l.db.QueryRow(`
SELECT id
FROM localids
WHERE local_id = ?
`, lid).Scan(&id)
switch {
case errors.Is(err, sql.ErrNoRows):
return "", storage.ErrNotFound
case err != nil:
return "", fmt.Errorf("%w: %v", ErrSqliteFailure, err)
}
return id, nil
}
func (l *LocalID) FindAll() (map[string]int, error) { func (l *LocalID) FindAll() (map[string]int, error) {
rows, err := l.db.Query(` rows, err := l.db.Query(`
SELECT id, local_id SELECT id, local_id

View File

@ -38,7 +38,7 @@ recurrer=?
return nil return nil
} }
func (t *SqliteTask) FindOne(id string) (item.Task, error) { func (t *SqliteTask) Find(id string) (item.Task, error) {
var tsk item.Task var tsk item.Task
var dateStr, timeStr, recurStr, durStr string var dateStr, timeStr, recurStr, durStr string
err := t.db.QueryRow(` err := t.db.QueryRow(`
@ -63,35 +63,14 @@ WHERE id = ?`, id).Scan(&tsk.ID, &tsk.Title, &dateStr, &timeStr, &durStr, &recur
return tsk, nil return tsk, nil
} }
func (t *SqliteTask) FindMany(params storage.TaskListParams) ([]item.Task, error) { func (t *SqliteTask) FindAll() ([]item.Task, error) {
query := `SELECT id, title, date, time, duration, recurrer FROM tasks` rows, err := t.db.Query(`
args := []interface{}{} SELECT id, title, date, time, duration, recurrer
where := []string{} FROM tasks`)
if params.Recurrer {
where = append(where, `recurrer IS NOT NULL AND recurrer != ''`)
}
if !params.Date.IsZero() && !params.IncludeBefore {
where = append(where, `date = ?`)
args = append(args, params.Date.String())
}
if !params.Date.IsZero() && params.IncludeBefore {
where = append(where, `date <= ?`)
args = append(args, params.Date.String())
}
if len(where) > 0 {
query += ` WHERE ` + where[0]
for _, w := range where[1:] {
query += ` AND ` + w
}
}
rows, err := t.db.Query(query, args...)
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err) return nil, fmt.Errorf("%w: %v", ErrSqliteFailure, err)
} }
tasks := make([]item.Task, 0) result := make([]item.Task, 0)
defer rows.Close() defer rows.Close()
for rows.Next() { for rows.Next() {
var tsk item.Task var tsk item.Task
@ -108,10 +87,10 @@ func (t *SqliteTask) FindMany(params storage.TaskListParams) ([]item.Task, error
tsk.Duration = dur tsk.Duration = dur
tsk.Recurrer = item.NewRecurrer(recurStr) tsk.Recurrer = item.NewRecurrer(recurStr)
tasks = append(tasks, tsk) result = append(result, tsk)
} }
return tasks, nil return result, nil
} }
func (s *SqliteTask) Delete(id string) error { func (s *SqliteTask) Delete(id string) error {

View File

@ -13,7 +13,6 @@ var (
) )
type LocalID interface { type LocalID interface {
FindOne(lid int) (string, error)
FindAll() (map[string]int, error) FindAll() (map[string]int, error)
FindOrNext(id string) (int, error) FindOrNext(id string) (int, error)
Next() (int, error) Next() (int, error)
@ -28,35 +27,13 @@ type Sync interface {
LastUpdate() (time.Time, error) LastUpdate() (time.Time, error)
} }
type TaskListParams struct {
Recurrer bool
Date item.Date
IncludeBefore bool
}
type Task interface { type Task interface {
Store(task item.Task) error Store(task item.Task) error
FindOne(id string) (item.Task, error) Find(id string) (item.Task, error)
FindMany(params TaskListParams) ([]item.Task, error) FindAll() ([]item.Task, error)
Delete(id string) error Delete(id string) error
} }
func Match(tsk item.Task, params TaskListParams) bool {
if params.Recurrer && tsk.Recurrer == nil {
return false
}
if !params.Date.IsZero() {
if !params.IncludeBefore && !params.Date.Equal(tsk.Date) {
return false
}
if params.IncludeBefore && tsk.Date.After(params.Date) {
return false
}
}
return true
}
func NextLocalID(used []int) int { func NextLocalID(used []int) int {
if len(used) == 0 { if len(used) == 0 {
return 1 return 1

View File

@ -3,60 +3,10 @@ package storage_test
import ( import (
"testing" "testing"
"go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/storage" "go-mod.ewintr.nl/planner/plan/storage"
) )
func TestMatch(t *testing.T) {
t.Parallel()
tskMatch := item.Task{
ID: "id",
Date: item.NewDate(2024, 12, 29),
Recurrer: item.NewRecurrer("2024-12-29, daily"),
TaskBody: item.TaskBody{
Title: "name",
},
}
tskNotMatch := item.Task{
ID: "id",
Date: item.NewDate(2024, 12, 28),
TaskBody: item.TaskBody{
Title: "name",
},
}
for _, tc := range []struct {
name string
params storage.TaskListParams
}{
{
name: "date",
params: storage.TaskListParams{
Date: item.NewDate(2024, 12, 29),
},
},
{
name: "recurrer",
params: storage.TaskListParams{
Recurrer: true,
},
},
} {
t.Run(tc.name, func(t *testing.T) {
if !storage.Match(tskMatch, tc.params) {
t.Errorf("exp tsk to match")
}
if storage.Match(tskNotMatch, tc.params) {
t.Errorf("exp tsk to not match")
}
})
}
}
func TestNextLocalId(t *testing.T) { func TestNextLocalId(t *testing.T) {
t.Parallel()
for _, tc := range []struct { for _, tc := range []struct {
name string name string
used []int used []int