From 5b5ae5727db51e7a540f8bc481deae1edddb801f Mon Sep 17 00:00:00 2001 From: Erik Winter Date: Tue, 29 Oct 2024 07:22:04 +0100 Subject: [PATCH] own argument parser and flags --- item/event.go | 14 ++++ item/event_test.go | 69 ++++++++++++++++ plan/command/add.go | 130 ++++++++++++++--------------- plan/command/add_test.go | 154 ++++++++++++++++++----------------- plan/command/argset.go | 63 ++++++++++++++ plan/command/argset_test.go | 110 +++++++++++++++++++++++++ plan/command/command.go | 65 +++++++++++++++ plan/command/command_test.go | 109 +++++++++++++++++++++++++ plan/command/delete.go | 64 +++++++++------ plan/command/delete_test.go | 26 ++++-- plan/command/flag.go | 109 +++++++++++++++++++++++++ plan/command/flag_test.go | 115 ++++++++++++++++++++++++++ plan/command/list.go | 29 ++++--- plan/command/list_test.go | 58 +++++++++++++ plan/command/show.go | 1 + plan/command/sync.go | 58 +++++++------ plan/command/sync_test.go | 44 +++++++++- plan/command/update.go | 139 +++++++++++++++++-------------- plan/command/update_test.go | 50 ++++++------ plan/main.go | 19 ++--- plan/storage/memory/event.go | 6 +- 21 files changed, 1117 insertions(+), 315 deletions(-) create mode 100644 plan/command/argset.go create mode 100644 plan/command/argset_test.go create mode 100644 plan/command/command.go create mode 100644 plan/command/command_test.go create mode 100644 plan/command/flag.go create mode 100644 plan/command/flag_test.go create mode 100644 plan/command/list_test.go create mode 100644 plan/command/show.go diff --git a/item/event.go b/item/event.go index aa11e5d..5a7fbfc 100644 --- a/item/event.go +++ b/item/event.go @@ -86,3 +86,17 @@ func (e Event) Item() (Item, error) { Body: string(body), }, nil } + +func (e Event) Valid() bool { + if e.Title == "" { + return false + } + if e.Start.IsZero() || e.Start.Year() < 2024 { + return false + } + if e.Duration.Seconds() < 1 { + return false + } + + return true +} diff --git a/item/event_test.go b/item/event_test.go index 39c5fb3..2e78cda 100644 --- a/item/event_test.go +++ b/item/event_test.go @@ -132,3 +132,72 @@ func TestEventItem(t *testing.T) { }) } } + +func TestEventValidate(t *testing.T) { + t.Parallel() + + oneHour, err := time.ParseDuration("1h") + if err != nil { + t.Errorf("exp nil, got %v", err) + } + + for _, tc := range []struct { + name string + event item.Event + exp bool + }{ + { + name: "empty", + }, + { + name: "missing title", + event: item.Event{ + ID: "a", + EventBody: item.EventBody{ + Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC), + Duration: oneHour, + }, + }, + }, + { + name: "no date", + event: item.Event{ + ID: "a", + EventBody: item.EventBody{ + Title: "title", + Start: time.Date(0, 0, 0, 8, 0, 0, 0, time.UTC), + Duration: oneHour, + }, + }, + }, + { + name: "no duration", + event: item.Event{ + ID: "a", + EventBody: item.EventBody{ + Title: "title", + Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC), + }, + }, + }, + { + name: "valid", + event: item.Event{ + ID: "a", + EventBody: item.EventBody{ + Title: "title", + Start: time.Date(2024, 9, 20, 8, 0, 0, 0, time.UTC), + Duration: oneHour, + }, + }, + exp: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + if act := tc.event.Valid(); tc.exp != act { + t.Errorf("exp %v, got %v", tc.exp, act) + } + + }) + } +} diff --git a/plan/command/add.go b/plan/command/add.go index 8f07e46..7274159 100644 --- a/plan/command/add.go +++ b/plan/command/add.go @@ -1,105 +1,107 @@ package command import ( - "errors" "fmt" + "strings" "time" "github.com/google/uuid" - "github.com/urfave/cli/v2" "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/storage" ) -var ( - ErrInvalidArg = errors.New("invalid argument") -) - -var AddCmd = &cli.Command{ - Name: "add", - Usage: "Add a new event", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "name", - Aliases: []string{"n"}, - Usage: "The event that will happen", - Required: true, - }, - &cli.StringFlag{ - Name: "on", - Aliases: []string{"o"}, - Usage: "The date, in YYYY-MM-DD format", - Required: true, - }, - &cli.StringFlag{ - Name: "at", - Aliases: []string{"a"}, - Usage: "The time, in HH:MM format. If omitted, the event will last the whole day", - }, - &cli.StringFlag{ - Name: "for", - Aliases: []string{"f"}, - Usage: "The duration, in show format (e.g. 1h30m)", - }, - }, +type Add struct { + localIDRepo storage.LocalID + eventRepo storage.Event + syncRepo storage.Sync + argSet *ArgSet } -func NewAddCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) *cli.Command { - AddCmd.Action = func(cCtx *cli.Context) error { - return Add(localRepo, eventRepo, syncRepo, cCtx.String("name"), cCtx.String("on"), cCtx.String("at"), cCtx.String("for")) +func NewAdd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) Command { + return &Add{ + localIDRepo: localRepo, + eventRepo: eventRepo, + syncRepo: syncRepo, + argSet: &ArgSet{ + Flags: map[string]Flag{ + FlagOn: &FlagDate{}, + FlagAt: &FlagTime{}, + FlagFor: &FlagDuration{}, + }, + }, } - return AddCmd } -func Add(localIDRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync, nameStr, onStr, atStr, frStr string) error { - if nameStr == "" { - return fmt.Errorf("%w: name is required", ErrInvalidArg) +func (add *Add) Execute(main []string, flags map[string]string) error { + if len(main) == 0 || main[0] != "add" { + return ErrWrongCommand } - if onStr == "" { + as := add.argSet + if len(main) > 1 { + as.Main = strings.Join(main[1:], " ") + } + for k := range as.Flags { + v, ok := flags[k] + if !ok { + continue + } + if err := as.Set(k, v); err != nil { + return fmt.Errorf("could not set %s: %v", k, err) + } + } + if as.Main == "" { + return fmt.Errorf("%w: title is required", ErrInvalidArg) + } + if !as.IsSet(FlagOn) { return fmt.Errorf("%w: date is required", ErrInvalidArg) } - if atStr == "" && frStr != "" { + if !as.IsSet(FlagAt) && as.IsSet(FlagFor) { return fmt.Errorf("%w: can not have duration without start time", ErrInvalidArg) } - if atStr == "" && frStr == "" { - frStr = "24h" + if as.IsSet(FlagAt) && !as.IsSet(FlagFor) { + if err := as.Flags[FlagFor].Set("1h"); err != nil { + return fmt.Errorf("could not set duration to one hour") + } + } + if !as.IsSet(FlagAt) && !as.IsSet(FlagFor) { + if err := as.Flags[FlagFor].Set("24h"); err != nil { + return fmt.Errorf("could not set duration to 24 hours") + } } - startFormat := "2006-01-02" - startStr := onStr - if atStr != "" { - startFormat = fmt.Sprintf("%s 15:04", startFormat) - startStr = fmt.Sprintf("%s %s", startStr, atStr) - } - start, err := time.Parse(startFormat, startStr) - if err != nil { - return fmt.Errorf("%w: could not parse start time and date: %v", ErrInvalidArg, err) + return add.do() +} + +func (add *Add) do() error { + as := add.argSet + start := as.GetTime(FlagOn) + if as.IsSet(FlagAt) { + at := as.GetTime(FlagAt) + h := time.Duration(at.Hour()) * time.Hour + m := time.Duration(at.Minute()) * time.Minute + start = start.Add(h).Add(m) } e := item.Event{ ID: uuid.New().String(), EventBody: item.EventBody{ - Title: nameStr, + Title: as.Main, Start: start, }, } - if frStr != "" { - fr, err := time.ParseDuration(frStr) - if err != nil { - return fmt.Errorf("%w: could not parse duration: %s", ErrInvalidArg, err) - } - e.Duration = fr + if as.IsSet(FlagFor) { + e.Duration = as.GetDuration(FlagFor) } - if err := eventRepo.Store(e); err != nil { + if err := add.eventRepo.Store(e); err != nil { return fmt.Errorf("could not store event: %v", err) } - localID, err := localIDRepo.Next() + localID, err := add.localIDRepo.Next() if err != nil { return fmt.Errorf("could not create next local id: %v", err) } - if err := localIDRepo.Store(e.ID, localID); err != nil { + if err := add.localIDRepo.Store(e.ID, localID); err != nil { return fmt.Errorf("could not store local id: %v", err) } @@ -107,7 +109,7 @@ func Add(localIDRepo storage.LocalID, eventRepo storage.Event, syncRepo storage. if err != nil { return fmt.Errorf("could not convert event to sync item: %v", err) } - if err := syncRepo.Store(it); err != nil { + if err := add.syncRepo.Store(it); err != nil { return fmt.Errorf("could not store sync item: %v", err) } diff --git a/plan/command/add_test.go b/plan/command/add_test.go index b66826c..924821f 100644 --- a/plan/command/add_test.go +++ b/plan/command/add_test.go @@ -13,107 +13,109 @@ import ( func TestAdd(t *testing.T) { t.Parallel() - oneHour, err := time.ParseDuration("1h") - if err != nil { - t.Errorf("exp nil, got %v", err) - } - oneDay, err := time.ParseDuration("24h") - if err != nil { - t.Errorf("exp nil, got %v", err) - } + aDateStr := "2024-11-02" + aDate := time.Date(2024, 11, 2, 0, 0, 0, 0, time.UTC) + aTimeStr := "12:00" + aDay := time.Duration(24) * time.Hour + anHourStr := "1h" + anHour := time.Hour + aDateAndTime := time.Date(2024, 11, 2, 12, 0, 0, 0, time.UTC) for _, tc := range []struct { name string - args map[string]string - expEvent item.Event + main []string + flags map[string]string expErr bool + expEvent item.Event }{ { - name: "no name", - args: map[string]string{ - "on": "2024-10-01", - "at": "9:00", - "for": "1h", + name: "empty", + expErr: true, + }, + { + name: "title missing", + main: []string{"add"}, + flags: map[string]string{ + command.FlagOn: aDateStr, }, expErr: true, }, { - name: "no date", - args: map[string]string{ - "name": "event", - "at": "9:00", - "for": "1h", - }, + name: "date missing", + main: []string{"add", "some", "title"}, expErr: true, }, { - name: "duration, but no time", - args: map[string]string{ - "name": "event", - "on": "2024-10-01", - "for": "1h", + name: "only date", + main: []string{"add", "title"}, + flags: map[string]string{ + command.FlagOn: aDateStr, + }, + expEvent: item.Event{ + ID: "title", + EventBody: item.EventBody{ + Title: "title", + Start: aDate, + Duration: aDay, + }, + }, + }, + { + name: "date and time", + main: []string{"add", "title"}, + flags: map[string]string{ + command.FlagOn: aDateStr, + command.FlagAt: aTimeStr, + }, + expEvent: item.Event{ + ID: "title", + EventBody: item.EventBody{ + Title: "title", + Start: aDateAndTime, + Duration: anHour, + }, + }, + }, + { + name: "date, time and duration", + main: []string{"add", "title"}, + flags: map[string]string{ + command.FlagOn: aDateStr, + command.FlagAt: aTimeStr, + command.FlagFor: anHourStr, + }, + expEvent: item.Event{ + ID: "title", + EventBody: item.EventBody{ + Title: "title", + Start: aDateAndTime, + Duration: anHour, + }, + }, + }, + { + name: "date and duration", + main: []string{"add", "title"}, + flags: map[string]string{ + command.FlagOn: aDateStr, + command.FlagFor: anHourStr, }, expErr: true, }, - { - name: "time, but no duration", - args: map[string]string{ - "name": "event", - "on": "2024-10-01", - "at": "9:00", - }, - expEvent: item.Event{ - ID: "a", - EventBody: item.EventBody{ - Title: "event", - Start: time.Date(2024, 10, 1, 9, 0, 0, 0, time.UTC), - }, - }, - }, - { - name: "no time, no duration", - args: map[string]string{ - "name": "event", - "on": "2024-10-01", - }, - expEvent: item.Event{ - ID: "a", - EventBody: item.EventBody{ - Title: "event", - Start: time.Date(2024, 10, 1, 0, 0, 0, 0, time.UTC), - Duration: oneDay, - }, - }, - }, - { - name: "full", - args: map[string]string{ - "name": "event", - "on": "2024-10-01", - "at": "9:00", - "for": "1h", - }, - expEvent: item.Event{ - ID: "a", - EventBody: item.EventBody{ - Title: "event", - Start: time.Date(2024, 10, 1, 9, 0, 0, 0, time.UTC), - Duration: oneHour, - }, - }, - }, } { t.Run(tc.name, func(t *testing.T) { eventRepo := memory.NewEvent() localRepo := memory.NewLocalID() syncRepo := memory.NewSync() - actErr := command.Add(localRepo, eventRepo, syncRepo, tc.args["name"], tc.args["on"], tc.args["at"], tc.args["for"]) != nil - if tc.expErr != actErr { - t.Errorf("exp %v, got %v", tc.expErr, actErr) + cmd := command.NewAdd(localRepo, eventRepo, syncRepo) + actParseErr := cmd.Execute(tc.main, tc.flags) != nil + if tc.expErr != actParseErr { + t.Errorf("exp %v, got %v", tc.expErr, actParseErr) } if tc.expErr { return } + actEvents, err := eventRepo.FindAll() if err != nil { t.Errorf("exp nil, got %v", err) diff --git a/plan/command/argset.go b/plan/command/argset.go new file mode 100644 index 0000000..138b80d --- /dev/null +++ b/plan/command/argset.go @@ -0,0 +1,63 @@ +package command + +import ( + "fmt" + "time" +) + +type ArgSet struct { + Main string + Flags map[string]Flag +} + +func (as *ArgSet) Set(name, val string) error { + f, ok := as.Flags[name] + if !ok { + return fmt.Errorf("unknown flag %s", name) + } + return f.Set(val) +} + +func (as *ArgSet) IsSet(name string) bool { + f, ok := as.Flags[name] + if !ok { + return false + } + return f.IsSet() +} + +func (as *ArgSet) GetString(name string) string { + flag, ok := as.Flags[name] + if !ok { + return "" + } + val, ok := flag.Get().(string) + if !ok { + return "" + } + return val +} + +func (as *ArgSet) GetTime(name string) time.Time { + flag, ok := as.Flags[name] + if !ok { + return time.Time{} + } + val, ok := flag.Get().(time.Time) + if !ok { + return time.Time{} + } + return val +} + +func (as *ArgSet) GetDuration(name string) time.Duration { + flag, ok := as.Flags[name] + if !ok { + return time.Duration(0) + } + val, ok := flag.Get().(time.Duration) + if !ok { + return time.Duration(0) + } + return val +} diff --git a/plan/command/argset_test.go b/plan/command/argset_test.go new file mode 100644 index 0000000..e8711e8 --- /dev/null +++ b/plan/command/argset_test.go @@ -0,0 +1,110 @@ +package command_test + +import ( + "testing" + "time" + + "go-mod.ewintr.nl/planner/plan/command" +) + +func TestArgSet(t *testing.T) { + for _, tt := range []struct { + name string + flags map[string]command.Flag + flagName string + setValue string + exp interface{} + expErr bool + }{ + { + name: "string flag success", + flags: map[string]command.Flag{ + "title": &command.FlagString{Name: "title"}, + }, + flagName: "title", + setValue: "test title", + exp: "test title", + }, + { + name: "date flag success", + flags: map[string]command.Flag{ + "date": &command.FlagDate{Name: "date"}, + }, + flagName: "date", + setValue: "2024-01-02", + exp: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC), + }, + { + name: "time flag success", + flags: map[string]command.Flag{ + "time": &command.FlagTime{Name: "time"}, + }, + flagName: "time", + setValue: "15:04", + exp: time.Date(0, 1, 1, 15, 4, 0, 0, time.UTC), + }, + { + name: "duration flag success", + flags: map[string]command.Flag{ + "duration": &command.FlagDuration{Name: "duration"}, + }, + flagName: "duration", + setValue: "2h30m", + exp: 2*time.Hour + 30*time.Minute, + }, + { + name: "unknown flag error", + flags: map[string]command.Flag{}, + flagName: "unknown", + setValue: "value", + expErr: true, + }, + { + name: "invalid date format error", + flags: map[string]command.Flag{ + "date": &command.FlagDate{Name: "date"}, + }, + flagName: "date", + setValue: "invalid", + expErr: true, + }, + } { + t.Run(tt.name, func(t *testing.T) { + as := &command.ArgSet{ + Main: "test", + Flags: tt.flags, + } + + err := as.Set(tt.flagName, tt.setValue) + if (err != nil) != tt.expErr { + t.Errorf("ArgSet.Set() error = %v, expErr %v", err, tt.expErr) + return + } + + if tt.expErr { + return + } + + // Verify IsSet() returns true after setting + if !as.IsSet(tt.flagName) { + t.Errorf("ArgSet.IsSet() = false, want true for flag %s", tt.flagName) + } + + // Verify the value was set correctly based on flag type + switch v := tt.exp.(type) { + case string: + if got := as.GetString(tt.flagName); got != v { + t.Errorf("ArgSet.GetString() = %v, want %v", got, v) + } + case time.Time: + if got := as.GetTime(tt.flagName); !got.Equal(v) { + t.Errorf("ArgSet.GetTime() = %v, want %v", got, v) + } + case time.Duration: + if got := as.GetDuration(tt.flagName); got != v { + t.Errorf("ArgSet.GetDuration() = %v, want %v", got, v) + } + } + }) + } +} diff --git a/plan/command/command.go b/plan/command/command.go new file mode 100644 index 0000000..5e74178 --- /dev/null +++ b/plan/command/command.go @@ -0,0 +1,65 @@ +package command + +import ( + "errors" + "fmt" + "strings" +) + +const ( + FlagTitle = "title" + FlagOn = "on" + FlagAt = "at" + FlagFor = "for" +) + +type Command interface { + Execute([]string, map[string]string) error +} + +type CLI struct { + Commands []Command +} + +func (cli *CLI) Run(args []string) error { + main, flags, err := ParseFlags(args) + if err != nil { + return err + } + for _, c := range cli.Commands { + err := c.Execute(main, flags) + switch { + case errors.Is(err, ErrWrongCommand): + continue + case err != nil: + return err + } + } + + return fmt.Errorf("could not find matching command") +} + +func ParseFlags(args []string) ([]string, map[string]string, error) { + flags := make(map[string]string) + main := make([]string, 0) + var inMain bool + for i := 0; i < len(args); i++ { + if strings.HasPrefix(args[i], "-") { + inMain = false + if i+1 >= len(args) { + return nil, nil, fmt.Errorf("flag wihout value") + } + flags[strings.TrimPrefix(args[i], "-")] = args[i+1] + i++ + continue + } + + if !inMain && len(main) > 0 { + return nil, nil, fmt.Errorf("two mains") + } + inMain = true + main = append(main, args[i]) + } + + return main, flags, nil +} diff --git a/plan/command/command_test.go b/plan/command/command_test.go new file mode 100644 index 0000000..09241e0 --- /dev/null +++ b/plan/command/command_test.go @@ -0,0 +1,109 @@ +package command_test + +// func TestArgSet(t *testing.T) { +// t.Parallel() + +// as := command.ArgSet{ +// Main: "main", +// Flags: map[string]string{ +// "name 1": "value 1", +// "name 2": "value 2", +// "name 3": "value 3", +// }, +// } + +// t.Run("hasflag", func(t *testing.T) { +// t.Run("true", func(t *testing.T) { +// if has := as.HasFlag("name 1"); !has { +// t.Errorf("exp true, got %v", has) +// } +// }) +// t.Run("false", func(t *testing.T) { +// if has := as.HasFlag("unknown"); has { +// t.Errorf("exp false, got %v", has) +// } +// }) +// }) + +// t.Run("flag", func(t *testing.T) { +// t.Run("known", func(t *testing.T) { +// if val := as.Flag("name 1"); val != "value 1" { +// t.Errorf("exp value 1, got %v", val) +// } +// }) +// t.Run("unknown", func(t *testing.T) { +// if val := as.Flag("unknown"); val != "" { +// t.Errorf(`exp "", got %v`, val) +// } +// }) +// }) + +// t.Run("setflag", func(t *testing.T) { +// exp := "new value" +// as.SetFlag("new name", exp) +// if act := as.Flag("new name"); exp != act { +// t.Errorf("exp %v, got %v", exp, act) +// } +// }) +// } + +// func TestParseArgs(t *testing.T) { +// t.Parallel() + +// for _, tc := range []struct { +// name string +// args []string +// expAS *command.ArgSet +// expErr bool +// }{ +// { +// name: "empty", +// expAS: &command.ArgSet{ +// Flags: map[string]string{}, +// }, +// }, +// { +// name: "just main", +// args: []string{"one", "two three", "four"}, +// expAS: &command.ArgSet{ +// Main: "one two three four", +// Flags: map[string]string{}, +// }, +// }, +// { +// name: "with flags", +// args: []string{"-flag1", "value1", "one", "two", "-flag2", "value2", "-flag3", "value3"}, +// expAS: &command.ArgSet{ +// Main: "one two", +// Flags: map[string]string{ +// "flag1": "value1", +// "flag2": "value2", +// "flag3": "value3", +// }, +// }, +// }, +// { +// name: "flag without value", +// args: []string{"one", "two", "-flag1"}, +// expErr: true, +// }, +// { +// name: "split main", +// args: []string{"one", "-flag1", "value1", "two"}, +// expErr: true, +// }, +// } { +// t.Run(tc.name, func(t *testing.T) { +// actAS, actErr := command.ParseArgs(tc.args) +// if tc.expErr != (actErr != nil) { +// t.Errorf("exp %v, got %v", tc.expErr, actErr) +// } +// if tc.expErr { +// return +// } +// if diff := cmp.Diff(tc.expAS, actAS); diff != "" { +// t.Errorf("(exp +, got -)\n%s", diff) +// } +// }) +// } +// } diff --git a/plan/command/delete.go b/plan/command/delete.go index 8e35b14..9490b56 100644 --- a/plan/command/delete.go +++ b/plan/command/delete.go @@ -2,39 +2,47 @@ package command import ( "fmt" + "strconv" - "github.com/urfave/cli/v2" "go-mod.ewintr.nl/planner/plan/storage" ) -var DeleteCmd = &cli.Command{ - Name: "delete", - Usage: "Delete an event", - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "localID", - Aliases: []string{"l"}, - Usage: "The local id of the event", - Required: true, - }, - }, +type Delete struct { + localIDRepo storage.LocalID + eventRepo storage.Event + syncRepo storage.Sync + localID int } -func NewDeleteCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) *cli.Command { - DeleteCmd.Action = func(cCtx *cli.Context) error { - return Delete(localRepo, eventRepo, syncRepo, cCtx.Int("localID")) +func NewDelete(localIDRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) Command { + return &Delete{ + localIDRepo: localIDRepo, + eventRepo: eventRepo, + syncRepo: syncRepo, } - return DeleteCmd } -func Delete(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync, localID int) error { +func (del *Delete) Execute(main []string, flags map[string]string) error { + if len(main) < 2 || main[0] != "delete" { + return ErrWrongCommand + } + localID, err := strconv.Atoi(main[1]) + if err != nil { + return fmt.Errorf("not a local id: %v", main[1]) + } + del.localID = localID + + return del.do() +} + +func (del *Delete) do() error { var id string - idMap, err := localRepo.FindAll() + idMap, err := del.localIDRepo.FindAll() if err != nil { return fmt.Errorf("could not get local ids: %v", err) } for eid, lid := range idMap { - if localID == lid { + if del.localID == lid { id = eid } } @@ -42,11 +50,7 @@ func Delete(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage return fmt.Errorf("could not find local id") } - if err := eventRepo.Delete(id); err != nil { - return fmt.Errorf("could not delete event: %v", err) - } - - e, err := eventRepo.Find(id) + e, err := del.eventRepo.Find(id) if err != nil { return fmt.Errorf("could not get event: %v", err) } @@ -55,8 +59,18 @@ func Delete(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage if err != nil { return fmt.Errorf("could not convert event to sync item: %v", err) } - if err := syncRepo.Store(it); err != nil { + it.Deleted = true + if err := del.syncRepo.Store(it); err != nil { return fmt.Errorf("could not store sync item: %v", err) } + + if err := del.localIDRepo.Delete(id); err != nil { + return fmt.Errorf("could not delete local id: %v", err) + } + + if err := del.eventRepo.Delete(id); err != nil { + return fmt.Errorf("could not delete event: %v", err) + } + return nil } diff --git a/plan/command/delete_test.go b/plan/command/delete_test.go index 772562a..607516a 100644 --- a/plan/command/delete_test.go +++ b/plan/command/delete_test.go @@ -23,14 +23,24 @@ func TestDelete(t *testing.T) { } for _, tc := range []struct { - name string - localID int - expErr bool + name string + main []string + flags map[string]string + expErr bool }{ { - name: "not found", - localID: 5, - expErr: true, + name: "invalid", + main: []string{"update"}, + expErr: true, + }, + { + name: "not found", + main: []string{"delete", "5"}, + expErr: true, + }, + { + name: "valid", + main: []string{"delete", "1"}, }, } { t.Run(tc.name, func(t *testing.T) { @@ -44,7 +54,9 @@ func TestDelete(t *testing.T) { t.Errorf("exp nil, got %v", err) } - actErr := command.Delete(localRepo, eventRepo, syncRepo, tc.localID) != nil + cmd := command.NewDelete(localRepo, eventRepo, syncRepo) + + actErr := cmd.Execute(tc.main, tc.flags) != nil if tc.expErr != actErr { t.Errorf("exp %v, got %v", tc.expErr, actErr) } diff --git a/plan/command/flag.go b/plan/command/flag.go new file mode 100644 index 0000000..979a1a8 --- /dev/null +++ b/plan/command/flag.go @@ -0,0 +1,109 @@ +package command + +import ( + "errors" + "fmt" + "time" +) + +const ( + DateFormat = "2006-01-02" + TimeFormat = "15:04" +) + +var ( + ErrWrongCommand = errors.New("wrong command") + ErrInvalidArg = errors.New("invalid argument") +) + +type Flag interface { + Set(val string) error + IsSet() bool + Get() any +} + +type FlagString struct { + Name string + Value string +} + +func (fs *FlagString) Set(val string) error { + fs.Value = val + return nil +} + +func (fs *FlagString) IsSet() bool { + return fs.Value != "" +} + +func (fs *FlagString) Get() any { + return fs.Value +} + +type FlagDate struct { + Name string + Value time.Time +} + +func (ft *FlagDate) Set(val string) error { + d, err := time.Parse(DateFormat, val) + if err != nil { + return fmt.Errorf("could not parse date: %v", d) + } + ft.Value = d + + return nil +} + +func (ft *FlagDate) IsSet() bool { + return !ft.Value.IsZero() +} + +func (fs *FlagDate) Get() any { + return fs.Value +} + +type FlagTime struct { + Name string + Value time.Time +} + +func (ft *FlagTime) Set(val string) error { + d, err := time.Parse(TimeFormat, val) + if err != nil { + return fmt.Errorf("could not parse date: %v", d) + } + ft.Value = d + + return nil +} + +func (fd *FlagTime) IsSet() bool { + return !fd.Value.IsZero() +} + +func (fs *FlagTime) Get() any { + return fs.Value +} + +type FlagDuration struct { + Name string + Value time.Duration +} + +func (fd *FlagDuration) Set(val string) error { + dur, err := time.ParseDuration(val) + if err != nil { + return fmt.Errorf("could not parse duration: %v", err) + } + fd.Value = dur + return nil +} + +func (fd *FlagDuration) IsSet() bool { + return fd.Value.String() != "0s" +} + +func (fs *FlagDuration) Get() any { + return fs.Value +} diff --git a/plan/command/flag_test.go b/plan/command/flag_test.go new file mode 100644 index 0000000..a663d2c --- /dev/null +++ b/plan/command/flag_test.go @@ -0,0 +1,115 @@ +package command_test + +import ( + "testing" + "time" + + "go-mod.ewintr.nl/planner/plan/command" +) + +func TestFlagString(t *testing.T) { + t.Parallel() + + valid := "test" + f := command.FlagString{} + if f.IsSet() { + t.Errorf("exp false, got true") + } + + if err := f.Set(valid); err != nil { + t.Errorf("exp nil, got %v", err) + } + + if !f.IsSet() { + t.Errorf("exp true, got false") + } + + act, ok := f.Get().(string) + if !ok { + t.Errorf("exp true, got false") + } + if act != valid { + t.Errorf("exp %v, got %v", valid, act) + } +} + +func TestFlagDate(t *testing.T) { + t.Parallel() + + valid := time.Date(2024, 11, 20, 0, 0, 0, 0, time.UTC) + validStr := "2024-11-20" + f := command.FlagDate{} + if f.IsSet() { + t.Errorf("exp false, got true") + } + + if err := f.Set(validStr); err != nil { + t.Errorf("exp nil, got %v", err) + } + + if !f.IsSet() { + t.Errorf("exp true, got false") + } + + act, ok := f.Get().(time.Time) + if !ok { + t.Errorf("exp true, got false") + } + if act != valid { + t.Errorf("exp %v, got %v", valid, act) + } +} + +func TestFlagTime(t *testing.T) { + t.Parallel() + + valid := time.Date(0, 1, 1, 12, 30, 0, 0, time.UTC) + validStr := "12:30" + f := command.FlagTime{} + if f.IsSet() { + t.Errorf("exp false, got true") + } + + if err := f.Set(validStr); err != nil { + t.Errorf("exp nil, got %v", err) + } + + if !f.IsSet() { + t.Errorf("exp true, got false") + } + + act, ok := f.Get().(time.Time) + if !ok { + t.Errorf("exp true, got false") + } + if act != valid { + t.Errorf("exp %v, got %v", valid, act) + } +} + +func TestFlagDurationTime(t *testing.T) { + t.Parallel() + + valid := time.Hour + validStr := "1h" + f := command.FlagDuration{} + if f.IsSet() { + t.Errorf("exp false, got true") + } + + if err := f.Set(validStr); err != nil { + t.Errorf("exp nil, got %v", err) + } + + if !f.IsSet() { + t.Errorf("exp true, got false") + } + + act, ok := f.Get().(time.Duration) + if !ok { + t.Errorf("exp true, got false") + } + if act != valid { + t.Errorf("exp %v, got %v", valid, act) + } +} diff --git a/plan/command/list.go b/plan/command/list.go index 417c433..e94ca91 100644 --- a/plan/command/list.go +++ b/plan/command/list.go @@ -4,28 +4,35 @@ import ( "fmt" "time" - "github.com/urfave/cli/v2" "go-mod.ewintr.nl/planner/plan/storage" ) -var ListCmd = &cli.Command{ - Name: "list", - Usage: "List everything", +type List struct { + localIDRepo storage.LocalID + eventRepo storage.Event } -func NewListCmd(localRepo storage.LocalID, eventRepo storage.Event) *cli.Command { - ListCmd.Action = func(cCtx *cli.Context) error { - return List(localRepo, eventRepo) +func NewList(localIDRepo storage.LocalID, eventRepo storage.Event) Command { + return &List{ + localIDRepo: localIDRepo, + eventRepo: eventRepo, } - return ListCmd } -func List(localRepo storage.LocalID, eventRepo storage.Event) error { - localIDs, err := localRepo.FindAll() +func (list *List) Execute(main []string, flags map[string]string) error { + if len(main) > 0 && main[0] != "list" { + return ErrWrongCommand + } + + return list.do() +} + +func (list *List) do() error { + localIDs, err := list.localIDRepo.FindAll() if err != nil { return fmt.Errorf("could not get local ids: %v", err) } - all, err := eventRepo.FindAll() + all, err := list.eventRepo.FindAll() if err != nil { return err } diff --git a/plan/command/list_test.go b/plan/command/list_test.go new file mode 100644 index 0000000..53c8707 --- /dev/null +++ b/plan/command/list_test.go @@ -0,0 +1,58 @@ +package command_test + +import ( + "testing" + "time" + + "go-mod.ewintr.nl/planner/item" + "go-mod.ewintr.nl/planner/plan/command" + "go-mod.ewintr.nl/planner/plan/storage/memory" +) + +func TestList(t *testing.T) { + t.Parallel() + + eventRepo := memory.NewEvent() + localRepo := memory.NewLocalID() + e := item.Event{ + ID: "id", + EventBody: item.EventBody{ + Title: "name", + Start: time.Date(2024, 10, 7, 9, 30, 0, 0, time.UTC), + }, + } + if err := eventRepo.Store(e); err != nil { + t.Errorf("exp nil, got %v", err) + } + if err := localRepo.Store(e.ID, 1); err != nil { + t.Errorf("exp nil, got %v", err) + } + + for _, tc := range []struct { + name string + main []string + expErr bool + }{ + { + name: "empty", + main: []string{}, + }, + { + name: "list", + main: []string{"list"}, + }, + { + name: "wrong", + main: []string{"delete"}, + expErr: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + cmd := command.NewList(localRepo, eventRepo) + actErr := cmd.Execute(tc.main, nil) != nil + if tc.expErr != actErr { + t.Errorf("exp %v, got %v", tc.expErr, actErr) + } + }) + } +} diff --git a/plan/command/show.go b/plan/command/show.go new file mode 100644 index 0000000..d47dcf0 --- /dev/null +++ b/plan/command/show.go @@ -0,0 +1 @@ +package command diff --git a/plan/command/sync.go b/plan/command/sync.go index 178f499..1f100fb 100644 --- a/plan/command/sync.go +++ b/plan/command/sync.go @@ -5,50 +5,54 @@ import ( "errors" "fmt" - "github.com/urfave/cli/v2" "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/storage" "go-mod.ewintr.nl/planner/sync/client" ) -var SyncCmd = &cli.Command{ - Name: "sync", - Usage: "Synchronize with server", - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "full", - Aliases: []string{"f"}, - Usage: "Force full sync", - }, - }, +type Sync struct { + client client.Client + syncRepo storage.Sync + localIDRepo storage.LocalID + eventRepo storage.Event } -func NewSyncCmd(client client.Client, syncRepo storage.Sync, localIDRepo storage.LocalID, eventRepo storage.Event) *cli.Command { - SyncCmd.Action = func(cCtx *cli.Context) error { - return Sync(client, syncRepo, localIDRepo, eventRepo, cCtx.Bool("full")) +func NewSync(client client.Client, syncRepo storage.Sync, localIDRepo storage.LocalID, eventRepo storage.Event) Command { + return &Sync{ + client: client, + syncRepo: syncRepo, + localIDRepo: localIDRepo, + eventRepo: eventRepo, } - return SyncCmd } -func Sync(client client.Client, syncRepo storage.Sync, localIDRepo storage.LocalID, eventRepo storage.Event, full bool) error { +func (sync *Sync) Execute(main []string, flags map[string]string) error { + if len(main) == 0 || main[0] != "sync" { + return ErrWrongCommand + } + + return sync.do() +} + +func (sync *Sync) do() error { // local new and updated - sendItems, err := syncRepo.FindAll() + sendItems, err := sync.syncRepo.FindAll() if err != nil { return fmt.Errorf("could not get updated items: %v", err) } - if err := client.Update(sendItems); err != nil { + if err := sync.client.Update(sendItems); err != nil { return fmt.Errorf("could not send updated items: %v", err) } - if err := syncRepo.DeleteAll(); err != nil { + if err := sync.syncRepo.DeleteAll(); err != nil { return fmt.Errorf("could not clear updated items: %v", err) } // get new/updated items - ts, err := syncRepo.LastUpdate() + ts, err := sync.syncRepo.LastUpdate() if err != nil { return fmt.Errorf("could not find timestamp of last update: %v", err) } - recItems, err := client.Updated([]item.Kind{item.KindEvent}, ts) + recItems, err := sync.client.Updated([]item.Kind{item.KindEvent}, ts) if err != nil { return fmt.Errorf("could not receive updates: %v", err) } @@ -56,10 +60,10 @@ func Sync(client client.Client, syncRepo storage.Sync, localIDRepo storage.Local updated := make([]item.Item, 0) for _, ri := range recItems { if ri.Deleted { - if err := localIDRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) { + if err := sync.localIDRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) { return fmt.Errorf("could not delete local id: %v", err) } - if err := eventRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) { + if err := sync.eventRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) { return fmt.Errorf("could not delete event: %v", err) } continue @@ -67,7 +71,7 @@ func Sync(client client.Client, syncRepo storage.Sync, localIDRepo storage.Local updated = append(updated, ri) } - lidMap, err := localIDRepo.FindAll() + lidMap, err := sync.localIDRepo.FindAll() if err != nil { return fmt.Errorf("could not get local ids: %v", err) } @@ -80,17 +84,17 @@ func Sync(client client.Client, syncRepo storage.Sync, localIDRepo storage.Local ID: u.ID, EventBody: eBody, } - if err := eventRepo.Store(e); err != nil { + if err := sync.eventRepo.Store(e); err != nil { return fmt.Errorf("could not store event: %v", err) } lid, ok := lidMap[u.ID] if !ok { - lid, err = localIDRepo.Next() + lid, err = sync.localIDRepo.Next() if err != nil { return fmt.Errorf("could not get next local id: %v", err) } - if err := localIDRepo.Store(u.ID, lid); err != nil { + if err := sync.localIDRepo.Store(u.ID, lid); err != nil { return fmt.Errorf("could not store local id: %v", err) } } diff --git a/plan/command/sync_test.go b/plan/command/sync_test.go index cd0935b..c50151f 100644 --- a/plan/command/sync_test.go +++ b/plan/command/sync_test.go @@ -11,6 +11,43 @@ import ( "go-mod.ewintr.nl/planner/sync/client" ) +func TestSyncParse(t *testing.T) { + t.Parallel() + + syncClient := client.NewMemory() + syncRepo := memory.NewSync() + localIDRepo := memory.NewLocalID() + eventRepo := memory.NewEvent() + + for _, tc := range []struct { + name string + main []string + expErr bool + }{ + { + name: "empty", + expErr: true, + }, + { + name: "wrong", + main: []string{"wrong"}, + expErr: true, + }, + { + name: "valid", + main: []string{"sync"}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + cmd := command.NewSync(syncClient, syncRepo, localIDRepo, eventRepo) + actErr := cmd.Execute(tc.main, nil) != nil + if tc.expErr != actErr { + t.Errorf("exp %v, got %v", tc.expErr, actErr) + } + }) + } +} + func TestSyncSend(t *testing.T) { t.Parallel() @@ -45,8 +82,8 @@ func TestSyncSend(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - - if err := command.Sync(syncClient, syncRepo, localIDRepo, eventRepo, false); err != nil { + cmd := command.NewSync(syncClient, syncRepo, localIDRepo, eventRepo) + if err := cmd.Execute([]string{"sync"}, nil); err != nil { t.Errorf("exp nil, got %v", err) } actItems, actErr := syncClient.Updated(tc.ks, tc.ts) @@ -163,7 +200,8 @@ func TestSyncReceive(t *testing.T) { } // sync - if err := command.Sync(syncClient, syncRepo, localIDRepo, eventRepo, false); err != nil { + cmd := command.NewSync(syncClient, syncRepo, localIDRepo, eventRepo) + if err := cmd.Execute([]string{"sync"}, nil); err != nil { t.Errorf("exp nil, got %v", err) } diff --git a/plan/command/update.go b/plan/command/update.go index 883875a..689159c 100644 --- a/plan/command/update.go +++ b/plan/command/update.go @@ -2,60 +2,73 @@ package command import ( "fmt" + "strconv" + "strings" "time" - "github.com/urfave/cli/v2" "go-mod.ewintr.nl/planner/plan/storage" ) -var UpdateCmd = &cli.Command{ - Name: "update", - Usage: "Update an event", - Flags: []cli.Flag{ - &cli.IntFlag{ - Name: "localID", - Aliases: []string{"l"}, - Usage: "The local id of the event", - Required: true, - }, - &cli.StringFlag{ - Name: "name", - Aliases: []string{"n"}, - Usage: "The event that will happen", - }, - &cli.StringFlag{ - Name: "on", - Aliases: []string{"o"}, - Usage: "The date, in YYYY-MM-DD format", - }, - &cli.StringFlag{ - Name: "at", - Aliases: []string{"a"}, - Usage: "The time, in HH:MM format. If omitted, the event will last the whole day", - }, - &cli.StringFlag{ - Name: "for", - Aliases: []string{"f"}, - Usage: "The duration, in show format (e.g. 1h30m)", - }, - }, +type Update struct { + localIDRepo storage.LocalID + eventRepo storage.Event + syncRepo storage.Sync + argSet *ArgSet + localID int } -func NewUpdateCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) *cli.Command { - UpdateCmd.Action = func(cCtx *cli.Context) error { - return Update(localRepo, eventRepo, syncRepo, cCtx.Int("localID"), cCtx.String("name"), cCtx.String("on"), cCtx.String("at"), cCtx.String("for")) +func NewUpdate(localIDRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) Command { + return &Update{ + localIDRepo: localIDRepo, + eventRepo: eventRepo, + syncRepo: syncRepo, + argSet: &ArgSet{ + Flags: map[string]Flag{ + FlagTitle: &FlagString{}, + FlagOn: &FlagDate{}, + FlagAt: &FlagTime{}, + FlagFor: &FlagDuration{}, + }, + }, } - return UpdateCmd } -func Update(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync, localID int, nameStr, onStr, atStr, frStr string) error { +func (update *Update) Execute(main []string, flags map[string]string) error { + if len(main) < 2 || main[0] != "update" { + return ErrWrongCommand + } + localID, err := strconv.Atoi(main[1]) + if err != nil { + return fmt.Errorf("not a local id: %v", main[1]) + } + update.localID = localID + main = main[2:] + + as := update.argSet + as.Main = strings.Join(main, " ") + for k := range as.Flags { + v, ok := flags[k] + if !ok { + continue + } + if err := as.Set(k, v); err != nil { + return fmt.Errorf("could not set %s: %v", k, err) + } + } + update.argSet = as + + return update.do() +} + +func (update *Update) do() error { + as := update.argSet var id string - idMap, err := localRepo.FindAll() + idMap, err := update.localIDRepo.FindAll() if err != nil { return fmt.Errorf("could not get local ids: %v", err) } for eid, lid := range idMap { - if localID == lid { + if update.localID == lid { id = eid } } @@ -63,39 +76,39 @@ func Update(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage return fmt.Errorf("could not find local id") } - e, err := eventRepo.Find(id) + e, err := update.eventRepo.Find(id) if err != nil { return fmt.Errorf("could not find event") } - if nameStr != "" { - e.Title = nameStr + if as.Main != "" { + e.Title = as.Main } - if onStr != "" || atStr != "" { - oldStart := e.Start - dateStr := oldStart.Format("2006-01-02") - if onStr != "" { - dateStr = onStr + if as.IsSet(FlagOn) || as.IsSet(FlagAt) { + on := time.Date(e.Start.Year(), e.Start.Month(), e.Start.Day(), 0, 0, 0, 0, time.UTC) + atH := time.Duration(e.Start.Hour()) * time.Hour + atM := time.Duration(e.Start.Minute()) * time.Minute + + if as.IsSet(FlagOn) { + on = as.GetTime(FlagOn) } - timeStr := oldStart.Format("15:04") - if atStr != "" { - timeStr = atStr + if as.IsSet(FlagAt) { + at := as.GetTime(FlagAt) + atH = time.Duration(at.Hour()) * time.Hour + atM = time.Duration(at.Minute()) * time.Minute } - newStart, err := time.Parse("2006-01-02 15:04", fmt.Sprintf("%s %s", dateStr, timeStr)) - if err != nil { - return fmt.Errorf("could not parse new start: %v", err) - } - e.Start = newStart + e.Start = on.Add(atH).Add(atM) } - if frStr != "" { // no check on at, can set a duration with at 00:00, making it not a whole day - fr, err := time.ParseDuration(frStr) - if err != nil { - return fmt.Errorf("%w: could not parse duration: %s", ErrInvalidArg, err) - } - e.Duration = fr + if as.IsSet(FlagFor) { + e.Duration = as.GetDuration(FlagFor) } - if err := eventRepo.Store(e); err != nil { + + if !e.Valid() { + return fmt.Errorf("event is unvalid") + } + + if err := update.eventRepo.Store(e); err != nil { return fmt.Errorf("could not store event: %v", err) } @@ -103,7 +116,7 @@ func Update(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage if err != nil { return fmt.Errorf("could not convert event to sync item: %v", err) } - if err := syncRepo.Store(it); err != nil { + if err := update.syncRepo.Store(it); err != nil { return fmt.Errorf("could not store sync item: %v", err) } diff --git a/plan/command/update_test.go b/plan/command/update_test.go index 090a297..767ef2d 100644 --- a/plan/command/update_test.go +++ b/plan/command/update_test.go @@ -1,6 +1,7 @@ package command_test import ( + "fmt" "testing" "time" @@ -10,7 +11,7 @@ import ( "go-mod.ewintr.nl/planner/plan/storage/memory" ) -func TestUpdate(t *testing.T) { +func TestUpdateExecute(t *testing.T) { t.Parallel() eid := "c" @@ -29,21 +30,14 @@ func TestUpdate(t *testing.T) { for _, tc := range []struct { name string localID int - args map[string]string + main []string + flags map[string]string expEvent item.Event expErr bool }{ { - name: "no args", - localID: lid, - expEvent: item.Event{ - ID: eid, - EventBody: item.EventBody{ - Title: title, - Start: start, - Duration: oneHour, - }, - }, + name: "no args", + expErr: true, }, { name: "not found", @@ -53,9 +47,7 @@ func TestUpdate(t *testing.T) { { name: "name", localID: lid, - args: map[string]string{ - "name": "updated", - }, + main: []string{"update", fmt.Sprintf("%d", lid), "updated"}, expEvent: item.Event{ ID: eid, EventBody: item.EventBody{ @@ -68,7 +60,8 @@ func TestUpdate(t *testing.T) { { name: "invalid on", localID: lid, - args: map[string]string{ + main: []string{"update", fmt.Sprintf("%d", lid)}, + flags: map[string]string{ "on": "invalid", }, expErr: true, @@ -76,7 +69,8 @@ func TestUpdate(t *testing.T) { { name: "on", localID: lid, - args: map[string]string{ + main: []string{"update", fmt.Sprintf("%d", lid)}, + flags: map[string]string{ "on": "2024-10-02", }, expEvent: item.Event{ @@ -91,7 +85,8 @@ func TestUpdate(t *testing.T) { { name: "invalid at", localID: lid, - args: map[string]string{ + main: []string{"update", fmt.Sprintf("%d", lid)}, + flags: map[string]string{ "at": "invalid", }, expErr: true, @@ -99,7 +94,8 @@ func TestUpdate(t *testing.T) { { name: "at", localID: lid, - args: map[string]string{ + main: []string{"update", fmt.Sprintf("%d", lid)}, + flags: map[string]string{ "at": "11:00", }, expEvent: item.Event{ @@ -114,7 +110,8 @@ func TestUpdate(t *testing.T) { { name: "on and at", localID: lid, - args: map[string]string{ + main: []string{"update", fmt.Sprintf("%d", lid)}, + flags: map[string]string{ "on": "2024-10-02", "at": "11:00", }, @@ -130,7 +127,8 @@ func TestUpdate(t *testing.T) { { name: "invalid for", localID: lid, - args: map[string]string{ + main: []string{"update", fmt.Sprintf("%d", lid)}, + flags: map[string]string{ "for": "invalid", }, expErr: true, @@ -138,7 +136,8 @@ func TestUpdate(t *testing.T) { { name: "for", localID: lid, - args: map[string]string{ + main: []string{"update", fmt.Sprintf("%d", lid)}, + flags: map[string]string{ "for": "2h", }, expEvent: item.Event{ @@ -169,9 +168,10 @@ func TestUpdate(t *testing.T) { t.Errorf("exp nil, ,got %v", err) } - actErr := command.Update(localIDRepo, eventRepo, syncRepo, tc.localID, tc.args["name"], tc.args["on"], tc.args["at"], tc.args["for"]) != nil - if tc.expErr != actErr { - t.Errorf("exp %v, got %v", tc.expErr, actErr) + cmd := command.NewUpdate(localIDRepo, eventRepo, syncRepo) + actParseErr := cmd.Execute(tc.main, tc.flags) != nil + if tc.expErr != actParseErr { + t.Errorf("exp %v, got %v", tc.expErr, actParseErr) } if tc.expErr { return diff --git a/plan/main.go b/plan/main.go index d48a2e0..3205fdc 100644 --- a/plan/main.go +++ b/plan/main.go @@ -5,7 +5,6 @@ import ( "os" "path/filepath" - "github.com/urfave/cli/v2" "go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/storage/sqlite" "go-mod.ewintr.nl/planner/sync/client" @@ -32,19 +31,17 @@ func main() { syncClient := client.New(conf.SyncURL, conf.ApiKey) - app := &cli.App{ - Name: "plan", - Usage: "Plan your day with events", - Commands: []*cli.Command{ - command.NewAddCmd(localIDRepo, eventRepo, syncRepo), - command.NewListCmd(localIDRepo, eventRepo), - command.NewUpdateCmd(localIDRepo, eventRepo, syncRepo), - command.NewDeleteCmd(localIDRepo, eventRepo, syncRepo), - command.NewSyncCmd(syncClient, syncRepo, localIDRepo, eventRepo), + cli := command.CLI{ + Commands: []command.Command{ + command.NewAdd(localIDRepo, eventRepo, syncRepo), + command.NewList(localIDRepo, eventRepo), + command.NewUpdate(localIDRepo, eventRepo, syncRepo), + command.NewDelete(localIDRepo, eventRepo, syncRepo), + command.NewSync(syncClient, syncRepo, localIDRepo, eventRepo), }, } - if err := app.Run(os.Args); err != nil { + if err := cli.Run(os.Args); err != nil { fmt.Println(err) os.Exit(1) } diff --git a/plan/storage/memory/event.go b/plan/storage/memory/event.go index 32214ed..2a3afdb 100644 --- a/plan/storage/memory/event.go +++ b/plan/storage/memory/event.go @@ -1,11 +1,11 @@ package memory import ( - "errors" "sort" "sync" "go-mod.ewintr.nl/planner/item" + "go-mod.ewintr.nl/planner/plan/storage" ) type Event struct { @@ -25,7 +25,7 @@ func (r *Event) Find(id string) (item.Event, error) { event, exists := r.events[id] if !exists { - return item.Event{}, errors.New("event not found") + return item.Event{}, storage.ErrNotFound } return event, nil } @@ -59,7 +59,7 @@ func (r *Event) Delete(id string) error { defer r.mutex.Unlock() if _, exists := r.events[id]; !exists { - return errors.New("event not found") + return storage.ErrNotFound } delete(r.events, id)