diff --git a/pkg/es/api.go b/pkg/es/api.go index 63a6346..9f11735 100644 --- a/pkg/es/api.go +++ b/pkg/es/api.go @@ -328,8 +328,8 @@ func ApiList(conf *cfg.Config, pattern string) error { filter := regexp.MustCompile(pattern) - table := printer.NewTable(conf, 4, 0) - table.Addheaders("path", "http verb", "tag", "description") + table := printer.NewTable(conf).WithSize(4, 0). + WithHeaders("path", "http verb", "tag", "description") for path, item := range assets.OpenAPI.Spec().Paths.Paths { if pattern != "" { diff --git a/pkg/es/ccr.go b/pkg/es/ccr.go index 671f724..cc42992 100644 --- a/pkg/es/ccr.go +++ b/pkg/es/ccr.go @@ -111,8 +111,8 @@ func CcrRemoteInfo(conf *cfg.Config, index string) error { mode = "leader" } - table := printer.NewTable(conf, 2, 5) - table.Addheaders("ccr remote property", "value") + table := printer.NewTable(conf).WithSize(2, 5). + WithHeaders("ccr remote property", "value") table.Entries = [][]any{ {"Remote Cluster", remote}, diff --git a/pkg/es/ccr_follower.go b/pkg/es/ccr_follower.go index feb8f9e..44901c5 100644 --- a/pkg/es/ccr_follower.go +++ b/pkg/es/ccr_follower.go @@ -164,8 +164,8 @@ func CcrFollowerShow(conf *cfg.Config, index string) error { follower := res.Indices[0].Shards[0] - table := printer.NewTable(conf, 2, 9) - table.Addheaders("ccr follower property", "value") + table := printer.NewTable(conf).WithSize(2, 9). + WithHeaders("ccr follower property", "value") table.Entries = [][]any{ {"name", index}, diff --git a/pkg/es/cluster.go b/pkg/es/cluster.go index a7af1d6..f0d2e56 100644 --- a/pkg/es/cluster.go +++ b/pkg/es/cluster.go @@ -58,8 +58,8 @@ func ClusterList(conf *cfg.Config) error { wg.Wait() - table := printer.NewTable(conf, 5, len(conf.Clusters)) - table.Addheaders("cluster", "uri", "reachable", "current", "error") + table := printer.NewTable(conf).WithSize(5, len(conf.Clusters)). + WithHeaders("cluster", "uri", "reachable", "current", "error") idx := 0 @@ -212,8 +212,8 @@ func ClusterStatus(conf *cfg.Config) error { } } - table := printer.NewTable(conf, 2, 7) - table.Addheaders(conf.DefaultCluster.Name, "status") + table := printer.NewTable(conf).WithSize(2, 7). + WithHeaders(conf.DefaultCluster.Name, "status") table.Entries = [][]any{ {"Cluster Name", res.health.ClusterName}, @@ -291,7 +291,7 @@ func gatherClusterStats(clusterstats *clusterstats.Response, table *printer.Tabl {"indices", clusterstats.Indices.Count}, {"Docs", clusterstats.Indices.Docs.Count}, {"Total Size", printer.Bytes(clusterstats.Indices.Docs.TotalSizeInBytes)}, - {"Total Queries", "%d", querycount}, + {"Total Queries", querycount}, {"Shards Primaries", clusterstats.Indices.Shards.Primaries}, {"Shards Total", clusterstats.Indices.Shards.Total}, {"Storage", fmt.Sprintf( diff --git a/pkg/es/cluster_settings.go b/pkg/es/cluster_settings.go index a4e85d4..c82a495 100644 --- a/pkg/es/cluster_settings.go +++ b/pkg/es/cluster_settings.go @@ -38,8 +38,8 @@ func ClusterSettingsList(conf *cfg.Config) error { return fmt.Errorf("failed to get cluster settings: %w", esErrorString(err)) } - table := printer.NewTable(conf, 2, 0) - table.Addheaders("setting", "value") + table := printer.NewTable(conf).WithSize(2, 0). + WithHeaders("setting", "value") entries := [][]any{} diff --git a/pkg/es/cluster_util.go b/pkg/es/cluster_util.go index 67fabcb..6fe7013 100644 --- a/pkg/es/cluster_util.go +++ b/pkg/es/cluster_util.go @@ -71,9 +71,9 @@ func checkClusterStatus(conf *cfg.Config, leader, follower string) bool { status[cluster] = clusterHealth } - table := printer.NewTable(conf, 3, 6) + table := printer.NewTable(conf).WithSize(3, 6). - table.Addheaders("setting", "leader:"+leader, "follower:"+follower) + WithHeaders("setting", "leader:"+leader, "follower:"+follower) table.Entries = [][]any{ {"Cluster Name", status[leader].ClusterName, status[follower].ClusterName}, @@ -151,8 +151,8 @@ func findIlmErrors(conf *cfg.Config, leader, follower string) bool { if len(failed[cluster]) > 0 { idx := 0 - table := printer.NewTable(conf, 2, len(failed[cluster])) - table.Addheaders("ilm errors on "+which, "errors") + table := printer.NewTable(conf).WithSize(2, len(failed[cluster])). + WithHeaders("ilm errors on "+which, "errors") for name, count := range failed[cluster] { table.Entries[idx] = []any{name, count} @@ -225,8 +225,8 @@ func findIndicesOnlyOnLeader(conf *cfg.Config, indices ClusterIndices, leader, f } idx := 0 - table := printer.NewTable(conf, 3, len(indexOnlyOnLeader)) - table.Addheaders("index only on leader", "size", "docscount") + table := printer.NewTable(conf).WithSize(3, len(indexOnlyOnLeader)). + WithHeaders("index only on leader", "size", "docscount") for name, index := range indexOnlyOnLeader { name := printer.Colorize(conf, "red", name) @@ -275,8 +275,8 @@ func findOrphanedIndices(conf *cfg.Config, indices ClusterIndices, leader, follo } idx := 0 - table := printer.NewTable(conf, 3, len(orphaned)) - table.Addheaders("orphaned index on follower", "size", "docscount") + table := printer.NewTable(conf).WithSize(3, len(orphaned)). + WithHeaders("orphaned index on follower", "size", "docscount") for name, index := range orphaned { name := printer.Colorize(conf, "red", name) @@ -311,8 +311,8 @@ func findFailedFollowerIndices(conf *cfg.Config, indices ClusterIndices, followe } idx := 0 - table := printer.NewTable(conf, 3, len(red)) - table.Addheaders("red index on follower", "size", "docscount") + table := printer.NewTable(conf).WithSize(3, len(red)). + WithHeaders("red index on follower", "size", "docscount") for name, index := range red { name := printer.Colorize(conf, "red", name) diff --git a/pkg/es/datastream.go b/pkg/es/datastream.go index 42f8339..bd88f0c 100644 --- a/pkg/es/datastream.go +++ b/pkg/es/datastream.go @@ -62,8 +62,8 @@ func DatastreamList(conf *cfg.Config) error { } } - table := printer.NewTable(conf, 7, size) - table.Addheaders("name", "ilm policy", "hidden", "system", "replicated", "generation", "timestamp field") + table := printer.NewTable(conf).WithSize(7, size). + WithHeaders("name", "ilm policy", "hidden", "system", "replicated", "generation", "timestamp field") for idx, datastream := range list { name := printer.Colorize(conf, datastream.Status.String(), datastream.Name) @@ -146,8 +146,8 @@ func DatastreamShow(conf *cfg.Config, dsname string) error { return fmt.Errorf("failed to get data stream stats: %w", esErrorString(err)) } - table := printer.NewTable(conf, 11, 2) - table.Addheaders("data stream property", "value") + table := printer.NewTable(conf).WithSize(11, 2). + WithHeaders("data stream property", "value") datastream := res.DataStreams[0] @@ -176,8 +176,8 @@ func DatastreamShow(conf *cfg.Config, dsname string) error { return err } - table = printer.NewTable(conf, 5, len(datastream.Indices)) - table.Addheaders("backing index name", "uuid", "prefer ilm", "ilm policy", "managed by") + table = printer.NewTable(conf).WithSize(5, len(datastream.Indices)). + WithHeaders("backing index name", "uuid", "prefer ilm", "ilm policy", "managed by") for idx, index := range datastream.Indices { policy := "" @@ -233,8 +233,8 @@ func DatastreamRollover(conf *cfg.Config, ds string) error { return fmt.Errorf("failed to rollover data stream: %w", esErrorString(err)) } - table := printer.NewTable(conf, 2, 5) - table.Addheaders("rollover response", "value") + table := printer.NewTable(conf).WithSize(2, 5). + WithHeaders("rollover response", "value") table.Entries = [][]any{ {"acknowledged", res.Acknowledged}, {"rolled over", res.RolledOver}, diff --git a/pkg/es/ilm.go b/pkg/es/ilm.go index b87597e..f374a66 100644 --- a/pkg/es/ilm.go +++ b/pkg/es/ilm.go @@ -85,8 +85,8 @@ func IlmList(conf *cfg.Config, pattern string) error { repr.Println(res) } - table := printer.NewTable(conf, 5, 0) - table.Addheaders("ilm policy", "hot", "warm", "frozen", "delete") + table := printer.NewTable(conf).WithSize(5, 0). + WithHeaders("ilm policy", "hot", "warm", "frozen", "delete") for name, ilm := range res { table.AddRow(name, @@ -123,8 +123,8 @@ func IlmShow(conf *cfg.Config, policy string) error { return IlmShowTree(conf, ilm.Policy) } - table := printer.NewTable(conf, 2, 5) - table.Addheaders("ilm policy setting", "value") + table := printer.NewTable(conf).WithSize(2, 5). + WithHeaders("ilm policy setting", "value") table.Entries = [][]any{ {"policy", policy}, @@ -142,9 +142,6 @@ func IlmShow(conf *cfg.Config, policy string) error { func IlmShowTree(conf *cfg.Config, ilm types.IlmPolicy) error { indent := "" - table := printer.NewTable(conf, 4, 0) - table.Addheaders("phase", "min age", "min size", "snapshot repo") - for _, phase := range IlmPhaseOrder { if phase == "hot" { fmt.Printf("%s%s phase:\n%s rollover after %s\n", @@ -280,8 +277,8 @@ func IlmExplain(conf *cfg.Config, index string) error { ilm := explain.(*types.LifecycleExplainManaged) - table := printer.NewTable(conf, 2, 0) - table.Addheaders("ilm status field", "value") + table := printer.NewTable(conf).WithSize(2, 0). + WithHeaders("ilm status field", "value") info := "" diff --git a/pkg/es/ilm_forecast.go b/pkg/es/ilm_forecast.go index be7c19f..0b79a61 100644 --- a/pkg/es/ilm_forecast.go +++ b/pkg/es/ilm_forecast.go @@ -105,8 +105,8 @@ func IlmForecastList(conf *cfg.Config, filter string) error { headers = append(headers, "ilm policy") } - table := printer.NewTable(conf, len(headers), 0) - table.Addheaders(headers...) + table := printer.NewTable(conf).WithSize(len(headers), 0). + WithHeaders(headers...) for _, phase := range phaseData { if filter != "" && !flt.MatchString(phase.index) { diff --git a/pkg/es/index.go b/pkg/es/index.go index 6aca80c..456e4c7 100644 --- a/pkg/es/index.go +++ b/pkg/es/index.go @@ -104,8 +104,8 @@ func IndexList(conf *cfg.Config) error { } } - table := printer.NewTable(conf, 3, size) - table.Addheaders("name", "size", "docscount") + table := printer.NewTable(conf).WithSize(3, size). + WithHeaders("name", "size", "docscount") for idx, index := range list { name := printer.Colorize(conf, *index.Health, *index.Index) @@ -140,8 +140,8 @@ func IndexShow(conf *cfg.Config, indexpattern string) error { idx++ } - table := printer.NewTable(conf, 2, 7) - table.Addheaders("index property", "value") + table := printer.NewTable(conf).WithSize(2, 7). + WithHeaders("index property", "value") ts, err := strconv.ParseInt(index.Settings.Index.CreationDate.(string), 10, 64) if err != nil { @@ -263,8 +263,8 @@ func IndexFields(conf *cfg.Config, index string) error { return fmt.Errorf("failed to retrieve field capabilties: %w", esErrorString(err)) } - table := printer.NewTable(conf, 5, 0) - table.Addheaders("field", "type", "searchable", "aggretable", "metadata") + table := printer.NewTable(conf).WithSize(5, 0). + WithHeaders("field", "type", "searchable", "aggretable", "metadata") idx := 0 @@ -336,7 +336,7 @@ func IndexDiskusage(conf *cfg.Config, index string) error { return errors.New("no disk usage reported for index") } - table := printer.NewTableEmpty(conf). + table := printer.NewTable(conf). WithHeaders("field", "bloom filter", "norms", "points", "term vectors", "knn vectors", "total") for name, field := range diskusage.Fields { diff --git a/pkg/es/index_alias.go b/pkg/es/index_alias.go index 1b5a8cb..8864f60 100644 --- a/pkg/es/index_alias.go +++ b/pkg/es/index_alias.go @@ -72,8 +72,8 @@ func IndexAliasList(conf *cfg.Config) error { } } - table := printer.NewTable(conf, 2, len(aliaslist)) - table.Addheaders("index", "alias") + table := printer.NewTable(conf).WithSize(2, len(aliaslist)). + WithHeaders("index", "alias") idx := 0 for index, aliases := range aliaslist { @@ -113,8 +113,8 @@ func IndexAliasRollover(conf *cfg.Config, alias string) error { return fmt.Errorf("failed to rollover index alias: %w", esErrorString(err)) } - table := printer.NewTable(conf, 2, 5) - table.Addheaders("rollover response", "value") + table := printer.NewTable(conf).WithSize(2, 5). + WithHeaders("rollover response", "value") table.Entries = [][]any{ {"acknowledged", res.Acknowledged}, {"rolled over", res.RolledOver}, diff --git a/pkg/es/index_copy.go b/pkg/es/index_copy.go index 63c5db6..36b396f 100644 --- a/pkg/es/index_copy.go +++ b/pkg/es/index_copy.go @@ -62,7 +62,7 @@ func IndexCopy(conf *cfg.Config) error { return fmt.Errorf("failed to copy indices: %w", esErrorString(err)) } - table := printer.NewTable(conf, 2, 0). + table := printer.NewTable(conf).WithSize(2, 0). WithHeaders("Reindex metrtic", "value") table.Entries = [][]any{ diff --git a/pkg/es/index_template.go b/pkg/es/index_template.go index 49f6252..3e5142e 100644 --- a/pkg/es/index_template.go +++ b/pkg/es/index_template.go @@ -59,8 +59,8 @@ func IndexTemplateList(conf *cfg.Config, filter string) error { slog.Debug("res", "index templates", res) - table := printer.NewTable(conf, 5, 0) - table.Addheaders("name", "description", "index patterns", "priority") + table := printer.NewTable(conf).WithSize(5, 0). + WithHeaders("name", "description", "index patterns", "priority") tplList := filterIndexTemplates(conf, filter, res.IndexTemplates) @@ -120,8 +120,8 @@ func IndexTemplateShow(conf *cfg.Config, tplname string) error { tpl := res.IndexTemplates[0] - table := printer.NewTable(conf, 2, 6) - table.Addheaders("index template property", "value") + table := printer.NewTable(conf).WithSize(2, 6). + WithHeaders("index template property", "value") desc, err := json.Marshal(tpl.IndexTemplate.Meta_["description"]) if err != nil { @@ -148,8 +148,8 @@ func IndexTemplateShow(conf *cfg.Config, tplname string) error { return err } - table = printer.NewTable(conf, 2, 0) - table.Addheaders("index setting property", "value") + table = printer.NewTable(conf).WithSize(2, 0). + WithHeaders("index setting property", "value") err = getIndexTemplateSettings(conf, tplname, table) if err != nil { @@ -162,8 +162,8 @@ func IndexTemplateShow(conf *cfg.Config, tplname string) error { return err } - table = printer.NewTable(conf, 2, 0) - table.Addheaders("index field mapping", "type") + table = printer.NewTable(conf).WithSize(2, 0). + WithHeaders("index field mapping", "type") if tpl.IndexTemplate.Template.Mappings != nil { for name, field := range tpl.IndexTemplate.Template.Mappings.Properties { @@ -441,8 +441,8 @@ func rolloverAliasIndexTemplate(conf *cfg.Config, name string) error { return nil } - table := printer.NewTable(conf, 4, 0) - table.Addheaders("rollover alias", "status", "ack", "new index") + table := printer.NewTable(conf).WithSize(4, 0). + WithHeaders("rollover alias", "status", "ack", "new index") // apply rollover to all matching aliases, if any for alias := range aliases { diff --git a/pkg/es/license.go b/pkg/es/license.go index 6f5b6c4..15a9c3e 100644 --- a/pkg/es/license.go +++ b/pkg/es/license.go @@ -35,7 +35,7 @@ func LicenseShow(conf *cfg.Config) error { slog.Debug("license show", "license", res) - table := printer.NewTableEmpty(conf).WithHeaders("license setting", "value") + table := printer.NewTable(conf).WithHeaders("license setting", "value") lic := res.License diff --git a/pkg/es/node.go b/pkg/es/node.go index a0c6e8c..74c35ee 100644 --- a/pkg/es/node.go +++ b/pkg/es/node.go @@ -37,7 +37,7 @@ func NodeList(conf *cfg.Config) error { slog.Debug("ES result", "nodes", nodes) - table := printer.NewTableEmpty(conf).WithHeaders( + table := printer.NewTable(conf).WithHeaders( "name", "ip", "load1m", "load5m", "load15m", "ram %", "heap %") for _, node := range nodes { @@ -104,8 +104,8 @@ func NodeShow(conf *cfg.Config, nodename string) error { for id, info := range res.Nodes { stat := stats.Nodes[id] - table := printer.NewTable(conf, 2, 0) - table.Addheaders(nodename+" property", "value") + table := printer.NewTable(conf).WithSize(2, 0). + WithHeaders(nodename+" property", "value") roles := make([]string, len(info.Roles)) for idx, role := range info.Roles { @@ -197,8 +197,8 @@ func NodeClients(conf *cfg.Config, nodename string) error { slog.Debug("ES result", "stat", stats) - table := printer.NewTable(conf, 5, 0) - table.Addheaders("agent", "id", "when", "from host", "url") + table := printer.NewTable(conf).WithSize(5, 0). + WithHeaders("agent", "id", "when", "from host", "url") for _, stat := range stats.Nodes { for _, client := range stat.Http.Clients { @@ -250,7 +250,7 @@ func NodeUsage(conf *cfg.Config, nodeid string) error { slog.Debug("ES result", "usage", stats) - table := printer.NewTableEmpty(conf).WithHeaders( + table := printer.NewTable(conf).WithHeaders( "node", "bulk", "doc get", diff --git a/pkg/es/role.go b/pkg/es/role.go index 3665940..89cc5bc 100644 --- a/pkg/es/role.go +++ b/pkg/es/role.go @@ -47,8 +47,8 @@ func RoleList(conf *cfg.Config) error { slog.Debug("ES result", "roles", res) - table := printer.NewTable(conf, 3, len(res)) - table.Addheaders("role", "index roles", "cluster roles") + table := printer.NewTable(conf).WithSize(3, len(res)). + WithHeaders("role", "index roles", "cluster roles") idx := 0 for name, role := range res { @@ -126,8 +126,8 @@ func roleRemoteClusters(conf *cfg.Config, role types.Role) error { return nil } - table := printer.NewTable(conf, 2, len(role.RemoteCluster)) - table.Addheaders("remote cluster", "privilege") + table := printer.NewTable(conf).WithSize(2, len(role.RemoteCluster)). + WithHeaders("remote cluster", "privilege") idx := 0 @@ -152,8 +152,8 @@ func roleClusters(conf *cfg.Config, role types.Role) error { return nil } - table := printer.NewTable(conf, 1, len(role.Cluster)) - table.Addheaders("cluster rights") + table := printer.NewTable(conf).WithSize(1, len(role.Cluster)). + WithHeaders("cluster rights") idx := 0 for _, cluster := range role.Cluster { @@ -171,8 +171,8 @@ func roleRemoteIndices(conf *cfg.Config, role types.Role) error { return nil } - table := printer.NewTable(conf, 3, len(role.RemoteIndices)) - table.Addheaders("remote index names", "index permissions", "allow restricted") + table := printer.NewTable(conf).WithSize(3, len(role.RemoteIndices)). + WithHeaders("remote index names", "index permissions", "allow restricted") idx := 0 @@ -197,8 +197,8 @@ func roleIndices(conf *cfg.Config, role types.Role) error { return nil } - table := printer.NewTable(conf, 3, len(role.Indices)) - table.Addheaders("index names", "index permissions", "allow restricted") + table := printer.NewTable(conf).WithSize(3, len(role.Indices)). + WithHeaders("index names", "index permissions", "allow restricted") idx := 0 @@ -223,8 +223,8 @@ func roleApplications(conf *cfg.Config, role types.Role) error { return nil } - table := printer.NewTable(conf, 3, len(role.Applications)) - table.Addheaders("application", "privileges", "resources") + table := printer.NewTable(conf).WithSize(3, len(role.Applications)). + WithHeaders("application", "privileges", "resources") idx := 0 for _, priv := range role.Applications { diff --git a/pkg/es/role_diff.go b/pkg/es/role_diff.go index 1f16588..77edd75 100644 --- a/pkg/es/role_diff.go +++ b/pkg/es/role_diff.go @@ -216,8 +216,8 @@ func RoleDiff(conf *cfg.Config, csvfile, role string) error { rows := diffRoles(conf, records, res) - table := printer.NewTable(conf, 3, len(rows)) - table.Addheaders("role", "is deployed", "is defined") + table := printer.NewTable(conf).WithSize(3, len(rows)). + WithHeaders("role", "is deployed", "is defined") for idx, row := range rows { deployed := printer.Colorize(conf, "green", "deployed") diff --git a/pkg/es/searchql.go b/pkg/es/searchql.go index d667558..552ce5a 100644 --- a/pkg/es/searchql.go +++ b/pkg/es/searchql.go @@ -64,14 +64,13 @@ func SearchQL(conf *cfg.Config, querystring string) error { return nil } - table := printer.NewTable(conf, len(qlResult.Columns), len(qlResult.Values)) - headers := make([]string, len(qlResult.Columns)) for idx, col := range qlResult.Columns { headers[idx] = col.Name } - table.Addheaders(headers...) + table := printer.NewTable(conf).WithSize(len(qlResult.Columns), len(qlResult.Values)). + WithHeaders(headers...) table.Entries = qlResult.Values diff --git a/pkg/es/shard.go b/pkg/es/shard.go index 8052add..4087333 100644 --- a/pkg/es/shard.go +++ b/pkg/es/shard.go @@ -100,8 +100,8 @@ func printShards(conf *cfg.Config, shardlist shards.Response) error { headers = append(headers, "node", "ip") } - table := printer.NewTable(conf, len(headers), len(shardlist)) - table.Addheaders(headers...) + table := printer.NewTable(conf).WithSize(len(headers), len(shardlist)). + WithHeaders(headers...) for idx, shard := range shardlist { name := colorzizeShard(conf, *shard.State, *shard.Index) @@ -163,8 +163,8 @@ func ShardAllocation(conf *cfg.Config, index string) error { slog.Debug("ES result", "explain", res) - table := printer.NewTable(conf, 2, 10) - table.Addheaders("shard allocation setting", "value") + table := printer.NewTable(conf).WithSize(2, 10). + WithHeaders("shard allocation setting", "value") table.Entries = [][]any{ {"Index", index}, diff --git a/pkg/es/snapshot.go b/pkg/es/snapshot.go index 4f13e49..3dab5bb 100644 --- a/pkg/es/snapshot.go +++ b/pkg/es/snapshot.go @@ -84,8 +84,8 @@ func SnapshotList(conf *cfg.Config) error { } } - table := printer.NewTable(conf, 5, len(snapshots)) - table.Addheaders("name", "index", "start", "orphaned", "status") + table := printer.NewTable(conf).WithSize(5, len(snapshots)). + WithHeaders("name", "index", "start", "orphaned", "status") for idx, snap := range snapshots { table.Entries[idx] = []any{ @@ -118,8 +118,8 @@ func SnapshotShow(conf *cfg.Config, snapshot string) error { return errors.New("no snapshot retrieved") } - table := printer.NewTable(conf, 2, 17) - table.Addheaders("snapshot property", "value") + table := printer.NewTable(conf).WithSize(2, 17). + WithHeaders("snapshot property", "value") snap := res.Snapshots[0] diff --git a/pkg/es/task.go b/pkg/es/task.go index 83297c7..f92c9e6 100644 --- a/pkg/es/task.go +++ b/pkg/es/task.go @@ -36,8 +36,8 @@ func TaskList(conf *cfg.Config) error { slog.Debug("res", "tasks", res) - table := printer.NewTable(conf, 6, len(res)) - table.Addheaders("task id", "action", "start time", "run time", "node", "type") + table := printer.NewTable(conf).WithSize(6, len(res)). + WithHeaders("task id", "action", "start time", "run time", "node", "type") for idx, task := range res { var ts time.Time diff --git a/pkg/printer/builder.go b/pkg/printer/builder.go index 10197d4..0ea2725 100644 --- a/pkg/printer/builder.go +++ b/pkg/printer/builder.go @@ -1,3 +1,5 @@ +package printer + /* Copyright © 2026 Thomas von Dein @@ -14,15 +16,15 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package printer import "strings" -// a wrapper around strings.Builder for convenience +// Builder is a wrapper around strings.Builder for convenience type Builder struct { strings.Builder } func (builder *Builder) WriteStringLine(in string) { - builder.WriteString(in + "\n") + builder.WriteString(in) + builder.WriteString("\n") } diff --git a/pkg/printer/bytes.go b/pkg/printer/bytes.go index ecbeaa7..c15be53 100644 --- a/pkg/printer/bytes.go +++ b/pkg/printer/bytes.go @@ -1,3 +1,5 @@ +package printer + /* Copyright © 2026 Thomas von Dein @@ -14,10 +16,11 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package printer import "github.com/dustin/go-humanize" +// ByteSize converts uint64 bytes into human readable size +// e.g. 1500 => 1.5kb type ByteSize struct { size uint64 } diff --git a/pkg/printer/cast.go b/pkg/printer/cast.go index 3f7c077..339d14d 100644 --- a/pkg/printer/cast.go +++ b/pkg/printer/cast.go @@ -1,3 +1,5 @@ +package printer + /* Copyright © 2026 Thomas von Dein @@ -14,7 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package printer import ( "fmt" @@ -25,9 +26,11 @@ import ( "github.com/elastic/go-elasticsearch/v9/typedapi/types" ) -func any2string(in any) string { +// stringer returns the string representation of different types of +// values +func stringer(cell any) string { //nolint:gocritic - switch val := in.(type) { + switch val := cell.(type) { case string: return val case bool: @@ -54,52 +57,27 @@ func any2string(in any) string { return string(val) case nil: return "null" - case types.Percentage, types.DateTime: - return val.(string) - } + case *types.Float64: + // ignore err here, because types.Float64.MarshalJSON() never returns one + f, _ := val.MarshalJSON() - return "" + return string(f) + default: + // Caution: this may cause a panic if the [unknown] type does + // not implement fmt.Stringer. In this case add another case + // to the type switch for it above. + return cell.(fmt.Stringer).String() + } } -func (data *Table) preprocessRows() { - if data.processed { - // only do it once - return +// visibleLen returns the length of a string but only visible chars, +// w/o ansi color escapes +func visibleLen(word string) int { + if !strings.Contains(word, "\x1b") { + // no ansi escape in there, use faster method + return len(word) } - // convert entries to strings - data.rows = make([][]string, len(data.Entries)) - for rowidx, entries := range data.Entries { - data.rows[rowidx] = make([]string, len(entries)) - - for colidx, entry := range data.Entries[rowidx] { - data.rows[rowidx][colidx] = any2string(entry) - } - } - - // determine header lenght's - for idx, head := range data.Headers { - data.lenHeaders[idx] = visibleLen(head) - } - - // determine max width per column - for _, entries := range data.rows { - currentWidth := 0 - - for idx, entry := range entries { - length := visibleLen(entry) - - if data.lenHeaders[idx] < length { - if length > currentWidth+data.maxwidth { - data.lenHeaders[idx] = data.maxwidth - currentWidth - } else { - data.lenHeaders[idx] = length - } - } - - currentWidth += data.lenHeaders[idx] - } - } - - data.processed = true + // contains escapes, need to clean up before counting + return len(ansiCtrlSeq.ReplaceAllLiteralString(word, "")) } diff --git a/pkg/printer/color.go b/pkg/printer/color.go index 573d6cf..1fdac91 100644 --- a/pkg/printer/color.go +++ b/pkg/printer/color.go @@ -1,3 +1,5 @@ +package printer + /* Copyright © 2026 Thomas von Dein @@ -15,8 +17,6 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package printer - import ( "os" diff --git a/pkg/printer/doc.go b/pkg/printer/doc.go index b970dd9..a1ebc1d 100644 --- a/pkg/printer/doc.go +++ b/pkg/printer/doc.go @@ -1,3 +1,5 @@ +package printer + /* Copyright © 2026 Thomas von Dein @@ -14,7 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package printer import ( "fmt" diff --git a/pkg/printer/metrics.go b/pkg/printer/metrics.go index caf5ed5..b3539e2 100644 --- a/pkg/printer/metrics.go +++ b/pkg/printer/metrics.go @@ -1,3 +1,5 @@ +package printer + /* Copyright © 2026 Thomas von Dein @@ -14,7 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package printer import ( "fmt" diff --git a/pkg/printer/pager.go b/pkg/printer/pager.go index 5cfb023..82e226e 100644 --- a/pkg/printer/pager.go +++ b/pkg/printer/pager.go @@ -1,3 +1,5 @@ +package printer + /* Copyright © 2026 Thomas von Dein @@ -14,7 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package printer // pager setup using bubbletea // file shamlelessly copied from: diff --git a/pkg/printer/table.go b/pkg/printer/table.go index eb43416..4a40bf0 100644 --- a/pkg/printer/table.go +++ b/pkg/printer/table.go @@ -1,3 +1,5 @@ +package printer + /* Copyright © 2026 Thomas von Dein @@ -14,7 +16,6 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package printer import ( "encoding/json" @@ -28,13 +29,20 @@ import ( "gopkg.in/yaml.v3" ) -type Table struct { - Mode string // tsv, json, yaml - Headers []string - RawHeaders []string - Entries [][]any +var ( + // ansiCtrlSeq is being used to remove ANSI control sequences so + // that we can properly determine string lenght's + ansiCtrlSeq = regexp.MustCompile(`\033.[0-9;]+m`) +) - rows [][]string // representation used for printing +// Table stores tabular data for printing +type Table struct { + Mode string // tsv, json, yaml + Headers []string // colored headers + RawHeaders []string // plain string headers + Entries [][]any // rows of cells as fed in by pkg/es + + rows [][]string // representation used for printing, cells are stringified processed bool lenHeaders []int alignInts bool @@ -42,73 +50,65 @@ type Table struct { debugGoRoutines bool } -func NewTable(conf *cfg.Config, columns, rows int) *Table { - table := new(Table{ +// NewTable returns a new empty table object with unaligned storage +func NewTable(conf *cfg.Config) *Table { + return new(Table{ Mode: conf.Output, maxwidth: cfg.GetTermWidth(), debugGoRoutines: conf.DebugGoRoutines, + Headers: []string{}, + RawHeaders: []string{}, + lenHeaders: []int{}, + alignInts: conf.AlignInts, }) - - table.Headers = make([]string, columns) - table.RawHeaders = make([]string, columns) - table.Entries = make([][]any, rows) - table.lenHeaders = make([]int, columns) - table.alignInts = conf.AlignInts - - return table } -func NewTableEmpty(conf *cfg.Config) *Table { - table := new(Table{ - Mode: conf.Output, - maxwidth: cfg.GetTermWidth(), - debugGoRoutines: conf.DebugGoRoutines, - }) - table.alignInts = conf.AlignInts - - return table -} - -func (table *Table) WithHeaders(headers ...string) *Table { - count := len(headers) - - table.Entries = [][]any{} - table.lenHeaders = make([]int, count) - table.Headers = make([]string, count) - table.RawHeaders = make([]string, count) - - table.Addheaders(headers...) - - return table -} - -func (table *Table) Print() error { - var err error - - switch table.Mode { - case "json": - err = table.PrintJSON() - case "yaml": - err = table.PrintYAML() - case "csv": - err = table.PrintCSV() - default: - err = table.PrintTSV() +// WithSize configures the dimensions of the table, allocs aligned storage +func (t *Table) WithSize(columns, rows int) *Table { + if columns > 0 { + t.Headers = make([]string, columns) + t.RawHeaders = make([]string, columns) + t.lenHeaders = make([]int, columns) } - if table.debugGoRoutines { + if rows > 0 { + t.Entries = make([][]any, rows) + } + + return t +} + +// WithHeaders sets table headers +func (t *Table) WithHeaders(headers ...string) *Table { + count := len(headers) + + return t.WithSize(count, 0).formatHeaders(headers...) +} + +// Print outputs the tabular data according to Table.Mode +func (t *Table) Print() error { + var err error + + switch t.Mode { + case "json": + err = t.PrintJSON() + case "yaml": + err = t.PrintYAML() + case "csv": + err = t.PrintCSV() + default: + err = t.PrintTSV() + } + + if t.debugGoRoutines { printGoRoutineMetrics() } return err } -var ( - ansiCtrlSeq = regexp.MustCompile(`\033.[0-9;]+m`) -) - -func (table *Table) PrintYAML() error { - raw := table.toMap() +func (t *Table) PrintYAML() error { + raw := t.toMap() body, err := yaml.Marshal(raw) if err != nil { @@ -120,8 +120,8 @@ func (table *Table) PrintYAML() error { return nil } -func (table *Table) PrintJSON() error { - raw := table.toMap() +func (t *Table) PrintJSON() error { + raw := t.toMap() body, err := json.MarshalIndent(raw, "", " ") if err != nil { @@ -133,56 +133,101 @@ func (table *Table) PrintJSON() error { return nil } -func (table *Table) PrintTSV() error { - // length's, convert cell types - table.preprocessRows() +func (t *Table) PrintCSV() error { + t.preprocessRows() - // output headers - for idx, header := range table.Headers { - if idx+1 != len(table.Headers) { - fmt.Print(header, strings.Repeat(" ", table.lenHeaders[idx]-visibleLen(header))) - } else { - // no padding for last header - fmt.Print(header) + fmt.Println(strings.Join(t.RawHeaders, ",")) + + for _, entries := range t.rows { + row := make([]string, len(entries)) + + for idx, entry := range entries { + if strings.Contains(entry, " ") || strings.Contains(entry, ",") { + row[idx] = `"` + entry + `"` + } else { + row[idx] = entry + } } - if idx < len(table.Headers)-1 { - fmt.Print(" ") - } + fmt.Println(strings.Join(row, ",")) } - fmt.Println() + return nil +} - for _, entries := range table.rows { +// Sort tabular data by first column +func (t *Table) Sort() { + // sanity checks + if len(t.Entries) == 0 { + return + } + + t.preprocessRows() + + sort.Slice(t.rows, func(i, j int) bool { + return t.rows[i][0] < t.rows[j][0] + }) +} + +func (t *Table) AddRow(fields ...any) { + t.Entries = append(t.Entries, fields) +} + +func (t *Table) AddRowLate(fields ...any) { + t.AddRow(fields) + + if !t.processed { + return + } + + row := make([]string, len(fields)) + + for idx, field := range fields { + row[idx] = stringer(field) + } + + t.rows = append(t.rows, row) +} + +// PrintTSV is the default printer, it outputs in tab-separated-value format +func (t *Table) PrintTSV() error { + t.preprocessRows() + t.printTsvHeaders() + + return t.printTsvRows() +} + +func (t *Table) printTsvRows() error { + for _, entries := range t.rows { currentWidth := 0 columns := len(entries) for idx, entry := range entries { length := visibleLen(entry) - if length+currentWidth > table.maxwidth && - table.maxwidth-currentWidth > 1 && + if length+currentWidth > t.maxwidth && + t.maxwidth-currentWidth > 1 && idx == columns-1 { // text is too wide to be put into one line, and // it's the last cell, so wrap it - entry = wrap(table.maxwidth-currentWidth, currentWidth+2, entry) + entry = wrap(t.maxwidth-currentWidth, currentWidth+2, entry) } - currentWidth += table.lenHeaders[idx] + currentWidth += t.lenHeaders[idx] switch { - case isInt(entry) && table.alignInts: + case isInt(entry) && t.alignInts: // align right - fmt.Print(strings.Repeat(" ", table.lenHeaders[idx]-length), entry) - case length < table.lenHeaders[idx] && idx+1 != len(entries): + fmt.Print(strings.Repeat(" ", t.lenHeaders[idx]-length), entry) + case length < t.lenHeaders[idx] && idx+1 != len(entries): // pad right, if required - fmt.Print(entry, strings.Repeat(" ", table.lenHeaders[idx]-length)) + fmt.Print(entry, strings.Repeat(" ", t.lenHeaders[idx]-length)) default: // no padding for last entry fmt.Print(entry) } - if idx < len(table.Headers)-1 { + if idx < len(t.Headers)-1 { fmt.Print(" ") } } @@ -193,6 +238,40 @@ func (table *Table) PrintTSV() error { return nil } +// printTsvHeaders outputs TSV headers +func (t *Table) printTsvHeaders() { + for idx, header := range t.Headers { + if idx+1 != len(t.Headers) { + fmt.Print(header, strings.Repeat(" ", t.lenHeaders[idx]-visibleLen(header))) + } else { + // no padding for last header + fmt.Print(header) + } + + if idx < len(t.Headers)-1 { + fmt.Print(" ") + } + } + + fmt.Println() +} + +// formatHeaders formats header fields according to output mode +func (t *Table) formatHeaders(headers ...string) *Table { + for idx, header := range headers { + switch t.Mode { + case "json", "yaml": + t.Headers[idx] = strings.ReplaceAll(strings.ToLower(header), " ", "_") + default: + t.Headers[idx] = Bold(strings.ReplaceAll(strings.ToUpper(header), " ", "-")) + } + + t.RawHeaders[idx] = header + } + + return t +} + // Wrap a text into multiple lines, first line is not indented, all // further lines will be indented. Used within Print() to print large // cell text. @@ -235,91 +314,61 @@ func wrap(width, indent int, text string) string { return wrapped } -func (table *Table) PrintCSV() error { - table.preprocessRows() - - fmt.Println(strings.Join(table.RawHeaders, ",")) - - for _, entries := range table.rows { - row := make([]string, len(entries)) - - for idx, entry := range entries { - if strings.Contains(entry, " ") || strings.Contains(entry, ",") { - row[idx] = `"` + entry + `"` - } else { - row[idx] = entry - } - } - - fmt.Println(strings.Join(row, ",")) - } - - return nil -} - -func (table *Table) Sort() { - // sanity checks - if len(table.Entries) == 0 { - return - } - - table.preprocessRows() - - sort.Slice(table.rows, func(i, j int) bool { - return table.rows[i][0] < table.rows[j][0] - }) -} - -func (table *Table) Addheaders(headers ...string) { - for idx, header := range headers { - switch table.Mode { - case "json", "yaml": - table.Headers[idx] = strings.ReplaceAll(strings.ToLower(header), " ", "_") - default: - table.Headers[idx] = Bold(strings.ReplaceAll(strings.ToUpper(header), " ", "-")) - } - - table.RawHeaders[idx] = header - } -} - -func (table *Table) AddRow(fields ...any) { - table.Entries = append(table.Entries, fields) -} - -func (table *Table) AddRowLate(fields ...any) { - table.AddRow(fields) - - if !table.processed { - return - } - - row := make([]string, len(fields)) - - for idx, field := range fields { - row[idx] = any2string(field) - } - - table.rows = append(table.rows, row) -} - // needed for json and yaml output -func (table *Table) toMap() []map[string]any { - raw := make([]map[string]any, len(table.Entries)) +func (t *Table) toMap() []map[string]any { + raw := make([]map[string]any, len(t.Entries)) - for idx, entries := range table.Entries { - raw[idx] = make(map[string]any, len(table.Headers)) + for idx, entries := range t.Entries { + raw[idx] = make(map[string]any, len(t.Headers)) for eidx, entry := range entries { - raw[idx][table.Headers[eidx]] = entry + raw[idx][t.Headers[eidx]] = entry } } return raw } -// return the length of a string but only visible chars, w/o ansi color escapes -func visibleLen(word string) int { - return len(ansiCtrlSeq.ReplaceAllLiteralString(word, "")) +func (t *Table) preprocessRows() { + if t.processed { + // only do it once + return + } + + // convert entries to strings + t.rows = make([][]string, len(t.Entries)) + for rowidx, entries := range t.Entries { + t.rows[rowidx] = make([]string, len(entries)) + + for colidx, entry := range t.Entries[rowidx] { + t.rows[rowidx][colidx] = stringer(entry) + } + } + + // determine header lenght's + for idx, head := range t.Headers { + t.lenHeaders[idx] = visibleLen(head) + } + + // determine max width per column + for _, entries := range t.rows { + currentWidth := 0 + + for idx, entry := range entries { + length := visibleLen(entry) + + if t.lenHeaders[idx] < length { + if length > currentWidth+t.maxwidth { + t.lenHeaders[idx] = t.maxwidth - currentWidth + } else { + t.lenHeaders[idx] = length + } + } + + currentWidth += t.lenHeaders[idx] + } + } + + t.processed = true } func isInt(num string) bool { diff --git a/t/Makefile b/t/Makefile index 7f90f67..040c959 100644 --- a/t/Makefile +++ b/t/Makefile @@ -1,4 +1,4 @@ -.PHONY: test clean cluster docs search up down delete +.PHONY: test clean cluster docs search up down delete runenv # docker stuff up: @@ -20,6 +20,8 @@ delete: clean-docker: down delete +runenv: up waitup cluster docs wait + # mosscap esctl stuff test: up waitup cluster docs wait search