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 ( import (
"fmt" "fmt"
"strings"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
@ -19,6 +20,8 @@ type AddCmd struct {
localIDRepo storage.LocalID localIDRepo storage.LocalID
eventRepo storage.Event eventRepo storage.Event
syncRepo storage.Sync syncRepo storage.Sync
title string
flags map[string]Flag
} }
func NewAddCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) Command { 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, localIDRepo: localRepo,
eventRepo: eventRepo, eventRepo: eventRepo,
syncRepo: syncRepo, syncRepo: syncRepo,
flags: map[string]Flag{
FlagOn: &FlagDate{},
FlagAt: &FlagTime{},
FlagFor: &FlagDuration{},
},
} }
} }
func (add *AddCmd) Parse(args []string) (*ArgSet, error) { func (add *AddCmd) Parse(as *ArgSet) error {
if len(args) == 0 || args[0] != "add" { if len(as.Main) == 0 || as.Main[0] != "add " {
return nil, ErrWrongCommand return ErrWrongCommand
}
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")
} }
as, err := ParseArgs(args[1:])
if err != nil {
return nil, err
} }
if as.Main == "" { return nil
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
} }
func (add *AddCmd) Do(as *ArgSet) error { func (add *AddCmd) Do() error {
startFormat := "2006-01-02" startFormat := "2006-01-02"
startStr := as.Flag(FlagOn) startStr := as.Flag(FlagOn)
if as.HasFlag(FlagAt) { if as.HasFlag(FlagAt) {

View File

@ -2,9 +2,12 @@ package command_test
import ( import (
"testing" "testing"
"time"
"github.com/google/go-cmp/cmp" "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/command"
"go-mod.ewintr.nl/planner/plan/storage/memory"
) )
func TestAddParse(t *testing.T) { 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) { t.Run(tc.name, func(t *testing.T) {
@ -72,144 +89,125 @@ func TestAddParse(t *testing.T) {
} }
} }
// func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {
// t.Parallel() t.Parallel()
// oneHour, err := time.ParseDuration("1h") oneHour, err := time.ParseDuration("1h")
// if err != nil { if err != nil {
// t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
// } }
// oneDay, err := time.ParseDuration("24h") oneDay, err := time.ParseDuration("24h")
// if err != nil { if err != nil {
// t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
// } }
// for _, tc := range []struct { for _, tc := range []struct {
// name string name string
// args map[string]string args *command.ArgSet
// expEvent item.Event expEvent item.Event
// expErr bool expErr bool
// }{ }{
// { {
// name: "no name", name: "time, but no duration",
// args: map[string]string{ args: &command.ArgSet{
// "on": "2024-10-01", Main: "event",
// "at": "9:00", Flags: map[string]string{
// "for": "1h", command.FlagOn: "2024-10-01",
// }, command.FlagAt: "9:00",
// expErr: true, },
// }, },
// { expEvent: item.Event{
// name: "no date", ID: "a",
// args: map[string]string{ EventBody: item.EventBody{
// "name": "event", Title: "event",
// "at": "9:00", Start: time.Date(2024, 10, 1, 9, 0, 0, 0, time.UTC),
// "for": "1h", },
// }, },
// expErr: true, },
// }, {
// { name: "no time, no duration",
// name: "duration, but no time", args: &command.ArgSet{
// args: map[string]string{ Main: "event",
// "name": "event", Flags: map[string]string{
// "on": "2024-10-01", command.FlagOn: "2024-10-01",
// "for": "1h", command.FlagFor: "24h",
// }, },
// expErr: true, },
// }, expEvent: item.Event{
// { ID: "a",
// name: "time, but no duration", EventBody: item.EventBody{
// args: map[string]string{ Title: "event",
// "name": "event", Start: time.Date(2024, 10, 1, 0, 0, 0, 0, time.UTC),
// "on": "2024-10-01", Duration: oneDay,
// "at": "9:00", },
// }, },
// expEvent: item.Event{ },
// ID: "a", {
// EventBody: item.EventBody{ name: "full",
// Title: "event", args: &command.ArgSet{
// Start: time.Date(2024, 10, 1, 9, 0, 0, 0, time.UTC), Main: "event",
// }, Flags: map[string]string{
// }, command.FlagOn: "2024-10-01",
// }, command.FlagAt: "9:00",
// { command.FlagFor: "1h",
// name: "no time, no duration", },
// args: map[string]string{ },
// "name": "event", expEvent: item.Event{
// "on": "2024-10-01", ID: "a",
// }, EventBody: item.EventBody{
// expEvent: item.Event{ Title: "event",
// ID: "a", Start: time.Date(2024, 10, 1, 9, 0, 0, 0, time.UTC),
// EventBody: item.EventBody{ Duration: oneHour,
// Title: "event", },
// Start: time.Date(2024, 10, 1, 0, 0, 0, 0, time.UTC), },
// Duration: oneDay, },
// }, } {
// }, t.Run(tc.name, func(t *testing.T) {
// }, eventRepo := memory.NewEvent()
// { localRepo := memory.NewLocalID()
// name: "full", syncRepo := memory.NewSync()
// args: map[string]string{ cmd := command.NewAddCmd(localRepo, eventRepo, syncRepo)
// "name": "event", actErr := cmd.Do(tc.args) != nil
// "on": "2024-10-01", if tc.expErr != actErr {
// "at": "9:00", t.Errorf("exp %v, got %v", tc.expErr, actErr)
// "for": "1h", }
// }, if tc.expErr {
// expEvent: item.Event{ return
// ID: "a", }
// EventBody: item.EventBody{ actEvents, err := eventRepo.FindAll()
// Title: "event", if err != nil {
// Start: time.Date(2024, 10, 1, 9, 0, 0, 0, time.UTC), t.Errorf("exp nil, got %v", err)
// Duration: oneHour, }
// }, if len(actEvents) != 1 {
// }, t.Errorf("exp 1, got %d", len(actEvents))
// }, }
// } {
// 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))
// }
// actLocalIDs, err := localRepo.FindAll() actLocalIDs, err := localRepo.FindAll()
// if err != nil { if err != nil {
// t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
// } }
// if len(actLocalIDs) != 1 { if len(actLocalIDs) != 1 {
// t.Errorf("exp 1, got %v", len(actLocalIDs)) t.Errorf("exp 1, got %v", len(actLocalIDs))
// } }
// if _, ok := actLocalIDs[actEvents[0].ID]; !ok { if _, ok := actLocalIDs[actEvents[0].ID]; !ok {
// t.Errorf("exp true, got %v", ok) t.Errorf("exp true, got %v", ok)
// } }
// if actEvents[0].ID == "" { if actEvents[0].ID == "" {
// t.Errorf("exp string not te be empty") t.Errorf("exp string not te be empty")
// } }
// tc.expEvent.ID = actEvents[0].ID tc.expEvent.ID = actEvents[0].ID
// if diff := cmp.Diff(tc.expEvent, actEvents[0]); diff != "" { if diff := cmp.Diff(tc.expEvent, actEvents[0]); diff != "" {
// t.Errorf("(exp +, got -)\n%s", diff) t.Errorf("(exp +, got -)\n%s", diff)
// } }
// updated, err := syncRepo.FindAll() updated, err := syncRepo.FindAll()
// if err != nil { if err != nil {
// t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
// } }
// if len(updated) != 1 { if len(updated) != 1 {
// t.Errorf("exp 1, got %v", len(updated)) t.Errorf("exp 1, got %v", len(updated))
// } }
// }) })
// } }
// } }

View File

@ -6,32 +6,14 @@ import (
"strings" "strings"
) )
var (
ErrWrongCommand = errors.New("wrong command")
ErrInvalidArg = errors.New("invalid argument")
)
type ArgSet struct { type ArgSet struct {
Main string Main []string
Flags map[string]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 { type Command interface {
Parse(args []string) (*ArgSet, error) Parse(args *ArgSet) error
Do(args *ArgSet) error Do() error
} }
type CLI struct { type CLI struct {
@ -39,22 +21,26 @@ type CLI struct {
} }
func (cli *CLI) Run(args []string) error { func (cli *CLI) Run(args []string) error {
as, err := ParseFlags(args)
if err != nil {
return err
}
for _, c := range cli.Commands { for _, c := range cli.Commands {
as, err := c.Parse(args) err := c.Parse(as)
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: default:
return c.Do(as) return c.Do()
} }
} }
return fmt.Errorf("could not find matching command") 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) flags := make(map[string]string)
main := make([]string, 0) main := make([]string, 0)
var inMain bool var inMain bool
@ -62,7 +48,7 @@ func ParseArgs(args []string) (*ArgSet, error) {
if strings.HasPrefix(args[i], "-") { if strings.HasPrefix(args[i], "-") {
inMain = false inMain = false
if i+1 >= len(args) { 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] flags[strings.TrimPrefix(args[i], "-")] = args[i+1]
i++ i++
@ -70,14 +56,14 @@ func ParseArgs(args []string) (*ArgSet, error) {
} }
if !inMain && len(main) > 0 { if !inMain && len(main) > 0 {
return &ArgSet{}, fmt.Errorf("two mains") return nil, fmt.Errorf("two mains")
} }
inMain = true inMain = true
main = append(main, args[i]) main = append(main, args[i])
} }
return &ArgSet{ return &ArgSet{
Main: strings.Join(main, " "), Main: main,
Flags: flags, Flags: flags,
}, nil }, 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"
}