This commit is contained in:
Erik Winter 2024-11-10 15:04:14 +01:00
parent b7da3fba1e
commit d1209b8af1
4 changed files with 272 additions and 185 deletions

View File

@ -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) {

View File

@ -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))
}
})
}
}

View File

@ -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
}

92
plan/command/flag.go Normal file
View File

@ -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"
}