From 5a4ef087e55184308debdd311ac09287c27b66f1 Mon Sep 17 00:00:00 2001 From: "R.I.Pienaar" Date: Tue, 10 Sep 2024 12:31:16 +0200 Subject: [PATCH] Further refactoring into internal utils package Signed-off-by: R.I.Pienaar --- cli/account_command.go | 3 +- cli/bench_command.go | 20 ++-- cli/consumer_command.go | 6 +- cli/object_command.go | 4 +- cli/pub_command.go | 3 +- cli/server_generate.go | 7 ++ cli/server_report_command.go | 6 +- cli/server_request_command.go | 3 +- cli/server_watch_acct_command.go | 19 ++-- cli/server_watch_js_command.go | 15 +-- cli/server_watch_srv_command.go | 27 ++--- cli/stream_command.go | 8 +- cli/sub_command.go | 3 +- cli/util.go | 162 +-------------------------- cli/util_test.go | 18 --- go.mod | 5 +- go.sum | 8 +- internal/util/config.go | 55 +++++++++ internal/util/util.go | 186 +++++++++++++++++++++---------- internal/util/util_test.go | 36 ++++++ 20 files changed, 302 insertions(+), 292 deletions(-) create mode 100644 internal/util/util_test.go diff --git a/cli/account_command.go b/cli/account_command.go index 0674819b..9aa3c77b 100644 --- a/cli/account_command.go +++ b/cli/account_command.go @@ -31,6 +31,7 @@ import ( "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" "github.com/nats-io/natscli/columns" + iu "github.com/nats-io/natscli/internal/util" ) type actCmd struct { @@ -403,7 +404,7 @@ func (c *actCmd) infoAction(_ *fisk.ParseContext) error { tlsc, _ := nc.TLSConnectionState() var ui *server.UserInfo - if serverMinVersion(nc.ConnectedServerVersion(), 2, 10, 0) { + if iu.ServerMinVersion(nc, 2, 10, 0) { subj := "$SYS.REQ.USER.INFO" if opts().Trace { log.Printf(">>> %s: {}\n", subj) diff --git a/cli/bench_command.go b/cli/bench_command.go index 634e3eb6..e79c39e7 100644 --- a/cli/bench_command.go +++ b/cli/bench_command.go @@ -17,7 +17,6 @@ import ( "bytes" "context" "fmt" - "github.com/nats-io/nats.go/jetstream" "math" "math/rand" "os" @@ -27,6 +26,9 @@ import ( "sync" "time" + "github.com/nats-io/nats.go/jetstream" + iu "github.com/nats-io/natscli/internal/util" + "github.com/choria-io/fisk" "github.com/dustin/go-humanize" "github.com/gosuri/uiprogress" @@ -1722,7 +1724,7 @@ func (c *benchCmd) runCorePublisher(bm *bench.Benchmark, errChan chan error, nc } progress = uiprogress.AddBar(barTotal).AppendCompleted().PrependElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() if numMsg == 0 { progress.PrependFunc(func(b *uiprogress.Bar) string { @@ -1779,7 +1781,7 @@ func (c *benchCmd) runCoreSubscriber(bm *bench.Benchmark, errChan chan error, nc if c.progressBar { progress = uiprogress.AddBar(numMsg).AppendCompleted().PrependElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() } state := "Setup " @@ -1855,7 +1857,7 @@ func (c *benchCmd) runCoreRequester(bm *bench.Benchmark, errChan chan error, nc if c.progressBar { progress = uiprogress.AddBar(numMsg).AppendCompleted().PrependElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() } var msg []byte @@ -1948,7 +1950,7 @@ func (c *benchCmd) runJSPublisher(bm *bench.Benchmark, errChan chan error, nc *n if c.progressBar { progress = uiprogress.AddBar(numMsg).AppendCompleted().PrependElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() } var msg []byte @@ -1996,7 +1998,7 @@ func (c *benchCmd) runJSSubscriber(bm *bench.Benchmark, errChan chan error, nc * if c.progressBar { progress = uiprogress.AddBar(numMsg).AppendCompleted().PrependElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() } state := "Setup " @@ -2186,7 +2188,7 @@ func (c *benchCmd) runKVPutter(bm *bench.Benchmark, errChan chan error, nc *nats if c.progressBar { progress = uiprogress.AddBar(numMsg).AppendCompleted().PrependElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() } var msg []byte @@ -2232,7 +2234,7 @@ func (c *benchCmd) runKVGetter(bm *bench.Benchmark, errChan chan error, nc *nats if c.progressBar { progress = uiprogress.AddBar(numMsg).AppendCompleted().PrependElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() } state := "Setup " @@ -2326,7 +2328,7 @@ func (c *benchCmd) runOldJSSubscriber(bm *bench.Benchmark, errChan chan error, n if c.progressBar { progress = uiprogress.AddBar(numMsg).AppendCompleted().PrependElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() } state := "Setup " diff --git a/cli/consumer_command.go b/cli/consumer_command.go index afa93dbd..795dc032 100644 --- a/cli/consumer_command.go +++ b/cli/consumer_command.go @@ -470,7 +470,7 @@ func (c *consumerCmd) graphAction(_ *fisk.ParseContext) error { asciigraph.Precision(0), ) - asciigraph.Clear() + iu.ClearScreen() fmt.Printf("Consumer Statistics for %s > %s\n", c.stream, c.consumer) fmt.Println() @@ -483,7 +483,7 @@ func (c *consumerCmd) graphAction(_ *fisk.ParseContext) error { fmt.Println(deliveredPlot) case <-ctx.Done(): - asciigraph.Clear() + iu.ClearScreen() return nil } } @@ -941,7 +941,7 @@ func (c *consumerCmd) showInfo(config api.ConsumerConfig, state api.ConsumerInfo if len(config.Metadata) > 0 { cols.AddSectionTitle("Metadata") - maxLen := progressWidth() + maxLen := iu.ProgressWidth() for k, v := range config.Metadata { if len(v) > maxLen && maxLen > 20 { w := maxLen/2 - 10 diff --git a/cli/object_command.go b/cli/object_command.go index 66b8e2ed..ba32dc26 100644 --- a/cli/object_command.go +++ b/cli/object_command.go @@ -500,7 +500,7 @@ func (c *objCommand) putAction(_ *fisk.ParseContext) error { progress = uiprogress.AddBar(int(stat.Size())).PrependFunc(func(b *uiprogress.Bar) string { return fmt.Sprintf("%s / %s", humanize.IBytes(uint64(b.Current())), hs) }) - progress.Width = progressWidth() + progress.Width = util.ProgressWidth() fmt.Println() uiprogress.Start() @@ -576,7 +576,7 @@ func (c *objCommand) getAction(_ *fisk.ParseContext) error { progress = uiprogress.AddBar(int(nfo.Size)).PrependFunc(func(b *uiprogress.Bar) string { return fmt.Sprintf("%s / %s", humanize.IBytes(uint64(b.Current())), hs) }) - progress.Width = progressWidth() + progress.Width = util.ProgressWidth() fmt.Println() uiprogress.Start() diff --git a/cli/pub_command.go b/cli/pub_command.go index 22141919..c52bee35 100644 --- a/cli/pub_command.go +++ b/cli/pub_command.go @@ -24,6 +24,7 @@ import ( "github.com/gosuri/uiprogress" "github.com/nats-io/jsm.go" "github.com/nats-io/nats.go" + iu "github.com/nats-io/natscli/internal/util" terminal "golang.org/x/term" ) @@ -305,7 +306,7 @@ func (c *pubCmd) publish(_ *fisk.ParseContext) error { progress = uiprogress.AddBar(c.cnt).PrependFunc(func(b *uiprogress.Bar) string { return fmt.Sprintf(progressFormat, b.Current(), c.cnt) }).AppendElapsed() - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() fmt.Println() uiprogress.Start() diff --git a/cli/server_generate.go b/cli/server_generate.go index d4ea06db..ea197d1d 100644 --- a/cli/server_generate.go +++ b/cli/server_generate.go @@ -47,6 +47,13 @@ func (c *serverGenerateCmd) generateAction(_ *fisk.ParseContext) error { return fmt.Errorf("target directory %s already exist", c.target) } + fmt.Println("This tool generates NATS Server configurations based on a question and answer") + fmt.Println("form-based approach and then renders the result into a directory.") + fmt.Println() + fmt.Println("It supports rendering local bundles compiled into the 'nats' command but can also") + fmt.Println("fetch and render remote ones using a URL.") + fmt.Println() + switch { case c.source == "": err = c.pickEmbedded() diff --git a/cli/server_report_command.go b/cli/server_report_command.go index fe74bc37..cff43fd6 100644 --- a/cli/server_report_command.go +++ b/cli/server_report_command.go @@ -152,16 +152,16 @@ func (c *SrvReportCmd) reportCpuOrMem(mem bool) error { return iu.PrintJSON(usage) } - width := progressWidth() / 2 + width := iu.ProgressWidth() / 2 if width > 30 { width = 30 } if mem { - return barGraph(os.Stdout, usage, "Memory Usage", width, true) + return iu.BarGraph(os.Stdout, usage, "Memory Usage", width, true) } - return barGraph(os.Stdout, usage, "CPU Usage", width, false) + return iu.BarGraph(os.Stdout, usage, "CPU Usage", width, false) } func (c *SrvReportCmd) reportJetStream(_ *fisk.ParseContext) error { diff --git a/cli/server_request_command.go b/cli/server_request_command.go index ed23fddd..cc5da3c3 100644 --- a/cli/server_request_command.go +++ b/cli/server_request_command.go @@ -17,11 +17,12 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/nats-io/natscli/options" "os" "path/filepath" "time" + "github.com/nats-io/natscli/options" + "github.com/choria-io/fisk" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" diff --git a/cli/server_watch_acct_command.go b/cli/server_watch_acct_command.go index 5436cdbe..6520c350 100644 --- a/cli/server_watch_acct_command.go +++ b/cli/server_watch_acct_command.go @@ -27,6 +27,7 @@ import ( "github.com/choria-io/fisk" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" + iu "github.com/nats-io/natscli/internal/util" terminal "golang.org/x/term" ) @@ -53,7 +54,7 @@ func configureServerWatchAccountCommand(watch *fisk.CmdClause) { "slow": "Slow Consumers", }, } - sortKeys := mapKeys(c.sortNames) + sortKeys := iu.MapKeys(c.sortNames) sort.Strings(sortKeys) accounts := watch.Command("accounts", "Watch account usage").Alias("account").Alias("acct").Action(c.accountsAction) @@ -162,19 +163,19 @@ func (c *SrvWatchAccountCmd) redraw() error { switch c.sort { case "subs": - return sortMultiSort(ai.NumSubs, aj.NumSubs, ai.Conns, aj.Conns) + return iu.SortMultiSort(ai.NumSubs, aj.NumSubs, ai.Conns, aj.Conns) case "slow": - return sortMultiSort(ai.SlowConsumers, aj.SlowConsumers, ai.Conns, aj.Conns) + return iu.SortMultiSort(ai.SlowConsumers, aj.SlowConsumers, ai.Conns, aj.Conns) case "sentb": - return sortMultiSort(ai.Sent.Bytes, aj.Sent.Bytes, ai.Conns, aj.Conns) + return iu.SortMultiSort(ai.Sent.Bytes, aj.Sent.Bytes, ai.Conns, aj.Conns) case "sentm": - return sortMultiSort(ai.Sent.Msgs, aj.Sent.Msgs, ai.Conns, aj.Conns) + return iu.SortMultiSort(ai.Sent.Msgs, aj.Sent.Msgs, ai.Conns, aj.Conns) case "recvb": - return sortMultiSort(ai.Received.Bytes, aj.Received.Bytes, ai.Conns, aj.Conns) + return iu.SortMultiSort(ai.Received.Bytes, aj.Received.Bytes, ai.Conns, aj.Conns) case "recvm": - return sortMultiSort(ai.Received.Msgs, aj.Received.Msgs, ai.Conns, aj.Conns) + return iu.SortMultiSort(ai.Received.Msgs, aj.Received.Msgs, ai.Conns, aj.Conns) default: - return sortMultiSort(ai.Conns, aj.Conns, ai.Conns, aj.Conns) + return iu.SortMultiSort(ai.Conns, aj.Conns, ai.Conns, aj.Conns) } }) @@ -193,7 +194,7 @@ func (c *SrvWatchAccountCmd) redraw() error { matched = accounts[:c.topCount] } - clearScreen() + iu.ClearScreen() for _, account := range matched { acct := account.Account if account.Name != "" { diff --git a/cli/server_watch_js_command.go b/cli/server_watch_js_command.go index 4b134cb7..0207da09 100644 --- a/cli/server_watch_js_command.go +++ b/cli/server_watch_js_command.go @@ -27,6 +27,7 @@ import ( "github.com/choria-io/fisk" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" + iu "github.com/nats-io/natscli/internal/util" terminal "golang.org/x/term" ) @@ -52,7 +53,7 @@ func configureServerWatchJSCommand(watch *fisk.CmdClause) { }, } - sortKeys := mapKeys(c.sortNames) + sortKeys := iu.MapKeys(c.sortNames) sort.Strings(sortKeys) js := watch.Command("jetstream", "Watch JetStream statistics").Alias("js").Alias("jsz").Action(c.jetstreamAction) @@ -173,15 +174,15 @@ func (c *SrvWatchJSCmd) redraw() error { switch c.sort { case "mem": - return sortMultiSort(si.Memory, sj.Memory, servers[i].Server.Name, servers[j].Server.Name) + return iu.SortMultiSort(si.Memory, sj.Memory, servers[i].Server.Name, servers[j].Server.Name) case "file": - return sortMultiSort(si.Store, sj.Store, servers[i].Server.Name, servers[j].Server.Name) + return iu.SortMultiSort(si.Store, sj.Store, servers[i].Server.Name, servers[j].Server.Name) case "api": - return sortMultiSort(si.API.Total, sj.API.Total, servers[i].Server.Name, servers[j].Server.Name) + return iu.SortMultiSort(si.API.Total, sj.API.Total, servers[i].Server.Name, servers[j].Server.Name) case "err": - return sortMultiSort(si.API.Errors, sj.API.Errors, servers[i].Server.Name, servers[j].Server.Name) + return iu.SortMultiSort(si.API.Errors, sj.API.Errors, servers[i].Server.Name, servers[j].Server.Name) default: - return sortMultiSort(si.HAAssets, sj.HAAssets, servers[i].Server.Name, servers[j].Server.Name) + return iu.SortMultiSort(si.HAAssets, sj.HAAssets, servers[i].Server.Name, servers[j].Server.Name) } }) @@ -213,7 +214,7 @@ func (c *SrvWatchJSCmd) redraw() error { } table.AddFooter("Totals (All Servers)", f(assets), fiBytes(mem), fiBytes(store), f(api), f(apiError)) - clearScreen() + iu.ClearScreen() fmt.Print(table.Render()) return nil diff --git a/cli/server_watch_srv_command.go b/cli/server_watch_srv_command.go index 21afe959..33ca6505 100644 --- a/cli/server_watch_srv_command.go +++ b/cli/server_watch_srv_command.go @@ -27,6 +27,7 @@ import ( "github.com/choria-io/fisk" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" + iu "github.com/nats-io/natscli/internal/util" terminal "golang.org/x/term" ) @@ -58,7 +59,7 @@ func configureServerWatchServerCommand(watch *fisk.CmdClause) { }, } - sortKeys := mapKeys(c.sortNames) + sortKeys := iu.MapKeys(c.sortNames) sort.Strings(sortKeys) servers := watch.Command("servers", "Watch server statistics").Alias("server").Alias("srv").Action(c.serversAction) @@ -178,27 +179,27 @@ func (c *SrvWatchServerCmd) redraw() error { switch c.sort { case "subs": - return sortMultiSort(si.NumSubs, sj.NumSubs, iName, jName) + return iu.SortMultiSort(si.NumSubs, sj.NumSubs, iName, jName) case "sentb": - return sortMultiSort(si.Sent.Bytes, sj.Sent.Bytes, iName, jName) + return iu.SortMultiSort(si.Sent.Bytes, sj.Sent.Bytes, iName, jName) case "sentm": - return sortMultiSort(si.Sent.Msgs, sj.Sent.Msgs, iName, jName) + return iu.SortMultiSort(si.Sent.Msgs, sj.Sent.Msgs, iName, jName) case "recvb": - return sortMultiSort(si.Received.Bytes, sj.Received.Bytes, iName, jName) + return iu.SortMultiSort(si.Received.Bytes, sj.Received.Bytes, iName, jName) case "recvm": - return sortMultiSort(si.Received.Msgs, sj.Received.Msgs, iName, jName) + return iu.SortMultiSort(si.Received.Msgs, sj.Received.Msgs, iName, jName) case "slow": - return sortMultiSort(si.SlowConsumers, sj.SlowConsumers, iName, jName) + return iu.SortMultiSort(si.SlowConsumers, sj.SlowConsumers, iName, jName) case "route": - return sortMultiSort(len(si.Routes), len(sj.Routes), iName, jName) + return iu.SortMultiSort(len(si.Routes), len(sj.Routes), iName, jName) case "gway": - return sortMultiSort(len(si.Gateways), len(sj.Gateways), iName, jName) + return iu.SortMultiSort(len(si.Gateways), len(sj.Gateways), iName, jName) case "mem": - return sortMultiSort(si.Mem, sj.Mem, iName, jName) + return iu.SortMultiSort(si.Mem, sj.Mem, iName, jName) case "cpu": - return sortMultiSort(si.CPU, sj.CPU, iName, jName) + return iu.SortMultiSort(si.CPU, sj.CPU, iName, jName) default: - return sortMultiSort(si.Connections, sj.Connections, iName, jName) + return iu.SortMultiSort(si.Connections, sj.Connections, iName, jName) } }) @@ -235,7 +236,7 @@ func (c *SrvWatchServerCmd) redraw() error { table.AddFooter("Totals (All Servers)", f(conns), f(subs), f(slow), fiBytes(uint64(mem)), "", "", "", fmt.Sprintf("%s / %s", f(sentM), fiBytes(uint64(sentB))), fmt.Sprintf("%s / %s", f(recvM), fiBytes(uint64(recvB)))) - clearScreen() + iu.ClearScreen() fmt.Print(table.Render()) return nil } diff --git a/cli/stream_command.go b/cli/stream_command.go index 6cd8d236..38384f41 100644 --- a/cli/stream_command.go +++ b/cli/stream_command.go @@ -522,7 +522,7 @@ func (c *streamCmd) graphAction(_ *fisk.ParseContext) error { asciigraph.Precision(0), ) - asciigraph.Clear() + iu.ClearScreen() fmt.Printf("Stream Statistics for %s\n", c.stream) fmt.Println() @@ -533,7 +533,7 @@ func (c *streamCmd) graphAction(_ *fisk.ParseContext) error { fmt.Println(msgRatePlot) case <-ctx.Done(): - asciigraph.Clear() + iu.ClearScreen() return nil } } @@ -1099,7 +1099,7 @@ func (c *streamCmd) restoreAction(_ *fisk.ParseContext) error { progress = uiprogress.AddBar(p.ChunksToSend()).AppendCompleted().PrependFunc(func(b *uiprogress.Bar) string { return humanize.IBytes(bps) + "/s" }) - progress.Width = progressWidth() + progress.Width = iu.ProgressWidth() } progress.Set(int(p.ChunksSent())) @@ -1201,7 +1201,7 @@ func backupStream(stream *jsm.Stream, showProgress bool, consumers bool, check b bar = progress.AddBar(expected).AppendCompleted().PrependFunc(func(b *uiprogress.Bar) string { return humanize.IBytes(bps) + "/s" }) - bar.Width = progressWidth() + bar.Width = iu.ProgressWidth() } if first { diff --git a/cli/sub_command.go b/cli/sub_command.go index d7b0e7ed..64b9a61f 100644 --- a/cli/sub_command.go +++ b/cli/sub_command.go @@ -32,6 +32,7 @@ import ( "github.com/nats-io/jsm.go" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" + iu "github.com/nats-io/natscli/internal/util" terminal "golang.org/x/term" ) @@ -172,7 +173,7 @@ func (c *subCmd) startGraph(ctx context.Context, mu *sync.Mutex) { c.height -= 6 } - asciigraph.Clear() + iu.ClearScreen() for _, subject := range c.subjects { rates := c.messageRates[subject] diff --git a/cli/util.go b/cli/util.go index 9634c4cb..9845af50 100644 --- a/cli/util.go +++ b/cli/util.go @@ -42,7 +42,6 @@ import ( "github.com/AlecAivazis/survey/v2" "github.com/choria-io/fisk" - "github.com/dustin/go-humanize" "github.com/google/shlex" "github.com/gosuri/uiprogress" "github.com/jedib0t/go-pretty/v6/table" @@ -51,13 +50,10 @@ import ( "github.com/mattn/go-isatty" "github.com/nats-io/jsm.go" "github.com/nats-io/jsm.go/api" + "github.com/nats-io/jsm.go/natscontext" "github.com/nats-io/nats-server/v2/server" "github.com/nats-io/nats.go" "github.com/nats-io/nuid" - "golang.org/x/exp/constraints" - terminal "golang.org/x/term" - - "github.com/nats-io/jsm.go/natscontext" ) var ( @@ -164,28 +160,6 @@ func selectStream(mgr *jsm.Manager, stream string, force bool, all bool) (string } } -// calculates progress bar width for uiprogress: -// -// if it cant figure out the width, assume 80 -// if the width is too small, set it to minWidth and just live with the overflow -// -// this ensures a reasonable progress size, ideally we should switch over -// to a spinner for < minWidth rather than cause overflows, but thats for later. -func progressWidth() int { - w, _, err := terminal.GetSize(int(os.Stdout.Fd())) - if err != nil { - return 80 - } - - minWidth := 10 - - if w-30 <= minWidth { - return minWidth - } else { - return w - 30 - } -} - func sinceRefOrNow(ref time.Time, ts time.Time) time.Duration { if ref.IsZero() { return time.Since(ts) @@ -1151,37 +1125,6 @@ func parseStringAsBytes(s string) (int64, error) { return num, nil } -var semVerRe = regexp.MustCompile(`\Av?([0-9]+)\.?([0-9]+)?\.?([0-9]+)?`) - -func versionComponents(version string) (major, minor, patch int, err error) { - m := semVerRe.FindStringSubmatch(version) - if m == nil { - return 0, 0, 0, errors.New("invalid semver") - } - major, err = strconv.Atoi(m[1]) - if err != nil { - return -1, -1, -1, err - } - minor, err = strconv.Atoi(m[2]) - if err != nil { - return -1, -1, -1, err - } - patch, err = strconv.Atoi(m[3]) - if err != nil { - return -1, -1, -1, err - } - return major, minor, patch, err -} - -// Check for minimum server requirement. -func serverMinVersion(version string, major, minor, patch int) bool { - smajor, sminor, spatch, _ := versionComponents(version) - if smajor < major || (smajor == major && sminor < minor) || (smajor == major && sminor == minor && spatch < patch) { - return false - } - return true -} - func outPutMSGBodyCompact(data []byte, filter string, subject string, stream string) (string, error) { if len(data) == 0 { fmt.Println("nil body") @@ -1250,85 +1193,7 @@ func filterDataThroughCmd(data []byte, filter, subject, stream string) ([]byte, return runner.CombinedOutput() } -// copied from choria-io/appbuilder -func barGraph(w io.Writer, data map[string]float64, caption string, width int, bytes bool) error { - longest := 0 - min := math.MaxFloat64 - max := -math.MaxFloat64 - keys := []string{} - for k, v := range data { - keys = append(keys, k) - if len(k) > longest { - longest = len(k) - } - - if v < min { - min = v - } - - if v > max { - max = v - } - } - - sort.Slice(keys, func(i, j int) bool { - return data[keys[i]] < data[keys[j]] - }) - - if caption != "" { - fmt.Fprintln(w, caption) - fmt.Fprintln(w) - } - - var steps float64 - if max == min { - steps = max / float64(width) - } else { - steps = (max - min) / float64(width) - } - - longestLine := 0 - for _, k := range keys { - v := data[k] - - var blocks int - switch { - case v == 0: - // value 0 is always 0 - blocks = 0 - case len(keys) == 1: - // one entry, so we show full width - blocks = width - case min == max: - // all entries have same value, so we show full width - blocks = width - default: - blocks = int((v - min) / steps) - } - - var h string - if bytes { - h = humanize.IBytes(uint64(v)) - } else { - h = humanize.Commaf(v) - } - - bar := strings.Repeat("█", blocks) - if blocks == 0 { - bar = "▏" - } - - line := fmt.Sprintf("%s%s: %s (%s)", strings.Repeat(" ", longest-len(k)+2), k, bar, h) - if len(line) > longestLine { - longestLine = len(line) - } - - fmt.Fprintln(w, line) - } - - return nil -} - +// currentActiveServers determines how many servers the connected server knows about func currentActiveServers(nc *nats.Conn) (int, error) { var expect int @@ -1345,26 +1210,3 @@ func currentActiveServers(nc *nats.Conn) (int, error) { return expect, err } - -// clearScreen tries to ensure resetting original state of screen, todo windows -func clearScreen() { - fmt.Print("\033[2J") - fmt.Print("\033[H") -} - -func sortMultiSort[V constraints.Ordered, S string | constraints.Ordered](i1 V, j1 V, i2 S, j2 S) bool { - if i1 == j1 { - return i2 < j2 - } - - return i1 > j1 -} - -func mapKeys[M ~map[K]V, K comparable, V any](m M) []K { - r := make([]K, 0, len(m)) - for k := range m { - r = append(r, k) - } - - return r -} diff --git a/cli/util_test.go b/cli/util_test.go index 115e9efb..3a25cce9 100644 --- a/cli/util_test.go +++ b/cli/util_test.go @@ -166,21 +166,3 @@ func TestHostnameCompactor(t *testing.T) { t.Fatalf("Recevied %#v", result) } } - -func TestMultipleSort(t *testing.T) { - if sortMultiSort(1, 1, "b", "a") { - t.Fatalf("expected true") - } - - if !sortMultiSort(1, 1, "a", "b") { - t.Fatalf("expected false") - } - - if sortMultiSort(1, 2, "a", "b") { - t.Fatalf("expected false") - } - - if !sortMultiSort(2, 1, "a", "b") { - t.Fatalf("expected true") - } -} diff --git a/go.mod b/go.mod index 91aeb50d..f2842b0b 100644 --- a/go.mod +++ b/go.mod @@ -20,9 +20,9 @@ require ( github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/klauspost/compress v1.17.9 github.com/mattn/go-isatty v0.0.20 - github.com/nats-io/jsm.go v0.1.1-0.20240909112155-c79a81abfae8 + github.com/nats-io/jsm.go v0.1.1-0.20240909124020-543abd828bd1 github.com/nats-io/jwt/v2 v2.5.8 - github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20240907185554-7f92c34c1415 + github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20240909173510-a07bde9fa7d4 github.com/nats-io/nats.go v1.37.0 github.com/nats-io/nkeys v0.4.7 github.com/nats-io/nuid v1.0.1 @@ -63,6 +63,7 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.6.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect diff --git a/go.sum b/go.sum index 92971577..38484661 100644 --- a/go.sum +++ b/go.sum @@ -106,12 +106,14 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nats-io/jsm.go v0.1.1-0.20240909112155-c79a81abfae8 h1:n7c7Sjo8bfYTsQhRuEa5uFsKl8mnw+yaWNQ1EVP4/7o= -github.com/nats-io/jsm.go v0.1.1-0.20240909112155-c79a81abfae8/go.mod h1:qarKt1X8221zgCOg+JcjkH1/i7+p3HQFRWNYv1lk3dI= +github.com/nats-io/jsm.go v0.1.1-0.20240909124020-543abd828bd1 h1:2V6TS5DnWPjmlkdMeGlA9zGlOUNtCEKo3UakdUkMgRY= +github.com/nats-io/jsm.go v0.1.1-0.20240909124020-543abd828bd1/go.mod h1:qarKt1X8221zgCOg+JcjkH1/i7+p3HQFRWNYv1lk3dI= github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE= github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A= github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20240907185554-7f92c34c1415 h1:gGXxOszQols/MkhpSc3Ec2RfP4T+aW0EBZkOinsHJ9M= github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20240907185554-7f92c34c1415/go.mod h1:odNGaRFETFJYsviPk2grICKGgoObmMjgIBDwny/uVrc= +github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20240909173510-a07bde9fa7d4 h1:4ZtCJK+tZMXPcyzFGNWNkJSY1/twBs15DHwMFp7BHWw= +github.com/nats-io/nats-server/v2 v2.11.0-dev.0.20240909173510-a07bde9fa7d4/go.mod h1:o07K/z9ovs02pREUUNWwGGuIYqL110VKnjbjHp+/TjE= github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE= github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= @@ -158,6 +160,8 @@ github.com/synadia-io/jwt-auth-builder.go v0.0.0-20240628155003-21e8d1e9d490/go. github.com/tylertreat/hdrhistogram-writer v0.0.0-20210816161836-2e440612a39f h1:SGznmvCovewbaSgBsHgdThtWsLj5aCLX/3ZXMLd1UD0= github.com/tylertreat/hdrhistogram-writer v0.0.0-20210816161836-2e440612a39f/go.mod h1:IY84XkhrEJTdHYLNy/zObs8mXuUAp9I65VyarbPSCCY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= diff --git a/internal/util/config.go b/internal/util/config.go index 5fceba0a..fbcf8855 100644 --- a/internal/util/config.go +++ b/internal/util/config.go @@ -17,6 +17,7 @@ import ( "encoding/json" "fmt" "os" + "os/user" "path/filepath" ) @@ -68,3 +69,57 @@ func SaveConfig(cfg *Config) error { return nil } + +// XdgShareHome is where to store data like nsc stored +func XdgShareHome() (string, error) { + parent := os.Getenv("XDG_DATA_HOME") + if parent != "" { + return parent, nil + } + + u, err := user.Current() + if err != nil { + return "", err + } + + if u.HomeDir == "" { + return "", fmt.Errorf("cannot determine home directory") + } + + return filepath.Join(u.HomeDir, ".local", "share"), nil +} + +// ConfigDir is the directory holding configuration files +func ConfigDir() (string, error) { + parent, err := ParentDir() + if err != nil { + return "", err + } + + dir := filepath.Join(parent, "nats", "cli") + err = os.MkdirAll(dir, 0700) + if err != nil { + return "", err + } + + return dir, nil +} + +// ParentDir is the parent, controlled by XDG_CONFIG_HOME, for any configuration +func ParentDir() (string, error) { + parent := os.Getenv("XDG_CONFIG_HOME") + if parent != "" { + return parent, nil + } + + u, err := user.Current() + if err != nil { + return "", err + } + + if u.HomeDir == "" { + return "", fmt.Errorf("cannot determine home directory") + } + + return filepath.Join(u.HomeDir, parent, ".config"), nil +} diff --git a/internal/util/util.go b/internal/util/util.go index 3c0a4f58..10aa5a35 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -18,18 +18,22 @@ import ( "encoding/json" "errors" "fmt" + "io" + "math" "os" - "os/user" - "path/filepath" "reflect" "regexp" + "sort" "strconv" "strings" "github.com/AlecAivazis/survey/v2" + "github.com/dustin/go-humanize" + "github.com/guptarohit/asciigraph" "github.com/nats-io/jsm.go" "github.com/nats-io/nats.go" "github.com/nats-io/natscli/options" + "golang.org/x/exp/constraints" "github.com/nats-io/nkeys" terminal "golang.org/x/term" @@ -99,60 +103,6 @@ func ServerMinVersion(nc *nats.Conn, major, minor, patch int) bool { return true } -// XdgShareHome is where to store data like nsc stored -func XdgShareHome() (string, error) { - parent := os.Getenv("XDG_DATA_HOME") - if parent != "" { - return parent, nil - } - - u, err := user.Current() - if err != nil { - return "", err - } - - if u.HomeDir == "" { - return "", fmt.Errorf("cannot determine home directory") - } - - return filepath.Join(u.HomeDir, ".local", "share"), nil -} - -// ConfigDir is the directory holding configuration files -func ConfigDir() (string, error) { - parent, err := ParentDir() - if err != nil { - return "", err - } - - dir := filepath.Join(parent, "nats", "cli") - err = os.MkdirAll(dir, 0700) - if err != nil { - return "", err - } - - return dir, nil -} - -// ParentDir is the parent, controlled by XDG_CONFIG_HOME, for any configuration -func ParentDir() (string, error) { - parent := os.Getenv("XDG_CONFIG_HOME") - if parent != "" { - return parent, nil - } - - u, err := user.Current() - if err != nil { - return "", err - } - - if u.HomeDir == "" { - return "", fmt.Errorf("cannot determine home directory") - } - - return filepath.Join(u.HomeDir, parent, ".config"), nil -} - // ToJSON converts any to json string func ToJSON(d any) (string, error) { j, err := json.MarshalIndent(d, "", " ") @@ -351,3 +301,127 @@ func SurveyColors() []survey.AskOpt { }), } } + +func SortMultiSort[V constraints.Ordered, S string | constraints.Ordered](i1 V, j1 V, i2 S, j2 S) bool { + if i1 == j1 { + return i2 < j2 + } + + return i1 > j1 +} + +// MapKeys extracts the keys from a map +func MapKeys[M ~map[K]V, K comparable, V any](m M) []K { + r := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) + } + + return r +} + +// ClearScreen tries to ensure resetting original state of screen +func ClearScreen() { + asciigraph.Clear() +} + +// BarGraph generates a bar group based on data, copied from choria-io/appbuilder +func BarGraph(w io.Writer, data map[string]float64, caption string, width int, bytes bool) error { + longest := 0 + minVal := math.MaxFloat64 + maxVal := -math.MaxFloat64 + keys := []string{} + for k, v := range data { + keys = append(keys, k) + if len(k) > longest { + longest = len(k) + } + + if v < minVal { + minVal = v + } + + if v > maxVal { + maxVal = v + } + } + + sort.Slice(keys, func(i, j int) bool { + return data[keys[i]] < data[keys[j]] + }) + + if caption != "" { + fmt.Fprintln(w, caption) + fmt.Fprintln(w) + } + + var steps float64 + if maxVal == minVal { + steps = maxVal / float64(width) + } else { + steps = (maxVal - minVal) / float64(width) + } + + longestLine := 0 + for _, k := range keys { + v := data[k] + + var blocks int + switch { + case v == 0: + // value 0 is always 0 + blocks = 0 + case len(keys) == 1: + // one entry, so we show full width + blocks = width + case minVal == maxVal: + // all entries have same value, so we show full width + blocks = width + default: + blocks = int((v - minVal) / steps) + } + + var h string + if bytes { + h = humanize.IBytes(uint64(v)) + } else { + h = humanize.Commaf(v) + } + + bar := strings.Repeat("█", blocks) + if blocks == 0 { + bar = "▏" + } + + line := fmt.Sprintf("%s%s: %s (%s)", strings.Repeat(" ", longest-len(k)+2), k, bar, h) + if len(line) > longestLine { + longestLine = len(line) + } + + fmt.Fprintln(w, line) + } + + return nil +} + +// ProgressWidth calculates progress bar width for uiprogress: +// +// if it cant figure out the width, assume 80 +// if the width is too small, set it to minWidth and just live with the overflow +// +// this ensures a reasonable progress size, ideally we should switch over +// to a spinner for < minWidth rather than cause overflows, but thats for later. +func ProgressWidth() int { + w, _, err := terminal.GetSize(int(os.Stdout.Fd())) + if err != nil { + return 80 + } + + minWidth := 10 + + if w-30 <= minWidth { + return minWidth + } else { + return w - 30 + } +} diff --git a/internal/util/util_test.go b/internal/util/util_test.go new file mode 100644 index 00000000..24c02db1 --- /dev/null +++ b/internal/util/util_test.go @@ -0,0 +1,36 @@ +// Copyright 2024 The NATS Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "testing" +) + +func TestMultipleSort(t *testing.T) { + if SortMultiSort(1, 1, "b", "a") { + t.Fatalf("expected true") + } + + if !SortMultiSort(1, 1, "a", "b") { + t.Fatalf("expected false") + } + + if SortMultiSort(1, 2, "a", "b") { + t.Fatalf("expected false") + } + + if !SortMultiSort(2, 1, "a", "b") { + t.Fatalf("expected true") + } +}