From 18982b5a35d88e94b619542ba7680867daff897b Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Fri, 26 Jan 2024 00:56:37 -0500 Subject: [PATCH 1/3] refactor(cli): split CLI and Namespace for clarity test(cli): coverage for namespace struct --- cli/cli.go | 30 ------------------------------ cli/namespace.go | 35 +++++++++++++++++++++++++++++++++++ cli/namespace_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 30 deletions(-) create mode 100644 cli/namespace.go create mode 100644 cli/namespace_test.go diff --git a/cli/cli.go b/cli/cli.go index 5d942de..778e013 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -13,14 +13,6 @@ type Flags struct { RawOutput bool } -// Command definition for CLI subcommands. -type Command struct { - Label string - Handler func([]string, Flags, state.State) error - Usage string - Description string -} - // Represents a CLI invocation. // Must be initialized with commands via AddCommand before running // with Run. @@ -29,12 +21,6 @@ type CLI struct { Metadata map[string]string } -type Namespace struct { - Label string - Commands map[string]Command - OrderedCommands []string -} - func (c *CLI) AddNamespace(namespace Namespace) *CLI { if c.Namespaces == nil { c.Namespaces = map[string]Namespace{} @@ -45,22 +31,6 @@ func (c *CLI) AddNamespace(namespace Namespace) *CLI { return c } -// Registers a command. -// This specifies a label that is used to route the user input to -// the right command, a handler that is called when the label is used, -// and usage/description details that get included in autogenerated help messaging. -func (n *Namespace) AddCommand(label string, handler func([]string, Flags, state.State) error, usage string, description string) *Namespace { - if n.Commands == nil { - n.Commands = map[string]Command{} - n.OrderedCommands = []string{} - } - - n.OrderedCommands = append(n.OrderedCommands, label) - n.Commands[label] = Command{Label: label, Handler: handler, Usage: usage, Description: description} - - return n -} - // Executes one of the registered commands if any match the provided // user arguments. func (c CLI) Run(args []string, currentState state.State) error { diff --git a/cli/namespace.go b/cli/namespace.go new file mode 100644 index 0000000..2bd734c --- /dev/null +++ b/cli/namespace.go @@ -0,0 +1,35 @@ +package cli + +import ( + state "v/state" +) + +type Namespace struct { + Label string + Commands map[string]Command + OrderedCommands []string +} + +// Command definition for CLI subcommands. +type Command struct { + Label string + Handler func([]string, Flags, state.State) error + Usage string + Description string +} + +// Registers a command. +// This specifies a label that is used to route the user input to +// the right command, a handler that is called when the label is used, +// and usage/description details that get included in autogenerated help messaging. +func (n *Namespace) AddCommand(label string, handler func([]string, Flags, state.State) error, usage string, description string) *Namespace { + if n.Commands == nil { + n.Commands = map[string]Command{} + n.OrderedCommands = []string{} + } + + n.OrderedCommands = append(n.OrderedCommands, label) + n.Commands[label] = Command{Label: label, Handler: handler, Usage: usage, Description: description} + + return n +} diff --git a/cli/namespace_test.go b/cli/namespace_test.go new file mode 100644 index 0000000..28a6b01 --- /dev/null +++ b/cli/namespace_test.go @@ -0,0 +1,31 @@ +package cli + +import ( + "testing" + state "v/state" +) + +func TestNamespaceAddCommand(t *testing.T) { + namespace := Namespace{} + + canary := 0 + + handler := func(a []string, b Flags, c state.State) error { + canary = 1 + return nil + } + + namespace.AddCommand("test", handler, "", "") + + if len(namespace.Commands) != 1 { + t.Errorf("Expected one command, found %d", len(namespace.Commands)) + } + + if e := namespace.Commands["test"].Handler([]string{}, Flags{}, state.State{}); e != nil { + t.Errorf("Unexpected error when running handler: %s", e) + } + + if canary != 1 { + t.Errorf("Expected canary value to have been modified.") + } +} From 1cfd60e9d7a3a43f6723f718824631f3014c61b7 Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Sun, 28 Jan 2024 13:52:21 -0500 Subject: [PATCH 2/3] refactor(cli): remove ordered commands array, dynamically gather instead --- cli/cli.go | 2 +- cli/namespace.go | 22 +++++++++++++++++----- cli/namespace_test.go | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index 778e013..ead2055 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -65,7 +65,7 @@ func (c CLI) Run(args []string, currentState state.State) error { func (c CLI) Help() { logger.InfoLogger.Printf("v: A simple version manager. (v%s)\n---", c.Metadata["Version"]) for _, namespace := range c.Namespaces { - for _, commandLabel := range namespace.OrderedCommands { + for _, commandLabel := range namespace.ListCommands() { command := namespace.Commands[commandLabel] logger.InfoLogger.Printf("\033[1m%-30s\033[0m%s\n", command.Usage, command.Description) } diff --git a/cli/namespace.go b/cli/namespace.go index 2bd734c..a590b04 100644 --- a/cli/namespace.go +++ b/cli/namespace.go @@ -1,13 +1,13 @@ package cli import ( + "slices" state "v/state" ) type Namespace struct { - Label string - Commands map[string]Command - OrderedCommands []string + Label string + Commands map[string]Command } // Command definition for CLI subcommands. @@ -25,11 +25,23 @@ type Command struct { func (n *Namespace) AddCommand(label string, handler func([]string, Flags, state.State) error, usage string, description string) *Namespace { if n.Commands == nil { n.Commands = map[string]Command{} - n.OrderedCommands = []string{} } - n.OrderedCommands = append(n.OrderedCommands, label) n.Commands[label] = Command{Label: label, Handler: handler, Usage: usage, Description: description} return n } + +// Returns an alpha-ordered list of commands in the namespace. +// The commands are returned as a list of command labels / actions. +func (n *Namespace) ListCommands() []string { + labels := []string{} + + for key := range n.Commands { + labels = append(labels, key) + } + + slices.Sort(labels) + + return labels +} diff --git a/cli/namespace_test.go b/cli/namespace_test.go index 28a6b01..fb32fb0 100644 --- a/cli/namespace_test.go +++ b/cli/namespace_test.go @@ -1,6 +1,7 @@ package cli import ( + "slices" "testing" state "v/state" ) @@ -29,3 +30,21 @@ func TestNamespaceAddCommand(t *testing.T) { t.Errorf("Expected canary value to have been modified.") } } + +func TestNamespaceListCommandsReturnsAlphaSortedLabels(t *testing.T) { + namespace := Namespace{} + + handler := func(a []string, b Flags, c state.State) error { + return nil + } + + // Inserted in non-alpha order. + namespace.AddCommand("b", handler, "", "") + namespace.AddCommand("a", handler, "", "") + + labels := namespace.ListCommands() + + if !slices.Equal(labels, []string{"a", "b"}) { + t.Errorf("Expected labels to be alpha-ordered. Got %v", labels) + } +} From 2c7225d44cccf819564d670aee09d8667f2437ba Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Sun, 28 Jan 2024 14:01:36 -0500 Subject: [PATCH 3/3] refactor(cli): ensure namespace iter are alpha-ordered --- cli/cli.go | 16 +++++++++++++++- cli/cli_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 cli/cli_test.go diff --git a/cli/cli.go b/cli/cli.go index ead2055..2a661c1 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -1,6 +1,7 @@ package cli import ( + "slices" "strings" logger "v/logger" state "v/state" @@ -31,6 +32,18 @@ func (c *CLI) AddNamespace(namespace Namespace) *CLI { return c } +func (c *CLI) ListNamespaces() []string { + labels := []string{} + + for key := range c.Namespaces { + labels = append(labels, key) + } + + slices.Sort(labels) + + return labels +} + // Executes one of the registered commands if any match the provided // user arguments. func (c CLI) Run(args []string, currentState state.State) error { @@ -64,7 +77,8 @@ func (c CLI) Run(args []string, currentState state.State) error { // and descriptions based on registered commands (see: AddCommand). func (c CLI) Help() { logger.InfoLogger.Printf("v: A simple version manager. (v%s)\n---", c.Metadata["Version"]) - for _, namespace := range c.Namespaces { + for _, namespaceLabel := range c.ListNamespaces() { + namespace := c.Namespaces[namespaceLabel] for _, commandLabel := range namespace.ListCommands() { command := namespace.Commands[commandLabel] logger.InfoLogger.Printf("\033[1m%-30s\033[0m%s\n", command.Usage, command.Description) diff --git a/cli/cli_test.go b/cli/cli_test.go new file mode 100644 index 0000000..6d95e51 --- /dev/null +++ b/cli/cli_test.go @@ -0,0 +1,37 @@ +package cli + +import ( + "slices" + "testing" +) + +func TestAddNamespace(t *testing.T) { + cli := CLI{} + + namespace := Namespace{Label: "test"} + + cli.AddNamespace(namespace) + + if len(cli.Namespaces) != 1 { + t.Errorf("Expected one namespace added.") + } + + if cli.Namespaces["test"].Label != namespace.Label { + t.Errorf("Unexpected label value: %s", cli.Namespaces["test"].Label) + } +} + +func TestListNamespacesReturnsAlphaOrderedLabels(t *testing.T) { + cli := CLI{} + + n1 := Namespace{Label: "a"} + n2 := Namespace{Label: "b"} + cli.AddNamespace(n2).AddNamespace(n1) + + labels := cli.ListNamespaces() + + if !slices.Equal(labels, []string{"a", "b"}) { + t.Errorf("Expected labels to be alpha ordered, got %v", labels) + } + +}