own argument parser and flags
This commit is contained in:
parent
6fe8561a6b
commit
5b5ae5727d
|
@ -86,3 +86,17 @@ func (e Event) Item() (Item, error) {
|
||||||
Body: string(body),
|
Body: string(body),
|
||||||
}, nil
|
}, 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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,105 +1,107 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go-mod.ewintr.nl/planner/item"
|
"go-mod.ewintr.nl/planner/item"
|
||||||
"go-mod.ewintr.nl/planner/plan/storage"
|
"go-mod.ewintr.nl/planner/plan/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Add struct {
|
||||||
ErrInvalidArg = errors.New("invalid argument")
|
localIDRepo storage.LocalID
|
||||||
)
|
eventRepo storage.Event
|
||||||
|
syncRepo storage.Sync
|
||||||
var AddCmd = &cli.Command{
|
argSet *ArgSet
|
||||||
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)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAddCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) *cli.Command {
|
func NewAdd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) Command {
|
||||||
AddCmd.Action = func(cCtx *cli.Context) error {
|
return &Add{
|
||||||
return Add(localRepo, eventRepo, syncRepo, cCtx.String("name"), cCtx.String("on"), cCtx.String("at"), cCtx.String("for"))
|
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 {
|
func (add *Add) Execute(main []string, flags map[string]string) error {
|
||||||
if nameStr == "" {
|
if len(main) == 0 || main[0] != "add" {
|
||||||
return fmt.Errorf("%w: name is required", ErrInvalidArg)
|
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)
|
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)
|
return fmt.Errorf("%w: can not have duration without start time", ErrInvalidArg)
|
||||||
}
|
}
|
||||||
if atStr == "" && frStr == "" {
|
if as.IsSet(FlagAt) && !as.IsSet(FlagFor) {
|
||||||
frStr = "24h"
|
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"
|
return add.do()
|
||||||
startStr := onStr
|
}
|
||||||
if atStr != "" {
|
|
||||||
startFormat = fmt.Sprintf("%s 15:04", startFormat)
|
func (add *Add) do() error {
|
||||||
startStr = fmt.Sprintf("%s %s", startStr, atStr)
|
as := add.argSet
|
||||||
}
|
start := as.GetTime(FlagOn)
|
||||||
start, err := time.Parse(startFormat, startStr)
|
if as.IsSet(FlagAt) {
|
||||||
if err != nil {
|
at := as.GetTime(FlagAt)
|
||||||
return fmt.Errorf("%w: could not parse start time and date: %v", ErrInvalidArg, err)
|
h := time.Duration(at.Hour()) * time.Hour
|
||||||
|
m := time.Duration(at.Minute()) * time.Minute
|
||||||
|
start = start.Add(h).Add(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
e := item.Event{
|
e := item.Event{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
EventBody: item.EventBody{
|
EventBody: item.EventBody{
|
||||||
Title: nameStr,
|
Title: as.Main,
|
||||||
Start: start,
|
Start: start,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if frStr != "" {
|
if as.IsSet(FlagFor) {
|
||||||
fr, err := time.ParseDuration(frStr)
|
e.Duration = as.GetDuration(FlagFor)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: could not parse duration: %s", ErrInvalidArg, err)
|
|
||||||
}
|
|
||||||
e.Duration = fr
|
|
||||||
}
|
}
|
||||||
if err := eventRepo.Store(e); err != nil {
|
if err := add.eventRepo.Store(e); err != nil {
|
||||||
return fmt.Errorf("could not store event: %v", err)
|
return fmt.Errorf("could not store event: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
localID, err := localIDRepo.Next()
|
localID, err := add.localIDRepo.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not create next local id: %v", err)
|
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)
|
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 {
|
if err != nil {
|
||||||
return 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 := syncRepo.Store(it); err != nil {
|
if err := add.syncRepo.Store(it); err != nil {
|
||||||
return fmt.Errorf("could not store sync item: %v", err)
|
return fmt.Errorf("could not store sync item: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,107 +13,109 @@ import (
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
oneHour, err := time.ParseDuration("1h")
|
aDateStr := "2024-11-02"
|
||||||
if err != nil {
|
aDate := time.Date(2024, 11, 2, 0, 0, 0, 0, time.UTC)
|
||||||
t.Errorf("exp nil, got %v", err)
|
aTimeStr := "12:00"
|
||||||
}
|
aDay := time.Duration(24) * time.Hour
|
||||||
oneDay, err := time.ParseDuration("24h")
|
anHourStr := "1h"
|
||||||
if err != nil {
|
anHour := time.Hour
|
||||||
t.Errorf("exp nil, got %v", err)
|
aDateAndTime := time.Date(2024, 11, 2, 12, 0, 0, 0, time.UTC)
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
args map[string]string
|
main []string
|
||||||
expEvent item.Event
|
flags map[string]string
|
||||||
expErr bool
|
expErr bool
|
||||||
|
expEvent item.Event
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no name",
|
name: "empty",
|
||||||
args: map[string]string{
|
expErr: true,
|
||||||
"on": "2024-10-01",
|
},
|
||||||
"at": "9:00",
|
{
|
||||||
"for": "1h",
|
name: "title missing",
|
||||||
|
main: []string{"add"},
|
||||||
|
flags: map[string]string{
|
||||||
|
command.FlagOn: aDateStr,
|
||||||
},
|
},
|
||||||
expErr: true,
|
expErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no date",
|
name: "date missing",
|
||||||
args: map[string]string{
|
main: []string{"add", "some", "title"},
|
||||||
"name": "event",
|
|
||||||
"at": "9:00",
|
|
||||||
"for": "1h",
|
|
||||||
},
|
|
||||||
expErr: true,
|
expErr: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "duration, but no time",
|
name: "only date",
|
||||||
args: map[string]string{
|
main: []string{"add", "title"},
|
||||||
"name": "event",
|
flags: map[string]string{
|
||||||
"on": "2024-10-01",
|
command.FlagOn: aDateStr,
|
||||||
"for": "1h",
|
},
|
||||||
|
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,
|
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) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
eventRepo := memory.NewEvent()
|
eventRepo := memory.NewEvent()
|
||||||
localRepo := memory.NewLocalID()
|
localRepo := memory.NewLocalID()
|
||||||
syncRepo := memory.NewSync()
|
syncRepo := memory.NewSync()
|
||||||
actErr := command.Add(localRepo, eventRepo, syncRepo, tc.args["name"], tc.args["on"], tc.args["at"], tc.args["for"]) != nil
|
cmd := command.NewAdd(localRepo, eventRepo, syncRepo)
|
||||||
if tc.expErr != actErr {
|
actParseErr := cmd.Execute(tc.main, tc.flags) != nil
|
||||||
t.Errorf("exp %v, got %v", tc.expErr, actErr)
|
if tc.expErr != actParseErr {
|
||||||
|
t.Errorf("exp %v, got %v", tc.expErr, actParseErr)
|
||||||
}
|
}
|
||||||
if tc.expErr {
|
if tc.expErr {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
actEvents, err := eventRepo.FindAll()
|
actEvents, err := eventRepo.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("exp nil, got %v", err)
|
t.Errorf("exp nil, got %v", err)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -2,39 +2,47 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go-mod.ewintr.nl/planner/plan/storage"
|
"go-mod.ewintr.nl/planner/plan/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DeleteCmd = &cli.Command{
|
type Delete struct {
|
||||||
Name: "delete",
|
localIDRepo storage.LocalID
|
||||||
Usage: "Delete an event",
|
eventRepo storage.Event
|
||||||
Flags: []cli.Flag{
|
syncRepo storage.Sync
|
||||||
&cli.IntFlag{
|
localID int
|
||||||
Name: "localID",
|
|
||||||
Aliases: []string{"l"},
|
|
||||||
Usage: "The local id of the event",
|
|
||||||
Required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeleteCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) *cli.Command {
|
func NewDelete(localIDRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) Command {
|
||||||
DeleteCmd.Action = func(cCtx *cli.Context) error {
|
return &Delete{
|
||||||
return Delete(localRepo, eventRepo, syncRepo, cCtx.Int("localID"))
|
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
|
var id string
|
||||||
idMap, err := localRepo.FindAll()
|
idMap, err := del.localIDRepo.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get local ids: %v", err)
|
return fmt.Errorf("could not get local ids: %v", err)
|
||||||
}
|
}
|
||||||
for eid, lid := range idMap {
|
for eid, lid := range idMap {
|
||||||
if localID == lid {
|
if del.localID == lid {
|
||||||
id = eid
|
id = eid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,11 +50,7 @@ func Delete(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage
|
||||||
return fmt.Errorf("could not find local id")
|
return fmt.Errorf("could not find local id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := eventRepo.Delete(id); err != nil {
|
e, err := del.eventRepo.Find(id)
|
||||||
return fmt.Errorf("could not delete event: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
e, err := eventRepo.Find(id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get event: %v", err)
|
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 {
|
if err != nil {
|
||||||
return 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 := 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)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,24 @@ func TestDelete(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
localID int
|
main []string
|
||||||
expErr bool
|
flags map[string]string
|
||||||
|
expErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "not found",
|
name: "invalid",
|
||||||
localID: 5,
|
main: []string{"update"},
|
||||||
expErr: true,
|
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) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
@ -44,7 +54,9 @@ func TestDelete(t *testing.T) {
|
||||||
t.Errorf("exp nil, got %v", err)
|
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 {
|
if tc.expErr != actErr {
|
||||||
t.Errorf("exp %v, got %v", tc.expErr, actErr)
|
t.Errorf("exp %v, got %v", tc.expErr, actErr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,28 +4,35 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go-mod.ewintr.nl/planner/plan/storage"
|
"go-mod.ewintr.nl/planner/plan/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ListCmd = &cli.Command{
|
type List struct {
|
||||||
Name: "list",
|
localIDRepo storage.LocalID
|
||||||
Usage: "List everything",
|
eventRepo storage.Event
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListCmd(localRepo storage.LocalID, eventRepo storage.Event) *cli.Command {
|
func NewList(localIDRepo storage.LocalID, eventRepo storage.Event) Command {
|
||||||
ListCmd.Action = func(cCtx *cli.Context) error {
|
return &List{
|
||||||
return List(localRepo, eventRepo)
|
localIDRepo: localIDRepo,
|
||||||
|
eventRepo: eventRepo,
|
||||||
}
|
}
|
||||||
return ListCmd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func List(localRepo storage.LocalID, eventRepo storage.Event) error {
|
func (list *List) Execute(main []string, flags map[string]string) error {
|
||||||
localIDs, err := localRepo.FindAll()
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get local ids: %v", err)
|
return fmt.Errorf("could not get local ids: %v", err)
|
||||||
}
|
}
|
||||||
all, err := eventRepo.FindAll()
|
all, err := list.eventRepo.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package command
|
|
@ -5,50 +5,54 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go-mod.ewintr.nl/planner/item"
|
"go-mod.ewintr.nl/planner/item"
|
||||||
"go-mod.ewintr.nl/planner/plan/storage"
|
"go-mod.ewintr.nl/planner/plan/storage"
|
||||||
"go-mod.ewintr.nl/planner/sync/client"
|
"go-mod.ewintr.nl/planner/sync/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
var SyncCmd = &cli.Command{
|
type Sync struct {
|
||||||
Name: "sync",
|
client client.Client
|
||||||
Usage: "Synchronize with server",
|
syncRepo storage.Sync
|
||||||
Flags: []cli.Flag{
|
localIDRepo storage.LocalID
|
||||||
&cli.BoolFlag{
|
eventRepo storage.Event
|
||||||
Name: "full",
|
|
||||||
Aliases: []string{"f"},
|
|
||||||
Usage: "Force full sync",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSyncCmd(client client.Client, syncRepo storage.Sync, localIDRepo storage.LocalID, eventRepo storage.Event) *cli.Command {
|
func NewSync(client client.Client, syncRepo storage.Sync, localIDRepo storage.LocalID, eventRepo storage.Event) Command {
|
||||||
SyncCmd.Action = func(cCtx *cli.Context) error {
|
return &Sync{
|
||||||
return Sync(client, syncRepo, localIDRepo, eventRepo, cCtx.Bool("full"))
|
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
|
// local new and updated
|
||||||
sendItems, err := syncRepo.FindAll()
|
sendItems, err := sync.syncRepo.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get updated items: %v", err)
|
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)
|
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)
|
return fmt.Errorf("could not clear updated items: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// get new/updated items
|
// get new/updated items
|
||||||
ts, err := syncRepo.LastUpdate()
|
ts, err := sync.syncRepo.LastUpdate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 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 := client.Updated([]item.Kind{item.KindEvent}, ts)
|
recItems, err := sync.client.Updated([]item.Kind{item.KindEvent}, ts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not receive updates: %v", err)
|
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)
|
updated := make([]item.Item, 0)
|
||||||
for _, ri := range recItems {
|
for _, ri := range recItems {
|
||||||
if ri.Deleted {
|
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)
|
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)
|
return fmt.Errorf("could not delete event: %v", err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
@ -67,7 +71,7 @@ func Sync(client client.Client, syncRepo storage.Sync, localIDRepo storage.Local
|
||||||
updated = append(updated, ri)
|
updated = append(updated, ri)
|
||||||
}
|
}
|
||||||
|
|
||||||
lidMap, err := localIDRepo.FindAll()
|
lidMap, err := sync.localIDRepo.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get local ids: %v", err)
|
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,
|
ID: u.ID,
|
||||||
EventBody: eBody,
|
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)
|
return fmt.Errorf("could not store event: %v", err)
|
||||||
}
|
}
|
||||||
lid, ok := lidMap[u.ID]
|
lid, ok := lidMap[u.ID]
|
||||||
if !ok {
|
if !ok {
|
||||||
lid, err = localIDRepo.Next()
|
lid, err = sync.localIDRepo.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get next local id: %v", err)
|
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)
|
return fmt.Errorf("could not store local id: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,43 @@ import (
|
||||||
"go-mod.ewintr.nl/planner/sync/client"
|
"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) {
|
func TestSyncSend(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -45,8 +82,8 @@ func TestSyncSend(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
cmd := command.NewSync(syncClient, syncRepo, localIDRepo, eventRepo)
|
||||||
if err := command.Sync(syncClient, syncRepo, localIDRepo, eventRepo, false); err != nil {
|
if err := cmd.Execute([]string{"sync"}, nil); err != nil {
|
||||||
t.Errorf("exp nil, got %v", err)
|
t.Errorf("exp nil, got %v", err)
|
||||||
}
|
}
|
||||||
actItems, actErr := syncClient.Updated(tc.ks, tc.ts)
|
actItems, actErr := syncClient.Updated(tc.ks, tc.ts)
|
||||||
|
@ -163,7 +200,8 @@ func TestSyncReceive(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sync
|
// 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)
|
t.Errorf("exp nil, got %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,60 +2,73 @@ package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go-mod.ewintr.nl/planner/plan/storage"
|
"go-mod.ewintr.nl/planner/plan/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var UpdateCmd = &cli.Command{
|
type Update struct {
|
||||||
Name: "update",
|
localIDRepo storage.LocalID
|
||||||
Usage: "Update an event",
|
eventRepo storage.Event
|
||||||
Flags: []cli.Flag{
|
syncRepo storage.Sync
|
||||||
&cli.IntFlag{
|
argSet *ArgSet
|
||||||
Name: "localID",
|
localID int
|
||||||
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)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUpdateCmd(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) *cli.Command {
|
func NewUpdate(localIDRepo storage.LocalID, eventRepo storage.Event, syncRepo storage.Sync) Command {
|
||||||
UpdateCmd.Action = func(cCtx *cli.Context) error {
|
return &Update{
|
||||||
return Update(localRepo, eventRepo, syncRepo, cCtx.Int("localID"), cCtx.String("name"), cCtx.String("on"), cCtx.String("at"), cCtx.String("for"))
|
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
|
var id string
|
||||||
idMap, err := localRepo.FindAll()
|
idMap, err := update.localIDRepo.FindAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get local ids: %v", err)
|
return fmt.Errorf("could not get local ids: %v", err)
|
||||||
}
|
}
|
||||||
for eid, lid := range idMap {
|
for eid, lid := range idMap {
|
||||||
if localID == lid {
|
if update.localID == lid {
|
||||||
id = eid
|
id = eid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,39 +76,39 @@ func Update(localRepo storage.LocalID, eventRepo storage.Event, syncRepo storage
|
||||||
return fmt.Errorf("could not find local id")
|
return fmt.Errorf("could not find local id")
|
||||||
}
|
}
|
||||||
|
|
||||||
e, err := eventRepo.Find(id)
|
e, err := update.eventRepo.Find(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not find event")
|
return fmt.Errorf("could not find event")
|
||||||
}
|
}
|
||||||
|
|
||||||
if nameStr != "" {
|
if as.Main != "" {
|
||||||
e.Title = nameStr
|
e.Title = as.Main
|
||||||
}
|
}
|
||||||
if onStr != "" || atStr != "" {
|
if as.IsSet(FlagOn) || as.IsSet(FlagAt) {
|
||||||
oldStart := e.Start
|
on := time.Date(e.Start.Year(), e.Start.Month(), e.Start.Day(), 0, 0, 0, 0, time.UTC)
|
||||||
dateStr := oldStart.Format("2006-01-02")
|
atH := time.Duration(e.Start.Hour()) * time.Hour
|
||||||
if onStr != "" {
|
atM := time.Duration(e.Start.Minute()) * time.Minute
|
||||||
dateStr = onStr
|
|
||||||
|
if as.IsSet(FlagOn) {
|
||||||
|
on = as.GetTime(FlagOn)
|
||||||
}
|
}
|
||||||
timeStr := oldStart.Format("15:04")
|
if as.IsSet(FlagAt) {
|
||||||
if atStr != "" {
|
at := as.GetTime(FlagAt)
|
||||||
timeStr = atStr
|
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))
|
e.Start = on.Add(atH).Add(atM)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not parse new start: %v", err)
|
|
||||||
}
|
|
||||||
e.Start = newStart
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if frStr != "" { // no check on at, can set a duration with at 00:00, making it not a whole day
|
if as.IsSet(FlagFor) {
|
||||||
fr, err := time.ParseDuration(frStr)
|
e.Duration = as.GetDuration(FlagFor)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%w: could not parse duration: %s", ErrInvalidArg, err)
|
|
||||||
}
|
|
||||||
e.Duration = fr
|
|
||||||
}
|
}
|
||||||
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)
|
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 {
|
if err != nil {
|
||||||
return 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 := syncRepo.Store(it); err != nil {
|
if err := update.syncRepo.Store(it); err != nil {
|
||||||
return fmt.Errorf("could not store sync item: %v", err)
|
return fmt.Errorf("could not store sync item: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package command_test
|
package command_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ import (
|
||||||
"go-mod.ewintr.nl/planner/plan/storage/memory"
|
"go-mod.ewintr.nl/planner/plan/storage/memory"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdateExecute(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
eid := "c"
|
eid := "c"
|
||||||
|
@ -29,21 +30,14 @@ func TestUpdate(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
localID int
|
localID int
|
||||||
args map[string]string
|
main []string
|
||||||
|
flags map[string]string
|
||||||
expEvent item.Event
|
expEvent item.Event
|
||||||
expErr bool
|
expErr bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "no args",
|
name: "no args",
|
||||||
localID: lid,
|
expErr: true,
|
||||||
expEvent: item.Event{
|
|
||||||
ID: eid,
|
|
||||||
EventBody: item.EventBody{
|
|
||||||
Title: title,
|
|
||||||
Start: start,
|
|
||||||
Duration: oneHour,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "not found",
|
name: "not found",
|
||||||
|
@ -53,9 +47,7 @@ func TestUpdate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
localID: lid,
|
localID: lid,
|
||||||
args: map[string]string{
|
main: []string{"update", fmt.Sprintf("%d", lid), "updated"},
|
||||||
"name": "updated",
|
|
||||||
},
|
|
||||||
expEvent: item.Event{
|
expEvent: item.Event{
|
||||||
ID: eid,
|
ID: eid,
|
||||||
EventBody: item.EventBody{
|
EventBody: item.EventBody{
|
||||||
|
@ -68,7 +60,8 @@ func TestUpdate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "invalid on",
|
name: "invalid on",
|
||||||
localID: lid,
|
localID: lid,
|
||||||
args: map[string]string{
|
main: []string{"update", fmt.Sprintf("%d", lid)},
|
||||||
|
flags: map[string]string{
|
||||||
"on": "invalid",
|
"on": "invalid",
|
||||||
},
|
},
|
||||||
expErr: true,
|
expErr: true,
|
||||||
|
@ -76,7 +69,8 @@ func TestUpdate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "on",
|
name: "on",
|
||||||
localID: lid,
|
localID: lid,
|
||||||
args: map[string]string{
|
main: []string{"update", fmt.Sprintf("%d", lid)},
|
||||||
|
flags: map[string]string{
|
||||||
"on": "2024-10-02",
|
"on": "2024-10-02",
|
||||||
},
|
},
|
||||||
expEvent: item.Event{
|
expEvent: item.Event{
|
||||||
|
@ -91,7 +85,8 @@ func TestUpdate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "invalid at",
|
name: "invalid at",
|
||||||
localID: lid,
|
localID: lid,
|
||||||
args: map[string]string{
|
main: []string{"update", fmt.Sprintf("%d", lid)},
|
||||||
|
flags: map[string]string{
|
||||||
"at": "invalid",
|
"at": "invalid",
|
||||||
},
|
},
|
||||||
expErr: true,
|
expErr: true,
|
||||||
|
@ -99,7 +94,8 @@ func TestUpdate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "at",
|
name: "at",
|
||||||
localID: lid,
|
localID: lid,
|
||||||
args: map[string]string{
|
main: []string{"update", fmt.Sprintf("%d", lid)},
|
||||||
|
flags: map[string]string{
|
||||||
"at": "11:00",
|
"at": "11:00",
|
||||||
},
|
},
|
||||||
expEvent: item.Event{
|
expEvent: item.Event{
|
||||||
|
@ -114,7 +110,8 @@ func TestUpdate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "on and at",
|
name: "on and at",
|
||||||
localID: lid,
|
localID: lid,
|
||||||
args: map[string]string{
|
main: []string{"update", fmt.Sprintf("%d", lid)},
|
||||||
|
flags: map[string]string{
|
||||||
"on": "2024-10-02",
|
"on": "2024-10-02",
|
||||||
"at": "11:00",
|
"at": "11:00",
|
||||||
},
|
},
|
||||||
|
@ -130,7 +127,8 @@ func TestUpdate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "invalid for",
|
name: "invalid for",
|
||||||
localID: lid,
|
localID: lid,
|
||||||
args: map[string]string{
|
main: []string{"update", fmt.Sprintf("%d", lid)},
|
||||||
|
flags: map[string]string{
|
||||||
"for": "invalid",
|
"for": "invalid",
|
||||||
},
|
},
|
||||||
expErr: true,
|
expErr: true,
|
||||||
|
@ -138,7 +136,8 @@ func TestUpdate(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "for",
|
name: "for",
|
||||||
localID: lid,
|
localID: lid,
|
||||||
args: map[string]string{
|
main: []string{"update", fmt.Sprintf("%d", lid)},
|
||||||
|
flags: map[string]string{
|
||||||
"for": "2h",
|
"for": "2h",
|
||||||
},
|
},
|
||||||
expEvent: item.Event{
|
expEvent: item.Event{
|
||||||
|
@ -169,9 +168,10 @@ func TestUpdate(t *testing.T) {
|
||||||
t.Errorf("exp nil, ,got %v", err)
|
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
|
cmd := command.NewUpdate(localIDRepo, eventRepo, syncRepo)
|
||||||
if tc.expErr != actErr {
|
actParseErr := cmd.Execute(tc.main, tc.flags) != nil
|
||||||
t.Errorf("exp %v, got %v", tc.expErr, actErr)
|
if tc.expErr != actParseErr {
|
||||||
|
t.Errorf("exp %v, got %v", tc.expErr, actParseErr)
|
||||||
}
|
}
|
||||||
if tc.expErr {
|
if tc.expErr {
|
||||||
return
|
return
|
||||||
|
|
19
plan/main.go
19
plan/main.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
"go-mod.ewintr.nl/planner/plan/command"
|
"go-mod.ewintr.nl/planner/plan/command"
|
||||||
"go-mod.ewintr.nl/planner/plan/storage/sqlite"
|
"go-mod.ewintr.nl/planner/plan/storage/sqlite"
|
||||||
"go-mod.ewintr.nl/planner/sync/client"
|
"go-mod.ewintr.nl/planner/sync/client"
|
||||||
|
@ -32,19 +31,17 @@ func main() {
|
||||||
|
|
||||||
syncClient := client.New(conf.SyncURL, conf.ApiKey)
|
syncClient := client.New(conf.SyncURL, conf.ApiKey)
|
||||||
|
|
||||||
app := &cli.App{
|
cli := command.CLI{
|
||||||
Name: "plan",
|
Commands: []command.Command{
|
||||||
Usage: "Plan your day with events",
|
command.NewAdd(localIDRepo, eventRepo, syncRepo),
|
||||||
Commands: []*cli.Command{
|
command.NewList(localIDRepo, eventRepo),
|
||||||
command.NewAddCmd(localIDRepo, eventRepo, syncRepo),
|
command.NewUpdate(localIDRepo, eventRepo, syncRepo),
|
||||||
command.NewListCmd(localIDRepo, eventRepo),
|
command.NewDelete(localIDRepo, eventRepo, syncRepo),
|
||||||
command.NewUpdateCmd(localIDRepo, eventRepo, syncRepo),
|
command.NewSync(syncClient, syncRepo, localIDRepo, eventRepo),
|
||||||
command.NewDeleteCmd(localIDRepo, eventRepo, syncRepo),
|
|
||||||
command.NewSyncCmd(syncClient, syncRepo, localIDRepo, eventRepo),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
if err := cli.Run(os.Args); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package memory
|
package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"go-mod.ewintr.nl/planner/item"
|
"go-mod.ewintr.nl/planner/item"
|
||||||
|
"go-mod.ewintr.nl/planner/plan/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
|
@ -25,7 +25,7 @@ func (r *Event) Find(id string) (item.Event, error) {
|
||||||
|
|
||||||
event, exists := r.events[id]
|
event, exists := r.events[id]
|
||||||
if !exists {
|
if !exists {
|
||||||
return item.Event{}, errors.New("event not found")
|
return item.Event{}, storage.ErrNotFound
|
||||||
}
|
}
|
||||||
return event, nil
|
return event, nil
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func (r *Event) Delete(id string) error {
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
if _, exists := r.events[id]; !exists {
|
if _, exists := r.events[id]; !exists {
|
||||||
return errors.New("event not found")
|
return storage.ErrNotFound
|
||||||
}
|
}
|
||||||
delete(r.events, id)
|
delete(r.events, id)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue