refactor args and cmds

This commit is contained in:
Erik Winter 2024-12-27 11:20:32 +01:00
parent 2a62e6c335
commit c209ba6197
18 changed files with 494 additions and 899 deletions

BIN
dist/plan vendored

Binary file not shown.

View File

@ -3,109 +3,107 @@ package command
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"go-mod.ewintr.nl/planner/item" "go-mod.ewintr.nl/planner/item"
"go-mod.ewintr.nl/planner/plan/storage"
) )
type Add struct { type AddArgs struct {
localIDRepo storage.LocalID fieldTPL map[string][]string
taskRepo storage.Task task item.Task
syncRepo storage.Sync
argSet *ArgSet
} }
func NewAdd(localRepo storage.LocalID, taskRepo storage.Task, syncRepo storage.Sync) Command { func NewAddArgs() AddArgs {
return &Add{ return AddArgs{
localIDRepo: localRepo, fieldTPL: map[string][]string{
taskRepo: taskRepo, "date": []string{"d", "date", "on"},
syncRepo: syncRepo, "time": []string{"t", "time", "at"},
argSet: &ArgSet{ "duration": []string{"dur", "duration", "for"},
Flags: map[string]Flag{ "recurrer": []string{"rec", "recurrer"},
FlagOn: &FlagDate{},
FlagAt: &FlagTime{},
FlagFor: &FlagDuration{},
FlagRec: &FlagRecurrer{},
},
}, },
} }
} }
func (add *Add) Execute(main []string, flags map[string]string) error { func (aa AddArgs) Parse(main []string, fields map[string]string) (Command, error) {
if len(main) == 0 || main[0] != "add" { if len(main) == 0 || main[0] != "add" {
return ErrWrongCommand return nil, ErrWrongCommand
} }
as := add.argSet main = main[1:]
if len(main) > 1 { if len(main) == 0 {
as.Main = strings.Join(main[1:], " ") return nil, fmt.Errorf("%w: title is required for add", ErrInvalidArg)
}
for k := range as.Flags {
v, ok := flags[k]
if !ok {
continue
}
if err := as.Set(k, v); err != nil {
return fmt.Errorf("could not set %s: %v", k, err)
}
}
if as.Main == "" {
return fmt.Errorf("%w: title is required", ErrInvalidArg)
}
if !as.IsSet(FlagOn) {
return fmt.Errorf("%w: date is required", ErrInvalidArg)
}
if !as.IsSet(FlagAt) && as.IsSet(FlagFor) {
return fmt.Errorf("%w: can not have duration without start time", ErrInvalidArg)
}
if as.IsSet(FlagAt) && !as.IsSet(FlagFor) {
if err := as.Flags[FlagFor].Set("1h"); err != nil {
return fmt.Errorf("could not set duration to one hour")
}
}
if !as.IsSet(FlagAt) && !as.IsSet(FlagFor) {
if err := as.Flags[FlagFor].Set("24h"); err != nil {
return fmt.Errorf("could not set duration to 24 hours")
} }
fields, err := ResolveFields(fields, aa.fieldTPL)
if err != nil {
return nil, err
} }
return add.do()
}
func (add *Add) do() error {
as := add.argSet
rec := as.GetRecurrer(FlagRec)
tsk := item.Task{ tsk := item.Task{
ID: uuid.New().String(), ID: uuid.New().String(),
Date: as.GetDate(FlagOn),
Recurrer: rec,
TaskBody: item.TaskBody{ TaskBody: item.TaskBody{
Title: as.Main, Title: strings.Join(main, ","),
Time: as.GetTime(FlagAt),
Duration: as.GetDuration(FlagFor),
}, },
} }
if rec != nil {
tsk.RecurNext = rec.First() if val, ok := fields["date"]; ok {
d := item.NewDateFromString(val)
if d.IsZero() {
return nil, fmt.Errorf("%w: could not parse date", ErrInvalidArg)
}
tsk.Date = d
}
if val, ok := fields["time"]; ok {
t := item.NewTimeFromString(val)
if t.IsZero() {
return nil, fmt.Errorf("%w: could not parse time", ErrInvalidArg)
}
tsk.Time = t
}
if val, ok := fields["duration"]; ok {
d, err := time.ParseDuration(val)
if err != nil {
return nil, fmt.Errorf("%w: could not parse duration", ErrInvalidArg)
}
tsk.Duration = d
}
if val, ok := fields["recurrer"]; ok {
rec := item.NewRecurrer(val)
if rec == nil {
return nil, fmt.Errorf("%w: could not parse recurrer", ErrInvalidArg)
}
tsk.Recurrer = rec
tsk.RecurNext = tsk.Recurrer.First()
} }
if err := add.taskRepo.Store(tsk); err != nil { return &Add{
args: AddArgs{
task: tsk,
},
}, nil
}
type Add struct {
args AddArgs
}
func (a *Add) Do(deps Dependencies) error {
if err := deps.TaskRepo.Store(a.args.task); err != nil {
return fmt.Errorf("could not store event: %v", err) return fmt.Errorf("could not store event: %v", err)
} }
localID, err := add.localIDRepo.Next() localID, err := deps.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 := add.localIDRepo.Store(tsk.ID, localID); err != nil { if err := deps.LocalIDRepo.Store(a.args.task.ID, localID); err != nil {
return fmt.Errorf("could not store local id: %v", err) return fmt.Errorf("could not store local id: %v", err)
} }
it, err := tsk.Item() it, err := a.args.task.Item()
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 := add.syncRepo.Store(it); err != nil { if err := deps.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)
} }

View File

@ -14,14 +14,13 @@ func TestAdd(t *testing.T) {
aDate := item.NewDate(2024, 11, 2) aDate := item.NewDate(2024, 11, 2)
aTime := item.NewTime(12, 0) aTime := item.NewTime(12, 0)
aDay := time.Duration(24) * time.Hour
anHourStr := "1h" anHourStr := "1h"
anHour := time.Hour anHour := time.Hour
for _, tc := range []struct { for _, tc := range []struct {
name string name string
main []string main []string
flags map[string]string fields map[string]string
expErr bool expErr bool
expTask item.Task expTask item.Task
}{ }{
@ -32,38 +31,18 @@ func TestAdd(t *testing.T) {
{ {
name: "title missing", name: "title missing",
main: []string{"add"}, main: []string{"add"},
flags: map[string]string{ fields: map[string]string{
command.FlagOn: aDate.String(), "date": aDate.String(),
}, },
expErr: true, expErr: true,
}, },
{ {
name: "date missing", name: "date time duration",
main: []string{"add", "some", "title"},
expErr: true,
},
{
name: "only date",
main: []string{"add", "title"}, main: []string{"add", "title"},
flags: map[string]string{ fields: map[string]string{
command.FlagOn: aDate.String(), "date": aDate.String(),
}, "time": aTime.String(),
expTask: item.Task{ "duration": anHourStr,
ID: "title",
Date: aDate,
TaskBody: item.TaskBody{
Title: "title",
Duration: aDay,
},
},
},
{
name: "date, time and duration",
main: []string{"add", "title"},
flags: map[string]string{
command.FlagOn: aDate.String(),
command.FlagAt: aTime.String(),
command.FlagFor: anHourStr,
}, },
expTask: item.Task{ expTask: item.Task{
ID: "title", ID: "title",
@ -75,28 +54,25 @@ func TestAdd(t *testing.T) {
}, },
}, },
}, },
{
name: "date and duration",
main: []string{"add", "title"},
flags: map[string]string{
command.FlagOn: aDate.String(),
command.FlagFor: anHourStr,
},
expErr: true,
},
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
taskRepo := memory.NewTask() taskRepo := memory.NewTask()
localRepo := memory.NewLocalID() localIDRepo := memory.NewLocalID()
syncRepo := memory.NewSync() syncRepo := memory.NewSync()
cmd := command.NewAdd(localRepo, taskRepo, syncRepo) cmd, actParseErr := command.NewAddArgs().Parse(tc.main, tc.fields)
actParseErr := cmd.Execute(tc.main, tc.flags) != nil if tc.expErr != (actParseErr != nil) {
if tc.expErr != actParseErr {
t.Errorf("exp %v, got %v", tc.expErr, actParseErr) t.Errorf("exp %v, got %v", tc.expErr, actParseErr)
} }
if tc.expErr { if tc.expErr {
return return
} }
if err := cmd.Do(command.Dependencies{
TaskRepo: taskRepo,
LocalIDRepo: localIDRepo,
SyncRepo: syncRepo,
}); err != nil {
t.Errorf("exp nil, got %v", err)
}
actTasks, err := taskRepo.FindAll() actTasks, err := taskRepo.FindAll()
if err != nil { if err != nil {
@ -106,7 +82,7 @@ func TestAdd(t *testing.T) {
t.Errorf("exp 1, got %d", len(actTasks)) t.Errorf("exp 1, got %d", len(actTasks))
} }
actLocalIDs, err := localRepo.FindAll() actLocalIDs, err := localIDRepo.FindAll()
if err != nil { if err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }

View File

@ -1,101 +0,0 @@
package command
import (
"fmt"
"time"
"go-mod.ewintr.nl/planner/item"
)
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) GetDate(name string) item.Date {
flag, ok := as.Flags[name]
if !ok {
return item.Date{}
}
val, ok := flag.Get().(item.Date)
if !ok {
return item.Date{}
}
return val
}
func (as *ArgSet) GetTime(name string) item.Time {
flag, ok := as.Flags[name]
if !ok {
return item.Time{}
}
val, ok := flag.Get().(item.Time)
if !ok {
return item.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
}
func (as *ArgSet) GetRecurrer(name string) item.Recurrer {
flag, ok := as.Flags[name]
if !ok {
return nil
}
val, ok := flag.Get().(item.Recurrer)
if !ok {
return nil
}
return val
}
func (as *ArgSet) GetInt(name string) int {
flag, ok := as.Flags[name]
if !ok {
return 0
}
val, ok := flag.Get().(int)
if !ok {
return 0
}
return val
}

View File

@ -1,103 +0,0 @@
package command_test
import (
"testing"
"time"
"go-mod.ewintr.nl/planner/item"
"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: "recur period flag success",
flags: map[string]command.Flag{
"recur": &command.FlagRecurrer{Name: "recur"},
},
flagName: "recur",
setValue: "2024-12-23, daily",
exp: item.NewRecurrer("2024-12-23, daily"),
},
{
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
}
if !as.IsSet(tt.flagName) {
t.Errorf("ArgSet.IsSet() = false, want true for flag %s", tt.flagName)
}
})
}
}

View File

@ -3,66 +3,104 @@ package command
import ( import (
"errors" "errors"
"fmt" "fmt"
"slices"
"strings" "strings"
"go-mod.ewintr.nl/planner/plan/storage"
"go-mod.ewintr.nl/planner/sync/client"
) )
const ( const (
FlagTitle = "title" DateFormat = "2006-01-02"
FlagOn = "on" TimeFormat = "15:04"
FlagAt = "at"
FlagFor = "for"
FlagRec = "rec"
) )
var (
ErrWrongCommand = errors.New("wrong command")
ErrInvalidArg = errors.New("invalid argument")
)
type Dependencies struct {
LocalIDRepo storage.LocalID
TaskRepo storage.Task
SyncRepo storage.Sync
SyncClient client.Client
}
type CommandArgs interface {
Parse(main []string, fields map[string]string) (Command, error)
}
type Command interface { type Command interface {
Execute([]string, map[string]string) error Do(deps Dependencies) error
} }
type CLI struct { type CLI struct {
Commands []Command deps Dependencies
cmdArgs []CommandArgs
}
func NewCLI(deps Dependencies) *CLI {
return &CLI{
deps: deps,
cmdArgs: []CommandArgs{
NewAddArgs(), NewDeleteArgs(), NewListArgs(),
NewSyncArgs(), NewUpdateArgs(),
},
}
} }
func (cli *CLI) Run(args []string) error { func (cli *CLI) Run(args []string) error {
main, flags, err := ParseFlags(args) main, flags := FindFields(args)
if err != nil { for _, ca := range cli.cmdArgs {
return err cmd, err := ca.Parse(main, flags)
}
for _, c := range cli.Commands {
err := c.Execute(main, flags)
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 nil return cmd.Do(cli.deps)
} }
} }
return fmt.Errorf("could not find matching command") return fmt.Errorf("could not find matching command")
} }
func ParseFlags(args []string) ([]string, map[string]string, error) { func FindFields(args []string) ([]string, map[string]string) {
flags := make(map[string]string) fields := make(map[string]string)
main := make([]string, 0) main := make([]string, 0)
var inMain bool
for i := 0; i < len(args); i++ { for i := 0; i < len(args); i++ {
if strings.HasPrefix(args[i], "-") { if k, v, ok := strings.Cut(args[i], ":"); ok && !strings.Contains(k, " ") {
inMain = false fields[k] = v
if i+1 >= len(args) {
return nil, nil, fmt.Errorf("flag wihout value")
}
flags[strings.TrimPrefix(args[i], "-")] = args[i+1]
i++
continue continue
} }
if !inMain && len(main) > 0 {
return nil, nil, fmt.Errorf("two mains")
}
inMain = true
main = append(main, args[i]) main = append(main, args[i])
} }
return main, flags, nil return main, fields
}
func ResolveFields(fields map[string]string, tmpl map[string][]string) (map[string]string, error) {
res := make(map[string]string)
for k, v := range fields {
for tk, tv := range tmpl {
if slices.Contains(tv, k) {
if _, ok := res[tk]; ok {
return nil, fmt.Errorf("%w: duplicate field: %v", ErrInvalidArg, tk)
}
res[tk] = v
delete(fields, k)
}
}
}
if len(fields) > 0 {
ks := make([]string, 0, len(fields))
for k := range fields {
ks = append(ks, k)
}
return nil, fmt.Errorf("%w: unknown field(s): %v", ErrInvalidArg, strings.Join(ks, ","))
}
return res, nil
} }

View File

@ -7,32 +7,31 @@ import (
"go-mod.ewintr.nl/planner/plan/command" "go-mod.ewintr.nl/planner/plan/command"
) )
func TestParseArgs(t *testing.T) { func TestFindFields(t *testing.T) {
t.Parallel() t.Parallel()
for _, tc := range []struct { for _, tc := range []struct {
name string name string
args []string args []string
expMain []string expMain []string
expFlags map[string]string expFields map[string]string
expErr bool
}{ }{
{ {
name: "empty", name: "empty",
expMain: []string{}, expMain: []string{},
expFlags: map[string]string{}, expFields: map[string]string{},
}, },
{ {
name: "just main", name: "just main",
args: []string{"one", "two three", "four"}, args: []string{"one", "two three", "four"},
expMain: []string{"one", "two three", "four"}, expMain: []string{"one", "two three", "four"},
expFlags: map[string]string{}, expFields: map[string]string{},
}, },
{ {
name: "with flags", name: "with flags",
args: []string{"-flag1", "value1", "one", "two", "-flag2", "value2", "-flag3", "value3"}, args: []string{"flag1:value1", "one", "two", "flag2:value2", "flag3:value3"},
expMain: []string{"one", "two"}, expMain: []string{"one", "two"},
expFlags: map[string]string{ expFields: map[string]string{
"flag1": "value1", "flag1": "value1",
"flag2": "value2", "flag2": "value2",
"flag3": "value3", "flag3": "value3",
@ -40,29 +39,88 @@ func TestParseArgs(t *testing.T) {
}, },
{ {
name: "flag without value", name: "flag without value",
args: []string{"one", "two", "-flag1"}, args: []string{"one", "two", "flag1:"},
expErr: true, expMain: []string{"one", "two"},
expFields: map[string]string{
"flag1": "",
},
}, },
{ {
name: "split main", name: "split main",
args: []string{"one", "-flag1", "value1", "two"}, args: []string{"one", "flag1:value1", "two"},
expErr: true, expMain: []string{"one", "two"},
expFields: map[string]string{
"flag1": "value1",
},
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
actMain, actFlags, actErr := command.ParseFlags(tc.args) actMain, actFields := command.FindFields(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.expMain, actMain); diff != "" { if diff := cmp.Diff(tc.expMain, actMain); diff != "" {
t.Errorf("(exp +, got -)\n%s", diff) t.Errorf("(exp +, got -)\n%s", diff)
} }
if diff := cmp.Diff(tc.expFlags, actFlags); diff != "" { if diff := cmp.Diff(tc.expFields, actFields); diff != "" {
t.Errorf("(exp +, got -)\n%s", diff) t.Errorf("(exp +, got -)\n%s", diff)
} }
}) })
} }
} }
func TestResolveFields(t *testing.T) {
t.Parallel()
tmpl := map[string][]string{
"one": []string{"a", "b"},
"two": []string{"c", "d"},
}
for _, tc := range []struct {
name string
fields map[string]string
expRes map[string]string
expErr bool
}{
{
name: "empty",
expRes: map[string]string{},
},
{
name: "unknown",
fields: map[string]string{
"unknown": "value",
},
expErr: true,
},
{
name: "duplicate",
fields: map[string]string{
"a": "val1",
"b": "val2",
},
expErr: true,
},
{
name: "valid",
fields: map[string]string{
"a": "val1",
"d": "val2",
},
expRes: map[string]string{
"one": "val1",
"two": "val2",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
actRes, actErr := command.ResolveFields(tc.fields, tmpl)
if tc.expErr != (actErr != nil) {
t.Errorf("exp %v, got %v", tc.expErr, actErr != nil)
}
if tc.expErr {
return
}
if diff := cmp.Diff(tc.expRes, actRes); diff != "" {
t.Errorf("(+exp, -got)%s\n", diff)
}
})
}
}

View File

@ -3,46 +3,45 @@ package command
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"go-mod.ewintr.nl/planner/plan/storage"
) )
type Delete struct { type DeleteArgs struct {
localIDRepo storage.LocalID LocalID int
taskRepo storage.Task
syncRepo storage.Sync
localID int
} }
func NewDelete(localIDRepo storage.LocalID, taskRepo storage.Task, syncRepo storage.Sync) Command { func NewDeleteArgs() DeleteArgs {
return &Delete{ return DeleteArgs{}
localIDRepo: localIDRepo,
taskRepo: taskRepo,
syncRepo: syncRepo,
}
} }
func (del *Delete) Execute(main []string, flags map[string]string) error { func (da DeleteArgs) Parse(main []string, flags map[string]string) (Command, error) {
if len(main) < 2 || main[0] != "delete" { if len(main) < 2 || main[0] != "delete" {
return ErrWrongCommand return nil, ErrWrongCommand
} }
localID, err := strconv.Atoi(main[1]) localID, err := strconv.Atoi(main[1])
if err != nil { if err != nil {
return fmt.Errorf("not a local id: %v", main[1]) return nil, fmt.Errorf("not a local id: %v", main[1])
} }
del.localID = localID
return del.do() return &Delete{
args: DeleteArgs{
LocalID: localID,
},
}, nil
} }
func (del *Delete) do() error { type Delete struct {
args DeleteArgs
}
func (del *Delete) Do(deps Dependencies) error {
var id string var id string
idMap, err := del.localIDRepo.FindAll() idMap, err := deps.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 tskID, lid := range idMap { for tskID, lid := range idMap {
if del.localID == lid { if del.args.LocalID == lid {
id = tskID id = tskID
} }
} }
@ -50,7 +49,7 @@ func (del *Delete) do() error {
return fmt.Errorf("could not find local id") return fmt.Errorf("could not find local id")
} }
tsk, err := del.taskRepo.Find(id) tsk, err := deps.TaskRepo.Find(id)
if err != nil { if err != nil {
return fmt.Errorf("could not get task: %v", err) return fmt.Errorf("could not get task: %v", err)
} }
@ -60,15 +59,15 @@ func (del *Delete) do() error {
return fmt.Errorf("could not convert task to sync item: %v", err) return fmt.Errorf("could not convert task to sync item: %v", err)
} }
it.Deleted = true it.Deleted = true
if err := del.syncRepo.Store(it); err != nil { if err := deps.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 { if err := deps.LocalIDRepo.Delete(id); err != nil {
return fmt.Errorf("could not delete local id: %v", err) return fmt.Errorf("could not delete local id: %v", err)
} }
if err := del.taskRepo.Delete(id); err != nil { if err := deps.TaskRepo.Delete(id); err != nil {
return fmt.Errorf("could not delete task: %v", err) return fmt.Errorf("could not delete task: %v", err)
} }

View File

@ -25,17 +25,18 @@ func TestDelete(t *testing.T) {
name string name string
main []string main []string
flags map[string]string flags map[string]string
expErr bool expParseErr bool
expDoErr bool
}{ }{
{ {
name: "invalid", name: "invalid",
main: []string{"update"}, main: []string{"update"},
expErr: true, expParseErr: true,
}, },
{ {
name: "not found", name: "not found",
main: []string{"delete", "5"}, main: []string{"delete", "5"},
expErr: true, expDoErr: true,
}, },
{ {
name: "valid", name: "valid",
@ -48,26 +49,35 @@ func TestDelete(t *testing.T) {
if err := taskRepo.Store(e); err != nil { if err := taskRepo.Store(e); err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
localRepo := memory.NewLocalID() localIDRepo := memory.NewLocalID()
if err := localRepo.Store(e.ID, 1); err != nil { if err := localIDRepo.Store(e.ID, 1); err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }
cmd := command.NewDelete(localRepo, taskRepo, syncRepo) cmd, actParseErr := command.NewDeleteArgs().Parse(tc.main, tc.flags)
if tc.expParseErr != (actParseErr != nil) {
actErr := cmd.Execute(tc.main, tc.flags) != nil t.Errorf("exp %v, got %v", tc.expParseErr, actParseErr)
if tc.expErr != actErr {
t.Errorf("exp %v, got %v", tc.expErr, actErr)
} }
if tc.expErr { if tc.expParseErr {
return
}
actDoErr := cmd.Do(command.Dependencies{
TaskRepo: taskRepo,
LocalIDRepo: localIDRepo,
SyncRepo: syncRepo,
}) != nil
if tc.expDoErr != actDoErr {
t.Errorf("exp false, got %v", actDoErr)
}
if tc.expDoErr {
return return
} }
_, repoErr := taskRepo.Find(e.ID) _, repoErr := taskRepo.Find(e.ID)
if !errors.Is(repoErr, storage.ErrNotFound) { if !errors.Is(repoErr, storage.ErrNotFound) {
t.Errorf("exp %v, got %v", storage.ErrNotFound, actErr) t.Errorf("exp %v, got %v", storage.ErrNotFound, repoErr)
} }
idMap, idErr := localRepo.FindAll() idMap, idErr := localIDRepo.FindAll()
if idErr != nil { if idErr != nil {
t.Errorf("exp nil, got %v", idErr) t.Errorf("exp nil, got %v", idErr)
} }

View File

@ -1,156 +0,0 @@
package command
import (
"errors"
"fmt"
"strconv"
"time"
"go-mod.ewintr.nl/planner/item"
)
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 item.Date
}
func (fd *FlagDate) Set(val string) error {
d := item.NewDateFromString(val)
if d.IsZero() {
return fmt.Errorf("could not parse date: %v", d)
}
fd.Value = d
return nil
}
func (fd *FlagDate) IsSet() bool {
return !fd.Value.IsZero()
}
func (fd *FlagDate) Get() any {
return fd.Value
}
type FlagTime struct {
Name string
Value item.Time
}
func (ft *FlagTime) Set(val string) error {
d := item.NewTimeFromString(val)
if d.IsZero() {
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
}
type FlagRecurrer struct {
Name string
Value item.Recurrer
}
func (fr *FlagRecurrer) Set(val string) error {
fr.Value = item.NewRecurrer(val)
if fr.Value == nil {
return fmt.Errorf("not a valid recurrer: %v", val)
}
return nil
}
func (fr *FlagRecurrer) IsSet() bool {
return fr.Value != nil
}
func (fr *FlagRecurrer) Get() any {
return fr.Value
}
type FlagInt struct {
Name string
Value int
}
func (fi *FlagInt) Set(val string) error {
i, err := strconv.Atoi(val)
if err != nil {
return fmt.Errorf("not a valid integer: %v", val)
}
fi.Value = i
return nil
}
func (fi *FlagInt) IsSet() bool {
return fi.Value != 0
}
func (fi *FlagInt) Get() any {
return fi.Value
}

View File

@ -1,141 +0,0 @@
package command_test
import (
"testing"
"time"
"go-mod.ewintr.nl/planner/item"
"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 := item.NewDate(2024, 11, 20)
f := command.FlagDate{}
if f.IsSet() {
t.Errorf("exp false, got true")
}
if err := f.Set(valid.String()); err != nil {
t.Errorf("exp nil, got %v", err)
}
if !f.IsSet() {
t.Errorf("exp true, got false")
}
act, ok := f.Get().(item.Date)
if !ok {
t.Errorf("exp true, got false")
}
if act.String() != valid.String() {
t.Errorf("exp %v, got %v", valid.String(), act.String())
}
}
func TestFlagTime(t *testing.T) {
t.Parallel()
valid := item.NewTime(12, 30)
f := command.FlagTime{}
if f.IsSet() {
t.Errorf("exp false, got true")
}
if err := f.Set(valid.String()); err != nil {
t.Errorf("exp nil, got %v", err)
}
if !f.IsSet() {
t.Errorf("exp true, got false")
}
act, ok := f.Get().(item.Time)
if !ok {
t.Errorf("exp true, got false")
}
if act.String() != valid.String() {
t.Errorf("exp %v, got %v", valid.String(), act.String())
}
}
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)
}
}
func TestFlagRecurrer(t *testing.T) {
t.Parallel()
validStr := "2024-12-23, daily"
valid := item.NewRecurrer(validStr)
f := command.FlagRecurrer{}
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().(item.Recurrer)
if !ok {
t.Errorf("exp true, got false")
}
if act != valid {
t.Errorf("exp %v, got %v", valid, act)
}
}

View File

@ -2,36 +2,32 @@ package command
import ( import (
"fmt" "fmt"
"go-mod.ewintr.nl/planner/plan/storage"
) )
type List struct { type ListArgs struct {
localIDRepo storage.LocalID
taskRepo storage.Task
} }
func NewList(localIDRepo storage.LocalID, taskRepo storage.Task) Command { func NewListArgs() ListArgs {
return &List{ return ListArgs{}
localIDRepo: localIDRepo,
taskRepo: taskRepo,
}
} }
func (list *List) Execute(main []string, flags map[string]string) error { func (la ListArgs) Parse(main []string, flags map[string]string) (Command, error) {
if len(main) > 0 && main[0] != "list" { if len(main) > 0 && main[0] != "list" {
return ErrWrongCommand return nil, ErrWrongCommand
} }
return list.do() return &List{}, nil
} }
func (list *List) do() error { type List struct {
localIDs, err := list.localIDRepo.FindAll() }
func (list *List) Do(deps Dependencies) error {
localIDs, err := deps.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 := list.taskRepo.FindAll() all, err := deps.TaskRepo.FindAll()
if err != nil { if err != nil {
return err return err
} }

View File

@ -47,11 +47,19 @@ func TestList(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
cmd := command.NewList(localRepo, taskRepo) cmd, actErr := command.NewListArgs().Parse(tc.main, nil)
actErr := cmd.Execute(tc.main, nil) != nil if tc.expErr != (actErr != nil) {
if tc.expErr != actErr {
t.Errorf("exp %v, got %v", tc.expErr, actErr) t.Errorf("exp %v, got %v", tc.expErr, actErr)
} }
if tc.expErr {
return
}
if err := cmd.Do(command.Dependencies{
TaskRepo: taskRepo,
LocalIDRepo: localRepo,
}); err != nil {
t.Errorf("exp nil, got %v", err)
}
}) })
} }
} }

View File

@ -7,52 +7,43 @@ import (
"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"
) )
type Sync struct { type SyncArgs struct{}
client client.Client
syncRepo storage.Sync func NewSyncArgs() SyncArgs {
localIDRepo storage.LocalID return SyncArgs{}
taskRepo storage.Task
} }
func NewSync(client client.Client, syncRepo storage.Sync, localIDRepo storage.LocalID, taskRepo storage.Task) Command { func (sa SyncArgs) Parse(main []string, flags map[string]string) (Command, error) {
return &Sync{
client: client,
syncRepo: syncRepo,
localIDRepo: localIDRepo,
taskRepo: taskRepo,
}
}
func (sync *Sync) Execute(main []string, flags map[string]string) error {
if len(main) == 0 || main[0] != "sync" { if len(main) == 0 || main[0] != "sync" {
return ErrWrongCommand return nil, ErrWrongCommand
} }
return sync.do() return &Sync{}, nil
} }
func (sync *Sync) do() error { type Sync struct{}
func (s *Sync) Do(deps Dependencies) error {
// local new and updated // local new and updated
sendItems, err := sync.syncRepo.FindAll() sendItems, err := deps.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 := sync.client.Update(sendItems); err != nil { if err := deps.SyncClient.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 := sync.syncRepo.DeleteAll(); err != nil { if err := deps.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 := sync.syncRepo.LastUpdate() ts, err := deps.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 := sync.client.Updated([]item.Kind{item.KindTask}, ts) recItems, err := deps.SyncClient.Updated([]item.Kind{item.KindTask}, 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)
} }
@ -60,10 +51,10 @@ func (sync *Sync) do() error {
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 := sync.localIDRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) { if err := deps.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 := sync.taskRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) { if err := deps.TaskRepo.Delete(ri.ID); err != nil && !errors.Is(err, storage.ErrNotFound) {
return fmt.Errorf("could not delete task: %v", err) return fmt.Errorf("could not delete task: %v", err)
} }
continue continue
@ -71,7 +62,7 @@ func (sync *Sync) do() error {
updated = append(updated, ri) updated = append(updated, ri)
} }
lidMap, err := sync.localIDRepo.FindAll() lidMap, err := deps.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)
} }
@ -87,17 +78,17 @@ func (sync *Sync) do() error {
RecurNext: u.RecurNext, RecurNext: u.RecurNext,
TaskBody: tskBody, TaskBody: tskBody,
} }
if err := sync.taskRepo.Store(tsk); err != nil { if err := deps.TaskRepo.Store(tsk); err != nil {
return fmt.Errorf("could not store task: %v", err) return fmt.Errorf("could not store task: %v", err)
} }
lid, ok := lidMap[u.ID] lid, ok := lidMap[u.ID]
if !ok { if !ok {
lid, err = sync.localIDRepo.Next() lid, err = deps.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 := sync.localIDRepo.Store(u.ID, lid); err != nil { if err := deps.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)
} }
} }

View File

@ -14,11 +14,6 @@ import (
func TestSyncParse(t *testing.T) { func TestSyncParse(t *testing.T) {
t.Parallel() t.Parallel()
syncClient := client.NewMemory()
syncRepo := memory.NewSync()
localIDRepo := memory.NewLocalID()
taskRepo := memory.NewTask()
for _, tc := range []struct { for _, tc := range []struct {
name string name string
main []string main []string
@ -39,9 +34,8 @@ func TestSyncParse(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, taskRepo) _, actErr := command.SyncArgs{}.Parse(tc.main, nil)
actErr := cmd.Execute(tc.main, nil) != nil if tc.expErr != (actErr != nil) {
if tc.expErr != actErr {
t.Errorf("exp %v, got %v", tc.expErr, actErr) t.Errorf("exp %v, got %v", tc.expErr, actErr)
} }
}) })
@ -82,8 +76,16 @@ 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, taskRepo) cmd, err := command.SyncArgs{}.Parse([]string{"sync"}, nil)
if err := cmd.Execute([]string{"sync"}, nil); err != nil { if err != nil {
t.Errorf("exp nil, got %v", err)
}
if err := cmd.Do(command.Dependencies{
TaskRepo: taskRepo,
LocalIDRepo: localIDRepo,
SyncRepo: syncRepo,
SyncClient: syncClient,
}); 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)
@ -181,12 +183,12 @@ func TestSyncReceive(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
// setup
syncClient := client.NewMemory() syncClient := client.NewMemory()
syncRepo := memory.NewSync() syncRepo := memory.NewSync()
localIDRepo := memory.NewLocalID() localIDRepo := memory.NewLocalID()
taskRepo := memory.NewTask() taskRepo := memory.NewTask()
// setup
for i, p := range tc.present { for i, p := range tc.present {
if err := taskRepo.Store(p); err != nil { if err := taskRepo.Store(p); err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
@ -200,8 +202,16 @@ func TestSyncReceive(t *testing.T) {
} }
// sync // sync
cmd := command.NewSync(syncClient, syncRepo, localIDRepo, taskRepo) cmd, err := command.NewSyncArgs().Parse([]string{"sync"}, nil)
if err := cmd.Execute([]string{"sync"}, nil); err != nil { if err != nil {
t.Errorf("exp nil, got %v", err)
}
if err := cmd.Do(command.Dependencies{
TaskRepo: taskRepo,
LocalIDRepo: localIDRepo,
SyncRepo: syncRepo,
SyncClient: syncClient,
}); err != nil {
t.Errorf("exp nil, got %v", err) t.Errorf("exp nil, got %v", err)
} }

View File

@ -4,71 +4,93 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"go-mod.ewintr.nl/planner/plan/storage" "go-mod.ewintr.nl/planner/item"
) )
type Update struct { type UpdateArgs struct {
localIDRepo storage.LocalID fieldTPL map[string][]string
taskRepo storage.Task LocalID int
syncRepo storage.Sync Title string
argSet *ArgSet Date item.Date
localID int Time item.Time
Duration time.Duration
Recurrer item.Recurrer
} }
func NewUpdate(localIDRepo storage.LocalID, taskRepo storage.Task, syncRepo storage.Sync) Command { func NewUpdateArgs() UpdateArgs {
return &Update{ return UpdateArgs{
localIDRepo: localIDRepo, fieldTPL: map[string][]string{
taskRepo: taskRepo, "date": []string{"d", "date", "on"},
syncRepo: syncRepo, "time": []string{"t", "time", "at"},
argSet: &ArgSet{ "duration": []string{"dur", "duration", "for"},
Flags: map[string]Flag{ "recurrer": []string{"rec", "recurrer"},
FlagTitle: &FlagString{},
FlagOn: &FlagDate{},
FlagAt: &FlagTime{},
FlagFor: &FlagDuration{},
FlagRec: &FlagRecurrer{},
},
}, },
} }
} }
func (update *Update) Execute(main []string, flags map[string]string) error { func (ua UpdateArgs) Parse(main []string, fields map[string]string) (Command, error) {
if len(main) < 2 || main[0] != "update" { if len(main) < 2 || main[0] != "update" {
return ErrWrongCommand return nil, ErrWrongCommand
} }
localID, err := strconv.Atoi(main[1]) localID, err := strconv.Atoi(main[1])
if err != nil { if err != nil {
return fmt.Errorf("not a local id: %v", main[1]) return nil, fmt.Errorf("not a local id: %v", main[1])
}
fields, err = ResolveFields(fields, ua.fieldTPL)
if err != nil {
return nil, err
}
args := UpdateArgs{
LocalID: localID,
Title: strings.Join(main[2:], " "),
} }
update.localID = localID
main = main[2:]
as := update.argSet if val, ok := fields["date"]; ok {
as.Main = strings.Join(main, " ") d := item.NewDateFromString(val)
for k := range as.Flags { if d.IsZero() {
v, ok := flags[k] return nil, fmt.Errorf("%w: could not parse date", ErrInvalidArg)
if !ok {
continue
} }
if err := as.Set(k, v); err != nil { args.Date = d
return fmt.Errorf("could not set %s: %v", k, err)
} }
if val, ok := fields["time"]; ok {
t := item.NewTimeFromString(val)
if t.IsZero() {
return nil, fmt.Errorf("%w: could not parse time", ErrInvalidArg)
}
args.Time = t
}
if val, ok := fields["duration"]; ok {
d, err := time.ParseDuration(val)
if err != nil {
return nil, fmt.Errorf("%w: could not parse duration", ErrInvalidArg)
}
args.Duration = d
}
if val, ok := fields["recurrer"]; ok {
rec := item.NewRecurrer(val)
if rec == nil {
return nil, fmt.Errorf("%w: could not parse recurrer", ErrInvalidArg)
}
args.Recurrer = rec
} }
update.argSet = as
return update.do() return &Update{args}, nil
} }
func (update *Update) do() error { type Update struct {
as := update.argSet args UpdateArgs
}
func (u *Update) Do(deps Dependencies) error {
var id string var id string
idMap, err := update.localIDRepo.FindAll() idMap, err := deps.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 tid, lid := range idMap { for tid, lid := range idMap {
if update.localID == lid { if u.args.LocalID == lid {
id = tid id = tid
} }
} }
@ -76,32 +98,33 @@ func (update *Update) do() error {
return fmt.Errorf("could not find local id") return fmt.Errorf("could not find local id")
} }
tsk, err := update.taskRepo.Find(id) tsk, err := deps.TaskRepo.Find(id)
if err != nil { if err != nil {
return fmt.Errorf("could not find task") return fmt.Errorf("could not find task")
} }
if as.Main != "" { if u.args.Title != "" {
tsk.Title = as.Main tsk.Title = u.args.Title
} }
if as.IsSet(FlagOn) { if !u.args.Date.IsZero() {
tsk.Date = as.GetDate(FlagOn) tsk.Date = u.args.Date
} }
if as.IsSet(FlagAt) { if !u.args.Time.IsZero() {
tsk.Time = as.GetTime(FlagAt) tsk.Time = u.args.Time
} }
if as.IsSet(FlagFor) { if u.args.Duration != 0 {
tsk.Duration = as.GetDuration(FlagFor) tsk.Duration = u.args.Duration
} }
if as.IsSet(FlagRec) { if u.args.Recurrer != nil {
tsk.Recurrer = as.GetRecurrer(FlagRec) tsk.Recurrer = u.args.Recurrer
tsk.RecurNext = tsk.Recurrer.First()
} }
if !tsk.Valid() { if !tsk.Valid() {
return fmt.Errorf("task is unvalid") return fmt.Errorf("task is unvalid")
} }
if err := update.taskRepo.Store(tsk); err != nil { if err := deps.TaskRepo.Store(tsk); err != nil {
return fmt.Errorf("could not store task: %v", err) return fmt.Errorf("could not store task: %v", err)
} }
@ -109,7 +132,7 @@ func (update *Update) do() error {
if err != nil { if err != nil {
return fmt.Errorf("could not convert task to sync item: %v", err) return fmt.Errorf("could not convert task to sync item: %v", err)
} }
if err := update.syncRepo.Store(it); err != nil { if err := deps.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)
} }

View File

@ -31,18 +31,19 @@ func TestUpdateExecute(t *testing.T) {
name string name string
localID int localID int
main []string main []string
flags map[string]string fields map[string]string
expTask item.Task expTask item.Task
expErr bool expParseErr bool
expDoErr bool
}{ }{
{ {
name: "no args", name: "no args",
expErr: true, expParseErr: true,
}, },
{ {
name: "not found", name: "not found",
localID: 1, main: []string{"update", "1"},
expErr: true, expDoErr: true,
}, },
{ {
name: "name", name: "name",
@ -59,19 +60,19 @@ func TestUpdateExecute(t *testing.T) {
}, },
}, },
{ {
name: "invalid on", name: "invalid date",
localID: lid, localID: lid,
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ fields: map[string]string{
"on": "invalid", "on": "invalid",
}, },
expErr: true, expParseErr: true,
}, },
{ {
name: "on", name: "date",
localID: lid, localID: lid,
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ fields: map[string]string{
"on": "2024-10-02", "on": "2024-10-02",
}, },
expTask: item.Task{ expTask: item.Task{
@ -85,20 +86,20 @@ func TestUpdateExecute(t *testing.T) {
}, },
}, },
{ {
name: "invalid at", name: "invalid time",
localID: lid, localID: lid,
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ fields: map[string]string{
"at": "invalid", "at": "invalid",
}, },
expErr: true, expParseErr: true,
}, },
{ {
name: "at", name: "time",
localID: lid, localID: lid,
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ fields: map[string]string{
"at": "11:00", "time": "11:00",
}, },
expTask: item.Task{ expTask: item.Task{
ID: tskID, ID: tskID,
@ -111,37 +112,19 @@ func TestUpdateExecute(t *testing.T) {
}, },
}, },
{ {
name: "on and at", name: "invalid duration",
localID: lid, localID: lid,
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ fields: map[string]string{
"on": "2024-10-02",
"at": "11:00",
},
expTask: item.Task{
ID: tskID,
Date: item.NewDate(2024, 10, 2),
TaskBody: item.TaskBody{
Title: title,
Time: item.NewTime(11, 0),
Duration: oneHour,
},
},
},
{
name: "invalid for",
localID: lid,
main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{
"for": "invalid", "for": "invalid",
}, },
expErr: true, expParseErr: true,
}, },
{ {
name: "for", name: "duration",
localID: lid, localID: lid,
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ fields: map[string]string{
"for": "2h", "for": "2h",
}, },
expTask: item.Task{ expTask: item.Task{
@ -155,17 +138,17 @@ func TestUpdateExecute(t *testing.T) {
}, },
}, },
{ {
name: "invalid rec", name: "invalid recurrer",
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ fields: map[string]string{
"rec": "invalud", "rec": "invalud",
}, },
expErr: true, expParseErr: true,
}, },
{ {
name: "valid rec", name: "valid recurrer",
main: []string{"update", fmt.Sprintf("%d", lid)}, main: []string{"update", fmt.Sprintf("%d", lid)},
flags: map[string]string{ fields: map[string]string{
"rec": "2024-12-08, daily", "rec": "2024-12-08, daily",
}, },
expTask: item.Task{ expTask: item.Task{
@ -199,12 +182,22 @@ func TestUpdateExecute(t *testing.T) {
t.Errorf("exp nil, ,got %v", err) t.Errorf("exp nil, ,got %v", err)
} }
cmd := command.NewUpdate(localIDRepo, taskRepo, syncRepo) cmd, actErr := command.NewUpdateArgs().Parse(tc.main, tc.fields)
actParseErr := cmd.Execute(tc.main, tc.flags) != nil if tc.expParseErr != (actErr != nil) {
if tc.expErr != actParseErr { t.Errorf("exp %v, got %v", tc.expParseErr, actErr)
t.Errorf("exp %v, got %v", tc.expErr, actParseErr)
} }
if tc.expErr { if tc.expParseErr {
return
}
actDoErr := cmd.Do(command.Dependencies{
TaskRepo: taskRepo,
LocalIDRepo: localIDRepo,
SyncRepo: syncRepo,
}) != nil
if tc.expDoErr != actDoErr {
t.Errorf("exp %v, got %v", tc.expDoErr, actDoErr)
}
if tc.expDoErr {
return return
} }

View File

@ -35,16 +35,12 @@ func main() {
syncClient := client.New(conf.SyncURL, conf.ApiKey) syncClient := client.New(conf.SyncURL, conf.ApiKey)
cli := command.CLI{ cli := command.NewCLI(command.Dependencies{
Commands: []command.Command{ LocalIDRepo: localIDRepo,
command.NewAdd(localIDRepo, taskRepo, syncRepo), TaskRepo: taskRepo,
command.NewList(localIDRepo, taskRepo), SyncRepo: syncRepo,
command.NewUpdate(localIDRepo, taskRepo, syncRepo), SyncClient: syncClient,
command.NewDelete(localIDRepo, taskRepo, syncRepo), })
command.NewSync(syncClient, syncRepo, localIDRepo, taskRepo),
},
}
if err := cli.Run(os.Args[1:]); err != nil { if err := cli.Run(os.Args[1:]); err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(1) os.Exit(1)