2024-10-29 07:22:04 +01:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2024-12-27 11:20:32 +01:00
|
|
|
"slices"
|
2024-10-29 07:22:04 +01:00
|
|
|
"strings"
|
2024-12-27 11:20:32 +01:00
|
|
|
|
|
|
|
"go-mod.ewintr.nl/planner/plan/storage"
|
|
|
|
"go-mod.ewintr.nl/planner/sync/client"
|
2024-10-29 07:22:04 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2024-12-27 11:20:32 +01:00
|
|
|
DateFormat = "2006-01-02"
|
|
|
|
TimeFormat = "15:04"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrWrongCommand = errors.New("wrong command")
|
|
|
|
ErrInvalidArg = errors.New("invalid argument")
|
2024-10-29 07:22:04 +01:00
|
|
|
)
|
|
|
|
|
2025-01-13 09:13:48 +01:00
|
|
|
type Repositories interface {
|
|
|
|
Begin() (*storage.Tx, error)
|
|
|
|
LocalID(tx *storage.Tx) storage.LocalID
|
|
|
|
Sync(tx *storage.Tx) storage.Sync
|
|
|
|
Task(tx *storage.Tx) storage.Task
|
2024-12-27 11:20:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type CommandArgs interface {
|
|
|
|
Parse(main []string, fields map[string]string) (Command, error)
|
|
|
|
}
|
|
|
|
|
2024-10-29 07:22:04 +01:00
|
|
|
type Command interface {
|
2025-01-13 09:13:48 +01:00
|
|
|
Do(repos Repositories, client client.Client) (CommandResult, error)
|
2024-12-29 09:32:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type CommandResult interface {
|
|
|
|
Render() string
|
2024-10-29 07:22:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type CLI struct {
|
2025-01-13 09:13:48 +01:00
|
|
|
repos Repositories
|
|
|
|
client client.Client
|
2024-12-27 11:20:32 +01:00
|
|
|
cmdArgs []CommandArgs
|
2024-10-29 07:22:04 +01:00
|
|
|
}
|
|
|
|
|
2025-01-13 09:13:48 +01:00
|
|
|
func NewCLI(repos Repositories, client client.Client) *CLI {
|
2024-12-27 11:20:32 +01:00
|
|
|
return &CLI{
|
2025-01-13 09:13:48 +01:00
|
|
|
repos: repos,
|
|
|
|
client: client,
|
2024-12-27 11:20:32 +01:00
|
|
|
cmdArgs: []CommandArgs{
|
2025-01-05 12:17:57 +01:00
|
|
|
NewShowArgs(), NewProjectsArgs(),
|
2024-12-27 11:20:32 +01:00
|
|
|
NewAddArgs(), NewDeleteArgs(), NewListArgs(),
|
|
|
|
NewSyncArgs(), NewUpdateArgs(),
|
|
|
|
},
|
2024-10-29 07:22:04 +01:00
|
|
|
}
|
2024-12-27 11:20:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cli *CLI) Run(args []string) error {
|
2024-12-29 09:32:49 +01:00
|
|
|
main, fields := FindFields(args)
|
2024-12-27 11:20:32 +01:00
|
|
|
for _, ca := range cli.cmdArgs {
|
2024-12-29 09:32:49 +01:00
|
|
|
cmd, err := ca.Parse(main, fields)
|
2024-10-29 07:22:04 +01:00
|
|
|
switch {
|
|
|
|
case errors.Is(err, ErrWrongCommand):
|
|
|
|
continue
|
|
|
|
case err != nil:
|
|
|
|
return err
|
|
|
|
}
|
2024-12-29 09:32:49 +01:00
|
|
|
|
2025-01-13 09:13:48 +01:00
|
|
|
result, err := cmd.Do(cli.repos, cli.client)
|
2024-12-29 09:32:49 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Println(result.Render())
|
|
|
|
|
|
|
|
return nil
|
2024-10-29 07:22:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf("could not find matching command")
|
|
|
|
}
|
|
|
|
|
2024-12-27 11:20:32 +01:00
|
|
|
func FindFields(args []string) ([]string, map[string]string) {
|
|
|
|
fields := make(map[string]string)
|
2024-10-29 07:22:04 +01:00
|
|
|
main := make([]string, 0)
|
|
|
|
for i := 0; i < len(args); i++ {
|
2025-01-05 08:58:29 +01:00
|
|
|
// normal key:value
|
2024-12-27 11:20:32 +01:00
|
|
|
if k, v, ok := strings.Cut(args[i], ":"); ok && !strings.Contains(k, " ") {
|
|
|
|
fields[k] = v
|
2024-10-29 07:22:04 +01:00
|
|
|
continue
|
|
|
|
}
|
2025-01-05 08:58:29 +01:00
|
|
|
// empty key:
|
|
|
|
if !strings.Contains(args[i], " ") && strings.HasSuffix(args[i], ":") {
|
|
|
|
k := strings.TrimSuffix(args[i], ":")
|
|
|
|
fields[k] = ""
|
|
|
|
}
|
2024-12-27 11:20:32 +01:00
|
|
|
main = append(main, args[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
return main, fields
|
|
|
|
}
|
2024-10-29 07:22:04 +01:00
|
|
|
|
2024-12-27 11:20:32 +01:00
|
|
|
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)
|
|
|
|
}
|
2024-10-29 07:22:04 +01:00
|
|
|
}
|
2024-12-27 11:20:32 +01:00
|
|
|
}
|
|
|
|
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, ","))
|
2024-10-29 07:22:04 +01:00
|
|
|
}
|
|
|
|
|
2024-12-27 11:20:32 +01:00
|
|
|
return res, nil
|
2024-10-29 07:22:04 +01:00
|
|
|
}
|