From 1b9c000cf0ee04f0dbe2d9e10ccafc3b652301fa Mon Sep 17 00:00:00 2001 From: Jussi Maki Date: Tue, 21 Jan 2025 16:43:52 +0100 Subject: [PATCH] Rework hive script commands The 'hive' command now prints objects. The 'hive start' and 'hive stop' are kept for backwards compatibility. 'hive/start' and 'hive/stop' are added to reflect the new style (flat commands, no sub-commands, thus better help). Signed-off-by: Jussi Maki --- cell/info.go | 18 ++++++++++------ cell/lifecycle.go | 13 +++++------ command.go | 5 ++++- hive.go | 15 +++++++------ hivetest/lifecycle.go | 3 ++- script.go | 50 ++++++++++++++++++++++++++++++++----------- script_test.go | 6 +++--- 7 files changed, 75 insertions(+), 35 deletions(-) diff --git a/cell/info.go b/cell/info.go index 7c20753..aa534a5 100644 --- a/cell/info.go +++ b/cell/info.go @@ -7,7 +7,6 @@ import ( "bufio" "fmt" "io" - "os" "strings" "github.com/davecgh/go-spew/spew" @@ -24,13 +23,20 @@ type InfoPrinter struct { width int } -func NewInfoPrinter() *InfoPrinter { - width, _, err := term.GetSize(int(os.Stdout.Fd())) - if err != nil { - width = 120 +type fder interface { + Fd() uintptr +} + +func NewInfoPrinter(w io.Writer) *InfoPrinter { + width := 120 + if f, ok := w.(fder); ok { + widthFd, _, err := term.GetSize(int(f.Fd())) + if err == nil { + width = widthFd + } } return &InfoPrinter{ - Writer: os.Stdout, + Writer: w, width: width, } } diff --git a/cell/lifecycle.go b/cell/lifecycle.go index a41f75e..c6db269 100644 --- a/cell/lifecycle.go +++ b/cell/lifecycle.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "io" "log/slog" "sync" "time" @@ -56,7 +57,7 @@ type Lifecycle interface { Start(*slog.Logger, context.Context) error Stop(*slog.Logger, context.Context) error - PrintHooks() + PrintHooks(io.Writer) } // DefaultLifecycle lifecycle implements a simple lifecycle management that conforms @@ -178,27 +179,27 @@ func (lc *DefaultLifecycle) Stop(log *slog.Logger, ctx context.Context) error { return errs } -func (lc *DefaultLifecycle) PrintHooks() { +func (lc *DefaultLifecycle) PrintHooks(w io.Writer) { lc.mu.Lock() defer lc.mu.Unlock() - fmt.Printf("Start hooks:\n\n") + fmt.Fprintf(w, "Start hooks:\n\n") for _, hook := range lc.hooks { fnName, exists := getHookFuncName(hook.HookInterface, true) if !exists { continue } - fmt.Printf(" • %s (%s)\n", fnName, hook.moduleID) + fmt.Fprintf(w, " • %s (%s)\n", fnName, hook.moduleID) } - fmt.Printf("\nStop hooks:\n\n") + fmt.Fprintf(w, "\nStop hooks:\n\n") for i := len(lc.hooks) - 1; i >= 0; i-- { hook := lc.hooks[i] fnName, exists := getHookFuncName(hook.HookInterface, false) if !exists { continue } - fmt.Printf(" • %s (%s)\n", fnName, hook.moduleID) + fmt.Fprintf(w, " • %s (%s)\n", fnName, hook.moduleID) } } diff --git a/command.go b/command.go index 62ec267..b398e77 100644 --- a/command.go +++ b/command.go @@ -4,6 +4,9 @@ package hive import ( + "log/slog" + "os" + "github.com/spf13/cobra" ) @@ -14,7 +17,7 @@ func (h *Hive) Command() *cobra.Command { Use: "hive", Short: "Inspect the hive", Run: func(cmd *cobra.Command, args []string) { - h.PrintObjects() + h.PrintObjects(os.Stdout, slog.Default()) }, TraverseChildren: false, } diff --git a/hive.go b/hive.go index 7760ec8..e2495ef 100644 --- a/hive.go +++ b/hive.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "io" "log/slog" "os" "os/signal" @@ -392,18 +393,18 @@ func (h *Hive) Shutdown(opts ...ShutdownOption) { } } -func (h *Hive) PrintObjects() { - if err := h.Populate(slog.Default()); err != nil { +func (h *Hive) PrintObjects(w io.Writer, log *slog.Logger) { + if err := h.Populate(log); err != nil { panic(fmt.Sprintf("Failed to populate object graph: %s", err)) } - fmt.Printf("Cells:\n\n") - ip := cell.NewInfoPrinter() + fmt.Fprintf(w, "Cells:\n\n") + ip := cell.NewInfoPrinter(w) for _, c := range h.cells { c.Info(h.container).Print(2, ip) - fmt.Println() + fmt.Fprintln(w) } - h.lifecycle.PrintHooks() + h.lifecycle.PrintHooks(w) } func (h *Hive) PrintDotGraph() { @@ -429,6 +430,8 @@ func (h *Hive) ScriptCommands(log *slog.Logger) (map[string]script.Cmd, error) { } m := map[string]script.Cmd{} m["hive"] = hiveScriptCmd(h, log) + m["hive/start"] = hiveStartCmd(h, log) + m["hive/stop"] = hiveStopCmd(h, log) // Gather the commands from the hive. h.container.Invoke(func(sc ScriptCmds) { diff --git a/hivetest/lifecycle.go b/hivetest/lifecycle.go index 0c4e369..40e5060 100644 --- a/hivetest/lifecycle.go +++ b/hivetest/lifecycle.go @@ -5,6 +5,7 @@ package hivetest import ( "context" + "io" "log/slog" "testing" @@ -47,7 +48,7 @@ func (lc *lifecycle) Append(hook cell.HookInterface) { } // PrintHooks implements cell.Lifecycle. -func (*lifecycle) PrintHooks() { +func (*lifecycle) PrintHooks(io.Writer) { panic("unimplemented") } diff --git a/script.go b/script.go index bf3691a..6a4ec4e 100644 --- a/script.go +++ b/script.go @@ -63,28 +63,54 @@ type ScriptCmdsOut struct { ScriptCmds []ScriptCmd `group:"script-commands,flatten"` } +const defaultScriptTimeout = time.Minute + func hiveScriptCmd(h *Hive, log *slog.Logger) script.Cmd { - const defaultTimeout = time.Minute return script.Command( script.CmdUsage{ - Summary: "manipulate the hive", - Args: "cmd args...", + Summary: "show the hive", }, func(s *script.State, args ...string) (script.WaitFunc, error) { - if len(args) < 1 { - return nil, fmt.Errorf("hive cmd args...\n'cmd' is one of: start, stop, jobs") - } - switch args[0] { - case "start": - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + switch { + // For backwards compatibility. + case len(args) >= 1 && args[0] == "start": + ctx, cancel := context.WithTimeout(context.Background(), defaultScriptTimeout) defer cancel() return nil, h.Start(log, ctx) - case "stop": - ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + case len(args) >= 1 && args[0] == "stop": + ctx, cancel := context.WithTimeout(context.Background(), defaultScriptTimeout) defer cancel() return nil, h.Stop(log, ctx) + default: + h.PrintObjects(s.LogWriter(), log) + return nil, nil } - return nil, fmt.Errorf("unknown hive command %q, expected one of: start, stop, jobs", args[0]) + }, + ) +} + +func hiveStartCmd(h *Hive, log *slog.Logger) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "start the hive", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultScriptTimeout) + defer cancel() + return nil, h.Start(log, ctx) + }, + ) +} + +func hiveStopCmd(h *Hive, log *slog.Logger) script.Cmd { + return script.Command( + script.CmdUsage{ + Summary: "stop the hive", + }, + func(s *script.State, args ...string) (script.WaitFunc, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultScriptTimeout) + defer cancel() + return nil, h.Start(log, ctx) }, ) } diff --git a/script_test.go b/script_test.go index 764b084..0838b58 100644 --- a/script_test.go +++ b/script_test.go @@ -60,16 +60,16 @@ func TestScriptCommands(t *testing.T) { s, err := script.NewState(context.TODO(), "/tmp", nil) require.NoError(t, err, "NewState") script := ` -hive start +hive/start example1 example2 -hive stop +hive/stop ` bio := bufio.NewReader(bytes.NewBufferString(script)) var stdout bytes.Buffer err = e.Execute(s, "", bio, &stdout) require.NoError(t, err, "Execute") - expected := `> hive start.*> example1.*hello1.*> example2.*hello2.*> hive stop` + expected := `> hive/start.*> example1.*hello1.*> example2.*hello2.*> hive/stop` require.Regexp(t, expected, strings.ReplaceAll(stdout.String(), "\n", " ")) }