From d1209b8af10d8b02ba5a95e56383a7cd47477f4f Mon Sep 17 00:00:00 2001 From: Erik Winter Date: Sun, 10 Nov 2024 15:04:14 +0100 Subject: [PATCH] wip --- plan/command/add.go | 53 +++++--- plan/command/add_test.go | 272 +++++++++++++++++++-------------------- plan/command/command.go | 40 ++---- plan/command/flag.go | 92 +++++++++++++ 4 files changed, 272 insertions(+), 185 deletions(-) create mode 100644 plan/command/flag.go diff --git a/plan/command/add.go b/plan/command/add.go index 716d59e..028b104 100644 --- a/plan/command/add.go +++ b/plan/command/add.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "strings" "time" "github.com/google/uuid" @@ -19,6 +20,8 @@ type AddCmd struct { localIDRepo storage.LocalID eventRepo storage.Event syncRepo storage.Sync + title string + flags map[string]Flag } func NewAddCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) Command { @@ -26,35 +29,43 @@ func NewAddCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo stor localIDRepo: localRepo, eventRepo: eventRepo, syncRepo: syncRepo, + flags: map[string]Flag{ + FlagOn: &FlagDate{}, + FlagAt: &FlagTime{}, + FlagFor: &FlagDuration{}, + }, } } -func (add *AddCmd) Parse(args []string) (*ArgSet, error) { - if len(args) == 0 || args[0] != "add" { - return nil, ErrWrongCommand +func (add *AddCmd) Parse(as *ArgSet) error { + if len(as.Main) == 0 || as.Main[0] != "add " { + return ErrWrongCommand } - as, err := ParseArgs(args[1:]) - if err != nil { - return nil, err + add.title = strings.Join(as.Main[1:], " ") + for k := range add.flags { + if err := add.flags[k].Set(as.Flags[k]); err != nil { + return fmt.Errorf("could not set %s: %v", k, err) + } + } + if add.title == "" { + return fmt.Errorf("%w: title is required", ErrInvalidArg) + } + if !add.flags[FlagOn].IsSet() { + return fmt.Errorf("%w: date is required", ErrInvalidArg) + } + if !add.flags[FlagAt].IsSet() && add.flags[FlagFor].IsSet() { + return fmt.Errorf("%w: can not have duration without start time", ErrInvalidArg) + } + if !add.flags[FlagAt].IsSet() && !add.flags[FlagFor].IsSet() { + if err := add.flags[FlagFor].Set("24h"); err != nil { + return fmt.Errorf("could not set duration to 24 hours") + } } - if as.Main == "" { - return nil, fmt.Errorf("%w: title is required", ErrInvalidArg) - } - if !as.HasFlag(FlagOn) { - return nil, fmt.Errorf("%w: date is required", ErrInvalidArg) - } - if !as.HasFlag(FlagAt) && as.HasFlag(FlagFor) { - return nil, fmt.Errorf("%w: can not have duration without start time", ErrInvalidArg) - } - if !as.HasFlag(FlagAt) && !as.HasFlag(FlagFor) { - as.SetFlag(FlagFor, "24h") - } - - return as, nil + return nil } -func (add *AddCmd) Do(as *ArgSet) error { +func (add *AddCmd) Do() error { startFormat := "2006-01-02" startStr := as.Flag(FlagOn) if as.HasFlag(FlagAt) { diff --git a/plan/command/add_test.go b/plan/command/add_test.go index bda5d5c..650d545 100644 --- a/plan/command/add_test.go +++ b/plan/command/add_test.go @@ -2,9 +2,12 @@ package command_test import ( "testing" + "time" "github.com/google/go-cmp/cmp" + "go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/plan/command" + "go-mod.ewintr.nl/planner/plan/storage/memory" ) func TestAddParse(t *testing.T) { @@ -54,7 +57,21 @@ func TestAddParse(t *testing.T) { }, }, { - // name: "start and duration" + name: "start and duration", + args: []string{"add", "title", "-on", "2024-11-10", "-at", "12:00", "-for", "1h"}, + expAS: &command.ArgSet{ + Main: "title", + Flags: map[string]string{ + command.FlagOn: "2024-11-10", + command.FlagAt: "12:00", + command.FlagFor: "1h", + }, + }, + }, + { + name: "start without duration", + args: []string{"add", "title", "-on", "2024-11-10", "-for", "1h"}, + expErr: true, }, } { t.Run(tc.name, func(t *testing.T) { @@ -72,144 +89,125 @@ func TestAddParse(t *testing.T) { } } -// func TestAdd(t *testing.T) { -// t.Parallel() +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) -// } + 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) + } -// for _, tc := range []struct { -// name string -// args map[string]string -// expEvent item.Event -// expErr bool -// }{ -// { -// name: "no name", -// args: map[string]string{ -// "on": "2024-10-01", -// "at": "9:00", -// "for": "1h", -// }, -// expErr: true, -// }, -// { -// name: "no date", -// args: map[string]string{ -// "name": "event", -// "at": "9:00", -// "for": "1h", -// }, -// expErr: true, -// }, -// { -// name: "duration, but no time", -// args: map[string]string{ -// "name": "event", -// "on": "2024-10-01", -// "for": "1h", -// }, -// 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) -// } -// if tc.expErr { -// return -// } -// actEvents, err := eventRepo.FindAll() -// if err != nil { -// t.Errorf("exp nil, got %v", err) -// } -// if len(actEvents) != 1 { -// t.Errorf("exp 1, got %d", len(actEvents)) -// } + for _, tc := range []struct { + name string + args *command.ArgSet + expEvent item.Event + expErr bool + }{ + { + name: "time, but no duration", + args: &command.ArgSet{ + Main: "event", + Flags: map[string]string{ + command.FlagOn: "2024-10-01", + command.FlagAt: "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: &command.ArgSet{ + Main: "event", + Flags: map[string]string{ + command.FlagOn: "2024-10-01", + command.FlagFor: "24h", + }, + }, + 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: &command.ArgSet{ + Main: "event", + Flags: map[string]string{ + command.FlagOn: "2024-10-01", + command.FlagAt: "9:00", + command.FlagFor: "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() + cmd := command.NewAddCmd(localRepo, eventRepo, syncRepo) + actErr := cmd.Do(tc.args) != nil + if tc.expErr != actErr { + t.Errorf("exp %v, got %v", tc.expErr, actErr) + } + if tc.expErr { + return + } + actEvents, err := eventRepo.FindAll() + if err != nil { + t.Errorf("exp nil, got %v", err) + } + if len(actEvents) != 1 { + t.Errorf("exp 1, got %d", len(actEvents)) + } -// actLocalIDs, err := localRepo.FindAll() -// if err != nil { -// t.Errorf("exp nil, got %v", err) -// } -// if len(actLocalIDs) != 1 { -// t.Errorf("exp 1, got %v", len(actLocalIDs)) -// } -// if _, ok := actLocalIDs[actEvents[0].ID]; !ok { -// t.Errorf("exp true, got %v", ok) -// } + actLocalIDs, err := localRepo.FindAll() + if err != nil { + t.Errorf("exp nil, got %v", err) + } + if len(actLocalIDs) != 1 { + t.Errorf("exp 1, got %v", len(actLocalIDs)) + } + if _, ok := actLocalIDs[actEvents[0].ID]; !ok { + t.Errorf("exp true, got %v", ok) + } -// if actEvents[0].ID == "" { -// t.Errorf("exp string not te be empty") -// } -// tc.expEvent.ID = actEvents[0].ID -// if diff := cmp.Diff(tc.expEvent, actEvents[0]); diff != "" { -// t.Errorf("(exp +, got -)\n%s", diff) -// } + if actEvents[0].ID == "" { + t.Errorf("exp string not te be empty") + } + tc.expEvent.ID = actEvents[0].ID + if diff := cmp.Diff(tc.expEvent, actEvents[0]); diff != "" { + t.Errorf("(exp +, got -)\n%s", diff) + } -// updated, err := syncRepo.FindAll() -// if err != nil { -// t.Errorf("exp nil, got %v", err) -// } -// if len(updated) != 1 { -// t.Errorf("exp 1, got %v", len(updated)) -// } -// }) -// } -// } + updated, err := syncRepo.FindAll() + if err != nil { + t.Errorf("exp nil, got %v", err) + } + if len(updated) != 1 { + t.Errorf("exp 1, got %v", len(updated)) + } + }) + } +} diff --git a/plan/command/command.go b/plan/command/command.go index 30c3389..4beaae1 100644 --- a/plan/command/command.go +++ b/plan/command/command.go @@ -6,32 +6,14 @@ import ( "strings" ) -var ( - ErrWrongCommand = errors.New("wrong command") - ErrInvalidArg = errors.New("invalid argument") -) - type ArgSet struct { - Main string + Main []string Flags map[string]string } -func (as *ArgSet) HasFlag(name string) bool { - _, ok := as.Flags[name] - return ok -} - -func (as *ArgSet) Flag(name string) string { - return as.Flags[name] -} - -func (as *ArgSet) SetFlag(name, value string) { - as.Flags[name] = value -} - type Command interface { - Parse(args []string) (*ArgSet, error) - Do(args *ArgSet) error + Parse(args *ArgSet) error + Do() error } type CLI struct { @@ -39,22 +21,26 @@ type CLI struct { } func (cli *CLI) Run(args []string) error { + as, err := ParseFlags(args) + if err != nil { + return err + } for _, c := range cli.Commands { - as, err := c.Parse(args) + err := c.Parse(as) switch { case errors.Is(err, ErrWrongCommand): continue case err != nil: return err default: - return c.Do(as) + return c.Do() } } return fmt.Errorf("could not find matching command") } -func ParseArgs(args []string) (*ArgSet, error) { +func ParseFlags(args []string) (*ArgSet, error) { flags := make(map[string]string) main := make([]string, 0) var inMain bool @@ -62,7 +48,7 @@ func ParseArgs(args []string) (*ArgSet, error) { if strings.HasPrefix(args[i], "-") { inMain = false if i+1 >= len(args) { - return &ArgSet{}, fmt.Errorf("flag wihout value") + return nil, fmt.Errorf("flag wihout value") } flags[strings.TrimPrefix(args[i], "-")] = args[i+1] i++ @@ -70,14 +56,14 @@ func ParseArgs(args []string) (*ArgSet, error) { } if !inMain && len(main) > 0 { - return &ArgSet{}, fmt.Errorf("two mains") + return nil, fmt.Errorf("two mains") } inMain = true main = append(main, args[i]) } return &ArgSet{ - Main: strings.Join(main, " "), + Main: main, Flags: flags, }, nil } diff --git a/plan/command/flag.go b/plan/command/flag.go new file mode 100644 index 0000000..fe6848c --- /dev/null +++ b/plan/command/flag.go @@ -0,0 +1,92 @@ +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 +} + +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 != "" +} + +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() +} + +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() +} + +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" +}