diff --git a/README.md b/README.md index 33fe72c..44a58ae 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ I would also like to mention some of the amazing libraries and packages I used f - [kingpin](https://github.com/alecthomas/kingpin) and [chroma](https://github.com/alecthomas/chroma) by Alec Thomas -- [tablewriter](https://github.com/olekukonko/tablewriter) by Oleku Konko +- [go-pretty](https://github.com/jedib0t/go-pretty) by Naveen Mahalingam - [diskv](https://github.com/peterbourgon/diskv) by Peter Bourgon diff --git a/application.go b/application.go index 3e28002..a601b0f 100644 --- a/application.go +++ b/application.go @@ -7,7 +7,6 @@ import ( "fmt" "io/ioutil" "os" - "runtime" "gopkg.in/alecthomas/kingpin.v2" @@ -39,13 +38,7 @@ func newApplication() error { } func bindAppFlags(app *kingpin.Application, global *commands.GlobalParameters) { - colorFlag := app.Flag("colour", "Enables colours in the standard output. To disable, use --no-colour (Disabled by default on Windows).") - - if runtime.GOOS == "windows" { - colorFlag.Default("false") - } else { - colorFlag.Default("true") - } + colorFlag := app.Flag("colour", "Enables colours in the standard output. To disable, use --no-colour.").Default("true") colorFlag.BoolVar(&global.EnableColor) diff --git a/commands/common.go b/commands/common.go index 3bdff19..2f7abed 100644 --- a/commands/common.go +++ b/commands/common.go @@ -9,10 +9,12 @@ import ( "strings" "syscall" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" - "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -66,27 +68,38 @@ func AddFormatFlag(c *kingpin.CmdClause, format *string) { } func PrintConfigTable(entries []*kafka.ConfigEntry) { - output.WithCount("Configurations", len(entries)) - table := output.InitStaticTable(os.Stdout, - output.H("Name", tablewriter.ALIGN_LEFT), - output.H("Value", tablewriter.ALIGN_LEFT), + table := tabular.NewTable(true, + tabular.C("Name").Align(tabular.AlignLeft).MaxWidth(100), + tabular.C("Value").Align(tabular.AlignLeft).FAlign(tabular.AlignRight).MaxWidth(100), ) + table.SetTitle(format.WithCount("Configurations", len(entries))) for _, config := range entries { - table.Append([]string{ - config.Name, - config.Value, - }) + parts := strings.Split(config.Value, ",") + table.AddRow(config.Name, strings.Join(parts, "\n")) } - table.SetFooter([]string{" ", fmt.Sprintf("Total: %d", len(entries))}) - table.SetFooterAlignment(tablewriter.ALIGN_RIGHT) + table.AddFooter("", fmt.Sprintf("Total: %d", len(entries))) table.Render() } func PrintConfigPlain(entries []*kafka.ConfigEntry) { - output.UnderlineWithCount("Configurations", len(entries)) + b := list.NewBullet() + b.SetTitle(format.WithCount("Configurations", len(entries))) for _, config := range entries { - fmt.Printf(" - %s: %s\n", config.Name, config.Value) + parts := strings.Split(config.Value, ",") + if len(parts) == 1 { + b.AddItem(fmt.Sprintf("%s: %v", config.Name, config.Value)) + continue + } + b.AddItem(config.Name) + b.Intend() + for _, val := range parts { + if !internal.IsEmpty(val) { + b.AddItem(val) + } + } + b.UnIntend() } + b.Render() } // AskForConfirmation asks the user for confirmation. The user must type in "yes/y", "no/n" or "exit/quit/q" diff --git a/commands/consume/interactive_mode.go b/commands/consume/interactive_mode.go index 2c36391..9ad7596 100644 --- a/commands/consume/interactive_mode.go +++ b/commands/consume/interactive_mode.go @@ -10,10 +10,10 @@ import ( "strconv" "strings" - "github.com/olekukonko/tablewriter" - "github.com/xitonix/trubka/commands" "github.com/xitonix/trubka/internal" + "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" "github.com/xitonix/trubka/protobuf" ) @@ -282,24 +282,21 @@ func askForStartingOffset(topic string, defaultCP *kafka.PartitionCheckpoints) ( } func confirmConsumerStart(topics map[string]*kafka.PartitionCheckpoints, contracts map[string]string) bool { - table := tablewriter.NewWriter(os.Stdout) - headers := []string{"Topic"} + headers := []*tabular.Column{tabular.C("Topic")} isProto := len(contracts) != 0 if isProto { - headers = append(headers, "Contract") + headers = append(headers, tabular.C("Contract")) } - headers = append(headers, "Offset") - table.SetHeader(headers) - table.SetRowLine(true) + headers = append(headers, tabular.C("Offset")) + table := tabular.NewTable(false, headers...) for topic, cp := range topics { - row := []string{topic} if isProto { - row = append(row, contracts[topic]) + table.AddRow(topic, contracts[topic], cp.OriginalFromValue()) + continue } - row = append(row, cp.OriginalFromValue()) - table.Append(row) + table.AddRow(topic, cp.OriginalFromValue()) } - fmt.Println() + output.NewLines(1) table.Render() return commands.AskForConfirmation("Start consuming") } diff --git a/commands/describe/broker.go b/commands/describe/broker.go index ded50f1..83c0080 100644 --- a/commands/describe/broker.go +++ b/commands/describe/broker.go @@ -2,18 +2,18 @@ package describe import ( "fmt" - "os" "regexp" "sort" - "strconv" "github.com/dustin/go-humanize" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" "github.com/xitonix/trubka/commands" "github.com/xitonix/trubka/internal" "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -23,6 +23,7 @@ type broker struct { topicsFilter *regexp.Regexp identifier string includeLogs bool + includeZeroLogs bool includeAPIVersions bool format string } @@ -34,10 +35,16 @@ func addBrokerSubCommand(parent *kingpin.CmdClause, global *commands.GlobalParam } c := parent.Command("broker", "Describes a Kafka broker.").Action(cmd.run) c.Arg("broker", "The broker address or Id.").Required().StringVar(&cmd.identifier) - c.Flag("include-logs", "Fetches information about the broker log files."). + c.Flag("include-logs", "Fetches information about the broker log for each topic."). Short('l'). NoEnvar(). BoolVar(&cmd.includeLogs) + + c.Flag("include-zero-entries", "Includes the topic log entries of size zero. Works with --include-logs only."). + Short('z'). + NoEnvar(). + BoolVar(&cmd.includeZeroLogs) + c.Flag("include-api-versions", "Fetches the API versions supported by the broker."). NoEnvar(). Short('a'). @@ -70,130 +77,161 @@ func (b *broker) run(_ *kingpin.ParseContext) error { switch b.format { case commands.PlainTextFormat: - b.printPlainTextOutput(meta) + return b.printPlainTextOutput(meta) case commands.TableFormat: - b.printTableOutput(meta) + return b.printTableOutput(meta) } return nil } -func (b *broker) printHeader(isController bool) { - c := internal.BoolToString(isController) - fmt.Printf("Controller Node: %s\n", - internal.Bold(c, isController && b.globalParams.EnableColor)) -} -func (b *broker) printPlainTextOutput(meta *kafka.BrokerMeta) { - b.printHeader(meta.IsController) - output.UnderlineWithCount("Consumer Groups", len(meta.ConsumerGroups)) +func (b *broker) printPlainTextOutput(meta *kafka.BrokerMeta) error { + header := format.WithCount("Consumer Groups", len(meta.ConsumerGroups)) + hLen := len(header) + if meta.IsController { + hLen += len(controlNodeFlag) + 3 + header = fmt.Sprintf("%s %v", header, format.GreenLabel(controlNodeFlag, b.globalParams.EnableColor)) + } + output.NewLines(1) + fmt.Println(format.UnderlineLen(header, hLen)) + l := list.NewBullet() for _, group := range meta.ConsumerGroups { - fmt.Printf(" - %s\n", group) + l.AddItem(group) } - fmt.Println() - + l.Render() if b.includeLogs && len(meta.Logs) != 0 { - b.printLogsPlain(meta.Logs) + output.NewLines(2) + if err := b.printLogsPlain(meta.Logs); err != nil { + return err + } } if b.includeAPIVersions && len(meta.APIs) != 0 { + output.NewLines(2) sort.Sort(kafka.APIByCode(meta.APIs)) b.printAPIPlain(meta.APIs) } + return nil } -func (b *broker) printTableOutput(meta *kafka.BrokerMeta) { - b.printHeader(meta.IsController) - table := output.InitStaticTable(os.Stdout, output.H("Consumer Groups", tablewriter.ALIGN_LEFT)) +func (b *broker) printTableOutput(meta *kafka.BrokerMeta) error { + header := "Consumer Groups" + if meta.IsController { + header = fmt.Sprintf("Consumer Groups %v", format.GreenLabel(controlNodeFlag, b.globalParams.EnableColor)) + } + table := tabular.NewTable(b.globalParams.EnableColor, tabular.C(header).Align(tabular.AlignLeft).FAlign(tabular.AlignRight)) for _, group := range meta.ConsumerGroups { - table.Append([]string{output.SpaceIfEmpty(group)}) + if len(group) > 0 { + table.AddRow(group) + } } - table.SetFooter([]string{fmt.Sprintf("Total: %d", len(meta.ConsumerGroups))}) - table.SetFooterAlignment(tablewriter.ALIGN_RIGHT) + table.AddFooter(fmt.Sprintf("Total: %d", len(meta.ConsumerGroups))) + output.NewLines(1) table.Render() if b.includeLogs && len(meta.Logs) != 0 { - b.printLogsTable(meta.Logs) + output.NewLines(2) + if err := b.printLogsTable(meta.Logs); err != nil { + return err + } } if b.includeAPIVersions && len(meta.APIs) != 0 { sort.Sort(kafka.APIByCode(meta.APIs)) + output.NewLines(2) b.printAPITable(meta.APIs) } + return nil } -func (b *broker) printLogsTable(logs []*kafka.LogFile) { +func (b *broker) printLogsTable(logs []*kafka.LogFile) error { for _, logFile := range logs { - fmt.Printf("\nLog File Path: %s\n", logFile.Path) sorted := logFile.SortByPermanentSize() if len(sorted) == 0 { - msg := internal.GetNotFoundMessage("topic log", "topic", b.topicsFilter) - fmt.Println(msg) - return + return internal.NotFoundError("topic log", "topic", b.topicsFilter) } - table := output.InitStaticTable(os.Stdout, - output.H("Topic", tablewriter.ALIGN_LEFT), - output.H("Permanent Logs", tablewriter.ALIGN_CENTER), - output.H("Temporary Logs", tablewriter.ALIGN_CENTER), + table := tabular.NewTable(b.globalParams.EnableColor, + tabular.C("Topic").Align(tabular.AlignLeft).FAlign(tabular.AlignRight), + tabular.C("Permanent Logs").FAlign(tabular.AlignCenter), + tabular.C("Temporary Logs").FAlign(tabular.AlignCenter), ) - rows := make([][]string, 0) - + table.SetTitle(fmt.Sprintf("Log File Path: %s", logFile.Path)) + table.TitleAlignment(tabular.AlignLeft) + var totalPerm, totalTemp uint64 for _, tLogs := range sorted { - row := []string{ - output.SpaceIfEmpty(tLogs.Topic), - output.SpaceIfEmpty(humanize.Bytes(tLogs.Permanent)), - output.SpaceIfEmpty(humanize.Bytes(tLogs.Temporary)), + if !b.includeZeroLogs && tLogs.Permanent == 0 && tLogs.Temporary == 0 { + continue } - rows = append(rows, row) + totalPerm += tLogs.Permanent + totalTemp += tLogs.Temporary + table.AddRow( + format.SpaceIfEmpty(tLogs.Topic), + format.SpaceIfEmpty(humanize.Bytes(tLogs.Permanent)), + format.SpaceIfEmpty(humanize.Bytes(tLogs.Temporary)), + ) } - table.AppendBulk(rows) + + table.AddFooter("Total", format.SpaceIfEmpty(humanize.Bytes(totalPerm)), format.SpaceIfEmpty(humanize.Bytes(totalTemp))) table.Render() } + return nil } -func (b *broker) printLogsPlain(logs []*kafka.LogFile) { +func (b *broker) printLogsPlain(logs []*kafka.LogFile) error { + l := list.NewBullet() + l.AsTree() for _, logFile := range logs { - title := fmt.Sprintf("\nPath: %s", logFile.Path) - fmt.Printf("%s\n", output.Underline(title)) + l.AddItem(logFile.Path) sorted := logFile.SortByPermanentSize() if len(sorted) == 0 { - msg := internal.GetNotFoundMessage("topic log", "topic", b.topicsFilter) - fmt.Println(msg) - return + return internal.NotFoundError("topic log", "topic", b.topicsFilter) } + l.Intend() + var totalPerm, totalTemp uint64 for _, tLogs := range sorted { - fmt.Printf(" - %s: PERM %s, TEMP %s\n", - tLogs.Topic, - humanize.Bytes(tLogs.Permanent), - humanize.Bytes(tLogs.Temporary)) + if !b.includeZeroLogs && tLogs.Permanent == 0 && tLogs.Temporary == 0 { + continue + } + totalPerm += tLogs.Permanent + totalTemp += tLogs.Temporary + l.AddItem(tLogs.Topic) + l.Intend() + if b.includeZeroLogs || tLogs.Permanent > 0 { + l.AddItem("Permanent: " + humanize.Bytes(tLogs.Permanent)) + } + if b.includeZeroLogs || tLogs.Temporary > 0 { + l.AddItem("Temporary: " + humanize.Bytes(tLogs.Temporary)) + } + l.UnIntend() } + l.UnIntend() + l.SetCaption(fmt.Sprintf("Total > Permanent: %s, Temporary: %s", humanize.Bytes(totalPerm), humanize.Bytes(totalTemp))) } + l.Render() + return nil } func (b *broker) printAPITable(apis []*kafka.API) { - output.WithCount("Supported API Versions", len(apis)) - table := output.InitStaticTable(os.Stdout, - output.H("API Key", tablewriter.ALIGN_CENTER), - output.H("Name", tablewriter.ALIGN_LEFT), - output.H("Min Version", tablewriter.ALIGN_CENTER), - output.H("Max Version", tablewriter.ALIGN_CENTER), + table := tabular.NewTable(b.globalParams.EnableColor, + tabular.C("API Key"), + tabular.C("Name").Align(tabular.AlignLeft), + tabular.C("Min Version"), + tabular.C("Max Version"), ) + table.TitleAlignment(tabular.AlignLeft) + table.SetTitle(format.WithCount("Supported API Versions", len(apis))) for _, api := range apis { - table.Append([]string{ - strconv.FormatInt(int64(api.Key), 10), - output.SpaceIfEmpty(api.Name), - strconv.FormatInt(int64(api.MinVersion), 10), - strconv.FormatInt(int64(api.MaxVersion), 10), - }) + table.AddRow(api.Key, format.SpaceIfEmpty(api.Name), api.MinVersion, api.MaxVersion) } - table.SetFooter([]string{fmt.Sprintf("Total: %d", len(apis)), " ", " ", " "}) - table.SetFooterAlignment(tablewriter.ALIGN_RIGHT) - + table.AddFooter(" ", fmt.Sprintf("Total: %d", len(apis)), " ", " ") table.Render() } func (b *broker) printAPIPlain(apis []*kafka.API) { - output.UnderlineWithCount("Supported API Versions", len(apis)) + l := list.NewBullet() + fmt.Println(format.UnderlinedTitleWithCount("Supported API Versions", len(apis))) for _, api := range apis { - fmt.Printf(" %s\n", api) + l.AddItem(api) } + l.Render() } diff --git a/commands/describe/cluster.go b/commands/describe/cluster.go index 124ba3e..5588890 100644 --- a/commands/describe/cluster.go +++ b/commands/describe/cluster.go @@ -2,20 +2,21 @@ package describe import ( "fmt" - "os" "sort" - "strconv" "gopkg.in/alecthomas/kingpin.v2" - "github.com/olekukonko/tablewriter" - "github.com/xitonix/trubka/commands" - "github.com/xitonix/trubka/internal" "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) +const ( + controlNodeFlag = "CTRL" +) + type cluster struct { globalParams *commands.GlobalParameters kafkaParams *commands.KafkaParameters @@ -53,6 +54,11 @@ func (c *cluster) run(_ *kingpin.ParseContext) error { return fmt.Errorf("failed to list the brokers: %w", err) } + if len(meta.Brokers) == 0 { + fmt.Println("No brokers found!") + return nil + } + sort.Sort(kafka.BrokersById(meta.Brokers)) sort.Sort(kafka.ConfigEntriesByName(meta.ConfigEntries)) @@ -66,42 +72,47 @@ func (c *cluster) run(_ *kingpin.ParseContext) error { } func (c *cluster) printTableOutput(meta *kafka.ClusterMetadata) { - table := output.InitStaticTable(os.Stdout, - output.H("ID", tablewriter.ALIGN_LEFT), - output.H("Address", tablewriter.ALIGN_LEFT), + table := tabular.NewTable(c.globalParams.EnableColor, + tabular.C("ID").Align(tabular.AlignLeft), + tabular.C("Address").Align(tabular.AlignLeft), ) - output.WithCount("Brokers", len(meta.Brokers)) + table.SetTitle(format.WithCount("Brokers", len(meta.Brokers))) for _, broker := range meta.Brokers { - id := strconv.FormatInt(int64(broker.ID), 10) - host := broker.Host if broker.IsController { - host += fmt.Sprintf(" [%s]", internal.Bold("C", c.globalParams.EnableColor)) + host := fmt.Sprintf("%v < %v", + format.BoldGreen(broker.Host, c.globalParams.EnableColor), + format.GreenLabel(controlNodeFlag, c.globalParams.EnableColor), + ) + table.AddRow(format.BoldGreen(broker.ID, c.globalParams.EnableColor), host) + continue } - row := []string{id, host} - table.Append(row) + table.AddRow(broker.ID, broker.Host) } - table.SetFooter([]string{" ", fmt.Sprintf("Total: %d", len(meta.Brokers))}) - table.SetFooterAlignment(tablewriter.ALIGN_RIGHT) + table.AddFooter("", fmt.Sprintf("Total: %d", len(meta.Brokers))) + output.NewLines(1) table.Render() - c.printLegend() if len(meta.ConfigEntries) > 0 { + output.NewLines(2) commands.PrintConfigTable(meta.ConfigEntries) } } func (c *cluster) printPlainTextOutput(meta *kafka.ClusterMetadata) { - output.UnderlineWithCount("Brokers", len(meta.Brokers)) + fmt.Printf("\n%s\n\n", format.UnderlinedTitleWithCount("Brokers", len(meta.Brokers))) for _, broker := range meta.Brokers { - fmt.Printf("%s\n", broker.String()) + if broker.IsController { + fmt.Printf("%v. %v < %v\n", + format.BoldGreen(broker.ID, c.globalParams.EnableColor), + format.BoldGreen(broker.Host, c.globalParams.EnableColor), + format.GreenLabel(controlNodeFlag, c.globalParams.EnableColor)) + } else { + fmt.Printf("%v. %v\n", broker.ID, broker.Host) + } } - c.printLegend() if len(meta.ConfigEntries) > 0 { + output.NewLines(2) commands.PrintConfigPlain(meta.ConfigEntries) } } - -func (*cluster) printLegend() { - fmt.Println("[C]: Controller Node") -} diff --git a/commands/describe/group.go b/commands/describe/group.go index d1c39f7..3c47ce9 100644 --- a/commands/describe/group.go +++ b/commands/describe/group.go @@ -1,17 +1,16 @@ package describe import ( - "bytes" "fmt" - "os" "strings" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" "github.com/xitonix/trubka/commands" - "github.com/xitonix/trubka/internal" "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -64,9 +63,17 @@ func (c *group) run(_ *kingpin.ParseContext) error { } func (c *group) printPlainTextOutput(details *kafka.ConsumerGroupDetails) { - fmt.Println(details.String()) - if c.includeMembers { - output.UnderlineWithCount("Members", len(details.Members)) + + fmt.Printf(" Name: %s\n Coordinator: %s\n State: %s\n Protocol: %s\nProtocol Type: %s", + details.Name, + details.Coordinator.Host, + format.GroupStateLabel(details.State, c.globalParams.EnableColor), + details.Protocol, + details.ProtocolType) + + if c.includeMembers && len(details.Members) > 0 { + output.NewLines(2) + fmt.Println(format.UnderlinedTitleWithCount("Members", len(details.Members))) for member, md := range details.Members { fmt.Println(" ID: " + member) fmt.Printf("HOST: %s\n\n", md.ClientHost) @@ -75,68 +82,72 @@ func (c *group) printPlainTextOutput(details *kafka.ConsumerGroupDetails) { } tps := details.Members[member].TopicPartitions sortedTopics := tps.SortedTopics() - output.UnderlineWithCount("Assignments", len(sortedTopics)) + fmt.Println(format.UnderlinedTitleWithCount("Assignments", len(sortedTopics))) + b := list.NewBullet() + b.AsTree() for _, topic := range sortedTopics { - space := strings.Repeat(" ", 2) - fmt.Printf("%s- %s: %s\n", space, topic, tps.SortedPartitionsString(topic)) + b.AddItem(topic) + b.Intend() + b.AddItem(tps.SortedPartitionsString(topic)) + b.UnIntend() } - fmt.Println() + b.Render() + output.NewLines(1) } } } func (c *group) printTableOutput(details *kafka.ConsumerGroupDetails) { - table := output.InitStaticTable(os.Stdout, - output.H("Coordinator", tablewriter.ALIGN_CENTER), - output.H("State", tablewriter.ALIGN_CENTER), - output.H("Protocol", tablewriter.ALIGN_CENTER), - output.H("Protocol Type", tablewriter.ALIGN_CENTER), + table := tabular.NewTable(c.globalParams.EnableColor, + tabular.C("Coordinator"), + tabular.C("State"), + tabular.C("Protocol"), + tabular.C("Protocol Type"), ) - table.Append([]string{details.Coordinator.Address, - internal.HighlightGroupState(details.State, c.globalParams.EnableColor), + + table.AddRow( + details.Coordinator.Host, + format.GroupStateLabel(details.State, c.globalParams.EnableColor), details.Protocol, - details.ProtocolType}, + details.ProtocolType, ) table.Render() - if c.includeMembers { + if c.includeMembers && len(details.Members) > 0 { c.printMemberDetailsTable(details.Members) } } func (c *group) printMemberDetailsTable(members map[string]*kafka.GroupMemberDetails) { - table := tablewriter.NewWriter(os.Stdout) - table = output.InitStaticTable(os.Stdout, - output.H("ID", tablewriter.ALIGN_LEFT), - output.H("Client Host", tablewriter.ALIGN_CENTER), - output.H("Assignments", tablewriter.ALIGN_CENTER), + table := tabular.NewTable(c.globalParams.EnableColor, + tabular.C("ID").HAlign(tabular.AlignLeft).FAlign(tabular.AlignRight), + tabular.C("Client Host"), + tabular.C("Assignments").Align(tabular.AlignLeft), ) - output.WithCount("Members", len(members)) - rows := make([][]string, 0) + table.SetTitle(format.WithCount("Members", len(members))) for name, desc := range members { - var buf bytes.Buffer - inner := output.InitStaticTable(&buf, - output.H("Topic", tablewriter.ALIGN_LEFT), - output.H("Partition", tablewriter.ALIGN_CENTER), - ) sortedTopics := desc.TopicPartitions.SortedTopics() - for _, topic := range sortedTopics { - inner.Append([]string{ - output.SpaceIfEmpty(topic), - output.SpaceIfEmpty(desc.TopicPartitions.SortedPartitionsString(topic)), - }) - } - inner.Render() - row := []string{ - output.SpaceIfEmpty(name), - output.SpaceIfEmpty(desc.ClientHost), - output.SpaceIfEmpty(buf.String()), + var buf strings.Builder + for i, topic := range sortedTopics { + buf.WriteString(format.Underline(topic)) + partitions := desc.TopicPartitions.SortedPartitions(topic) + for j, p := range partitions { + if j%20 == 0 { + buf.WriteString("\n") + } + buf.WriteString(fmt.Sprintf("%d ", p)) + } + if i < len(sortedTopics)-1 { + buf.WriteString("\n\n") + } } - rows = append(rows, row) + table.AddRow( + format.SpaceIfEmpty(name), + format.SpaceIfEmpty(desc.ClientHost), + format.SpaceIfEmpty(buf.String()), + ) } - table.AppendBulk(rows) - table.SetFooter([]string{fmt.Sprintf("Total: %d", len(members)), " ", " "}) - table.SetFooterAlignment(tablewriter.ALIGN_RIGHT) + table.AddFooter(fmt.Sprintf("Total: %d", len(members)), " ", " ") table.Render() } diff --git a/commands/describe/topic.go b/commands/describe/topic.go index d690ff4..39f9072 100644 --- a/commands/describe/topic.go +++ b/commands/describe/topic.go @@ -3,17 +3,17 @@ package describe import ( "bytes" "fmt" - "os" "sort" - "strconv" "strings" "github.com/dustin/go-humanize" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" "github.com/xitonix/trubka/commands" "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -59,6 +59,10 @@ func (t *topic) run(_ *kingpin.ParseContext) error { return err } + if len(meta.Partitions) == 0 { + return fmt.Errorf("topic %s not found", t.topic) + } + sort.Sort(kafka.PartitionMetaById(meta.Partitions)) if t.loadConfigs { sort.Sort(kafka.ConfigEntriesByName(meta.ConfigEntries)) @@ -75,82 +79,72 @@ func (t *topic) run(_ *kingpin.ParseContext) error { func (t *topic) printPlainTextOutput(meta *kafka.TopicMetadata) { var totalOffsets int64 - output.UnderlineWithCount("Partitions", len(meta.Partitions)) + fmt.Println(format.UnderlinedTitleWithCount("Partitions", len(meta.Partitions))) + b := list.NewBullet() + b.AsTree() for _, pm := range meta.Partitions { - var offset string + b.AddItem(fmt.Sprintf("P%d", pm.Id)) + b.Intend() if t.includeOffsets { - offset = fmt.Sprintf("\n - Offset: %s", humanize.Comma(pm.Offset)) + b.AddItem(fmt.Sprintf("Offset: %s", humanize.Comma(pm.Offset))) totalOffsets += pm.Offset } - fmt.Printf("P%d: %s\n - Leader: %s\n - ISRs: %s\n - Replicas: %s", - pm.Id, - offset, - pm.Leader.Host, - t.brokersToLine(pm.ISRs...), - t.brokersToLine(pm.Replicas...)) - + b.AddItem(fmt.Sprintf("Leader: %s", pm.Leader.MarkedHostName())) + b.AddItem(fmt.Sprintf("ISRs: %s", t.brokersToLine(pm.ISRs...))) + b.AddItem(fmt.Sprintf("Replicas: %s", t.brokersToLine(pm.Replicas...))) if len(pm.OfflineReplicas) > 0 { - fmt.Printf("\n - Offline Replicas: %s", t.brokersToLine(pm.OfflineReplicas...)) + b.AddItem(fmt.Sprintf("Offline Replicas: %s", t.brokersToLine(pm.OfflineReplicas...))) } - fmt.Print("\n\n") + b.UnIntend() } + b.SetCaption(kafka.ControllerBrokerLabel + " CONTROLLER NODES") + b.Render() if t.includeOffsets { - fmt.Println(output.Underline("Total Offsets")) + output.NewLines(1) + fmt.Println(format.Underline("Total Offsets")) fmt.Println(humanize.Comma(totalOffsets)) } if t.loadConfigs { + output.NewLines(2) commands.PrintConfigPlain(meta.ConfigEntries) } } func (t *topic) printTableOutput(meta *kafka.TopicMetadata) { - var table *tablewriter.Table - if t.includeOffsets { - table = output.InitStaticTable(os.Stdout, - output.H("Partition", tablewriter.ALIGN_CENTER), - output.H("Offset", tablewriter.ALIGN_CENTER), - output.H("Leader", tablewriter.ALIGN_LEFT), - output.H("Replicas", tablewriter.ALIGN_LEFT), - output.H("Offline Replicas", tablewriter.ALIGN_LEFT), - output.H("ISRs", tablewriter.ALIGN_LEFT), - ) - } else { - table = output.InitStaticTable(os.Stdout, - output.H("Partition", tablewriter.ALIGN_CENTER), - output.H("Leader", tablewriter.ALIGN_LEFT), - output.H("Replicas", tablewriter.ALIGN_LEFT), - output.H("Offline Replicas", tablewriter.ALIGN_LEFT), - output.H("ISRs", tablewriter.ALIGN_LEFT), - ) - } - output.WithCount("Partitions", len(meta.Partitions)) + table := tabular.NewTable(t.globalParams.EnableColor, + tabular.C("Partition"), + tabular.C("Offset").FAlign(tabular.AlignCenter), + tabular.C("Leader").Align(tabular.AlignLeft), + tabular.C("Replicas").Align(tabular.AlignLeft), + tabular.C("Offline Replicas").Align(tabular.AlignLeft), + tabular.C("ISRs").Align(tabular.AlignLeft), + ) + table.SetTitle(format.WithCount("Partitions", len(meta.Partitions))) var totalOffsets int64 for _, pm := range meta.Partitions { - partition := strconv.FormatInt(int64(pm.Id), 10) - row := []string{partition} - + offset := "-" if t.includeOffsets { - row = append(row, humanize.Comma(pm.Offset)) + offset = humanize.Comma(pm.Offset) totalOffsets += pm.Offset } - row = append(row, - output.SpaceIfEmpty(t.brokersToList(pm.Leader)), - output.SpaceIfEmpty(t.brokersToList(pm.Replicas...)), - output.SpaceIfEmpty(t.brokersToList(pm.OfflineReplicas...)), - output.SpaceIfEmpty(t.brokersToList(pm.ISRs...)), + table.AddRow( + pm.Id, + offset, + format.SpaceIfEmpty(pm.Leader.MarkedHostName()), + format.SpaceIfEmpty(t.brokersToList(pm.Replicas...)), + format.SpaceIfEmpty(t.brokersToList(pm.OfflineReplicas...)), + format.SpaceIfEmpty(t.brokersToList(pm.ISRs...)), ) - table.Append(row) } - footer := []string{fmt.Sprintf("Total: %d", len(meta.Partitions))} + total := " " if t.includeOffsets { - footer = append(footer, humanize.Comma(totalOffsets)) + total = humanize.Comma(totalOffsets) } - footer = append(footer, " ", " ", " ", " ") - table.SetFooter(footer) - table.SetFooterAlignment(tablewriter.ALIGN_CENTER) + table.AddFooter(fmt.Sprintf("Total: %d", len(meta.Partitions)), total, " ", " ", " ", " ") + table.SetCaption(kafka.ControllerBrokerLabel + " CONTROLLER NODES") table.Render() if t.loadConfigs { @@ -158,13 +152,13 @@ func (t *topic) printTableOutput(meta *kafka.TopicMetadata) { } } -func (*topic) brokersToList(brokers ...*kafka.Broker) string { +func (t *topic) brokersToList(brokers ...*kafka.Broker) string { if len(brokers) == 1 { return brokers[0].Host } var buf bytes.Buffer for i, b := range brokers { - buf.WriteString(fmt.Sprintf("%s", b.Host)) + buf.WriteString(b.MarkedHostName()) if i < len(brokers)-1 { buf.WriteString("\n") } @@ -172,10 +166,10 @@ func (*topic) brokersToList(brokers ...*kafka.Broker) string { return buf.String() } -func (*topic) brokersToLine(brokers ...*kafka.Broker) string { +func (t *topic) brokersToLine(brokers ...*kafka.Broker) string { result := make([]string, len(brokers)) for i, b := range brokers { - result[i] = b.Host + result[i] = b.MarkedHostName() } return strings.Join(result, ", ") } diff --git a/commands/list/group_offsets.go b/commands/list/group_offsets.go index 7becba6..c1c5f71 100644 --- a/commands/list/group_offsets.go +++ b/commands/list/group_offsets.go @@ -2,17 +2,17 @@ package list import ( "fmt" - "os" "regexp" "strconv" "github.com/dustin/go-humanize" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" "github.com/xitonix/trubka/commands" "github.com/xitonix/trubka/internal" - "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -55,8 +55,7 @@ func (g *groupOffset) run(_ *kingpin.ParseContext) error { } if len(topics) == 0 { - fmt.Println(internal.GetNotFoundMessage("topic", "topic", g.topicFilter)) - return nil + return internal.NotFoundError("topic", "topic", g.topicFilter) } switch g.format { @@ -70,34 +69,27 @@ func (g *groupOffset) run(_ *kingpin.ParseContext) error { func (g *groupOffset) printTableOutput(topics kafka.TopicPartitionOffset) { for topic, partitionOffsets := range topics { - fmt.Printf("%s: %s\n", - internal.Bold("TOPIC", g.globalParams.EnableColor), - internal.Bold(topic, g.globalParams.EnableColor)) + table := tabular.NewTable(g.globalParams.EnableColor, + tabular.C("Partition").MinWidth(10), + tabular.C("Latest").MinWidth(10).Align(tabular.AlignCenter), + tabular.C("Current").MinWidth(10).Align(tabular.AlignCenter), + tabular.C("Lag").MinWidth(10).Humanize().FAlign(tabular.AlignCenter).Warn(0, true), + ) + table.SetTitle(fmt.Sprintf("Topic: %s", topic)) if len(partitionOffsets) > 0 { - table := output.InitStaticTable(os.Stdout, - output.H("Partition", tablewriter.ALIGN_CENTER), - output.H("Latest", tablewriter.ALIGN_CENTER), - output.H("Current", tablewriter.ALIGN_CENTER), - output.H("Lag", tablewriter.ALIGN_CENTER), - ) - table.SetColMinWidth(0, 10) - table.SetColMinWidth(1, 10) - table.SetColMinWidth(2, 10) - table.SetColMinWidth(3, 10) partitions := partitionOffsets.SortPartitions() var totalLag int64 for _, partition := range partitions { offsets := partitionOffsets[int32(partition)] lag := offsets.Lag() - totalLag += offsets.Lag() + totalLag += lag latest := humanize.Comma(offsets.Latest) current := humanize.Comma(offsets.Current) part := strconv.FormatInt(int64(partition), 10) - table.Append([]string{part, latest, current, fmt.Sprint(highlightLag(lag, g.globalParams.EnableColor))}) + table.AddRow(part, latest, current, lag) } - table.SetFooter([]string{" ", " ", " ", humanize.Comma(totalLag)}) - table.SetFooterAlignment(tablewriter.ALIGN_CENTER) + table.AddFooter(" ", " ", " ", totalLag) table.Render() } } @@ -105,19 +97,29 @@ func (g *groupOffset) printTableOutput(topics kafka.TopicPartitionOffset) { func (g *groupOffset) printPlainTextOutput(topics kafka.TopicPartitionOffset) { for topic, partitionOffsets := range topics { - fmt.Printf("%s\n", internal.Bold(topic, g.globalParams.EnableColor)) + b := list.NewBullet() + b.AsTree() + b.SetTitle(topic) + var totalLag int64 if len(partitionOffsets) > 0 { - fmt.Printf("\n") - var totalLag int64 partitions := partitionOffsets.SortPartitions() + b.Intend() for _, partition := range partitions { offsets := partitionOffsets[int32(partition)] lag := offsets.Lag() - totalLag += offsets.Lag() - fmt.Printf(" Partition %2d: %d out of %d (Lag: %s) \n", partition, offsets.Current, offsets.Latest, - highlightLag(lag, g.globalParams.EnableColor)) + totalLag += lag + + b.AddItem(fmt.Sprintf("P%d", partition)) + b.Intend() + b.AddItem(fmt.Sprintf(" Latest: %s", humanize.Comma(offsets.Latest))) + b.AddItem(fmt.Sprintf("Current: %s", humanize.Comma(offsets.Current))) + b.AddItem(fmt.Sprintf(" Lag: %v", format.Warn(lag, g.globalParams.EnableColor, true))) + b.UnIntend() } - fmt.Printf(" -----------------\n Total Lag: %s\n\n", humanize.Comma(totalLag)) + } + b.Render() + if len(partitionOffsets) > 0 { + fmt.Printf("\n%s\n%v\n\n", format.Underline("Total Lag"), format.Warn(totalLag, g.globalParams.EnableColor, true)) } } } diff --git a/commands/list/groups.go b/commands/list/groups.go index 3bf1664..a03c57b 100644 --- a/commands/list/groups.go +++ b/commands/list/groups.go @@ -2,17 +2,18 @@ package list import ( "fmt" - "os" "regexp" "sort" "github.com/dustin/go-humanize" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" "github.com/xitonix/trubka/commands" "github.com/xitonix/trubka/internal" "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -59,8 +60,7 @@ func (c *groups) run(_ *kingpin.ParseContext) error { } if len(groups) == 0 { - fmt.Println(internal.GetNotFoundMessage("consumer group", "group", c.groupFilter)) - return nil + return internal.NotFoundError("consumer group", "group", c.groupFilter) } sort.Sort(kafka.ConsumerGroupDetailsByName(groups)) @@ -75,49 +75,60 @@ func (c *groups) run(_ *kingpin.ParseContext) error { } func (c *groups) printPlainTextOutput(groups []*kafka.ConsumerGroupDetails) { - for _, group := range groups { - if c.includeState { - fmt.Printf("%s\n\n", group) - } else { - fmt.Printf("%s\n", group.Name) + if c.includeState { + for _, group := range groups { + b := list.NewBullet() + b.AsTree() + b.AddItem(group.Name) + b.Intend() + b.AddItem(fmt.Sprintf(" State: %s", format.GroupStateLabel(group.State, c.globalParams.EnableColor))) + b.AddItem(fmt.Sprintf(" Protocol: %s", group.Protocol)) + b.AddItem(fmt.Sprintf("Protocol Type: %s", group.ProtocolType)) + b.AddItem(fmt.Sprintf(" Coordinator: %s", group.Coordinator.Host)) + b.UnIntend() + b.Render() + output.NewLines(1) + } + } else { + b := list.NewBullet() + for _, group := range groups { + b.AddItem(group.Name) } + b.Render() + output.NewLines(1) } - fmt.Printf("\nTotal: %s", humanize.Comma(int64(len(groups)))) + fmt.Printf("%s\n %s", format.Underline("Total"), humanize.Comma(int64(len(groups)))) } func (c *groups) printTableOutput(groups []*kafka.ConsumerGroupDetails) { - var table *tablewriter.Table + var table *tabular.Table if c.includeState { - table = output.InitStaticTable(os.Stdout, - output.H("Name", tablewriter.ALIGN_LEFT), - output.H("State", tablewriter.ALIGN_CENTER), - output.H("Protocol", tablewriter.ALIGN_CENTER), - output.H("Protocol Type", tablewriter.ALIGN_CENTER), - output.H("Coordinator", tablewriter.ALIGN_CENTER), + table = tabular.NewTable(c.globalParams.EnableColor, + tabular.C("Name").Align(tabular.AlignLeft), + tabular.C("State"), + tabular.C("Protocol"), + tabular.C("Protocol Type"), + tabular.C("Coordinator"), ) } else { - table = output.InitStaticTable(os.Stdout, output.H("Consumer Group", tablewriter.ALIGN_LEFT)) + table = tabular.NewTable(c.globalParams.EnableColor, tabular.C("Consumer Group").Align(tabular.AlignLeft)) } - rows := make([][]string, 0) for _, group := range groups { if c.includeState { - rows = append(rows, []string{ - group.Name, - internal.HighlightGroupState(group.State, c.globalParams.EnableColor), + table.AddRow(group.Name, + format.GroupStateLabel(group.State, c.globalParams.EnableColor), group.Protocol, group.ProtocolType, - group.Coordinator.Host}) + group.Coordinator.Host) } else { - rows = append(rows, []string{group.Name}) + table.AddRow(group.Name) } } - table.AppendBulk(rows) if c.includeState { - table.SetFooter([]string{fmt.Sprintf("Total: %s", humanize.Comma(int64(len(groups)))), " ", " ", " ", " "}) + table.AddFooter(fmt.Sprintf("Total: %s", humanize.Comma(int64(len(groups)))), " ", " ", " ", " ") } else { - table.SetFooter([]string{fmt.Sprintf("Total: %s", humanize.Comma(int64(len(groups))))}) + table.AddFooter(fmt.Sprintf("Total: %s", humanize.Comma(int64(len(groups))))) } - table.SetFooterAlignment(tablewriter.ALIGN_RIGHT) table.Render() } diff --git a/commands/list/local_offsets.go b/commands/list/local_offsets.go index 9a5df20..07758fb 100644 --- a/commands/list/local_offsets.go +++ b/commands/list/local_offsets.go @@ -2,15 +2,14 @@ package list import ( "fmt" - "os" - "strconv" "github.com/dustin/go-humanize" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" "github.com/xitonix/trubka/commands" - "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -71,42 +70,44 @@ func (l *listLocalOffsets) run(_ *kingpin.ParseContext) error { func (l *listLocalOffsets) printTableOutput(offsets kafka.PartitionOffset) { sortedPartitions := offsets.SortPartitions() - - table := output.InitStaticTable(os.Stdout, - output.H("Partition", tablewriter.ALIGN_CENTER), - output.H("Latest", tablewriter.ALIGN_CENTER), - output.H("Current", tablewriter.ALIGN_CENTER), - output.H("Lag", tablewriter.ALIGN_CENTER), + table := tabular.NewTable(l.globalParams.EnableColor, + tabular.C("Partition"), + tabular.C("Latest").MinWidth(10), + tabular.C("Current").MinWidth(10), + tabular.C("Lag").MinWidth(10).Humanize().Warn(0, true).FAlign(tabular.AlignCenter), ) - table.SetColMinWidth(1, 10) - table.SetColMinWidth(2, 10) - table.SetColMinWidth(3, 10) + table.SetTitle(format.WithCount("Partitions", len(sortedPartitions))) var totalLag int64 - output.WithCount("Partitions", len(sortedPartitions)) for _, partition := range sortedPartitions { offsets := offsets[int32(partition)] lag := offsets.Lag() - totalLag += offsets.Lag() + totalLag += lag latest := humanize.Comma(offsets.Latest) current := humanize.Comma(offsets.Current) - part := strconv.FormatInt(int64(partition), 10) - table.Append([]string{part, latest, current, fmt.Sprint(highlightLag(lag, l.globalParams.EnableColor))}) + table.AddRow(partition, latest, current, lag) } - table.SetFooter([]string{" ", " ", " ", humanize.Comma(totalLag)}) - table.SetFooterAlignment(tablewriter.ALIGN_CENTER) + table.AddFooter(" ", " ", " ", totalLag) table.Render() } func (l *listLocalOffsets) printPlainTextOutput(offsets kafka.PartitionOffset) { partitions := offsets.SortPartitions() var totalLag int64 - output.UnderlineWithCount("Partitions", len(partitions)) + fmt.Println(format.UnderlinedTitleWithCount("Partitions", len(partitions))) + for _, partition := range partitions { + b := list.NewBullet() + b.AsTree() offsets := offsets[int32(partition)] lag := offsets.Lag() - totalLag += offsets.Lag() - fmt.Printf("Partition %2d: %d out of %d (Lag: %s) \n", partition, offsets.Current, offsets.Latest, - highlightLag(lag, l.globalParams.EnableColor)) + totalLag += lag + b.AddItem(fmt.Sprintf("P%d", partition)) + b.Intend() + b.AddItem(fmt.Sprintf(" Latest: %s", humanize.Comma(offsets.Latest))) + b.AddItem(fmt.Sprintf("Current: %s", humanize.Comma(offsets.Current))) + b.AddItem(fmt.Sprintf(" Lag: %v", format.Warn(lag, l.globalParams.EnableColor, true))) + b.UnIntend() + b.Render() } - fmt.Printf("-----------------\nTotal Lag: %s\n\n", humanize.Comma(totalLag)) + fmt.Printf("\n%s\n%v", format.Underline("Total Lag"), format.Warn(totalLag, l.globalParams.EnableColor, true)) } diff --git a/commands/list/local_topics.go b/commands/list/local_topics.go index aa257d3..4da58d7 100644 --- a/commands/list/local_topics.go +++ b/commands/list/local_topics.go @@ -2,16 +2,17 @@ package list import ( "fmt" - "os" "regexp" "sort" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" "github.com/xitonix/trubka/commands" "github.com/xitonix/trubka/internal" "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -60,27 +61,28 @@ func (l *listLocalTopics) run(_ *kingpin.ParseContext) error { func (l *listLocalTopics) printTableOutput(store map[string][]string) { for env, topics := range store { - table := output.InitStaticTable(os.Stdout, output.H(env, tablewriter.ALIGN_LEFT)) - table.SetColMinWidth(0, 50) + table := tabular.NewTable(l.globalParams.EnableColor, tabular.C(format.WithCount(env, len(topics))).Align(tabular.AlignLeft).MinWidth(60)) sort.Strings(topics) for _, topic := range topics { - table.Append([]string{output.SpaceIfEmpty(topic)}) + table.AddRow(format.SpaceIfEmpty(topic)) } - table.SetFooter([]string{fmt.Sprintf("Total: %d", len(topics))}) - table.SetFooterAlignment(tablewriter.ALIGN_RIGHT) + table.AddFooter(fmt.Sprintf("Total: %d", len(topics))) table.Render() - fmt.Println() + output.NewLines(1) } } func (l *listLocalTopics) printPlainTextOutput(store map[string][]string) { + b := list.NewBullet() + b.AsTree() for env, topics := range store { - line := fmt.Sprintf("Environment: %s (%d)", env, len(topics)) - fmt.Println(internal.Bold(line, l.globalParams.EnableColor)) + b.AddItem(format.WithCount(env, len(topics))) + b.Intend() sort.Strings(topics) for _, topic := range topics { - fmt.Printf(" - %s\n", topic) + b.AddItem(topic) } - fmt.Println() + b.UnIntend() } + b.Render() } diff --git a/commands/list/topics.go b/commands/list/topics.go index cf9bf6f..185c58c 100644 --- a/commands/list/topics.go +++ b/commands/list/topics.go @@ -2,18 +2,17 @@ package list import ( "fmt" - "os" "regexp" "sort" - "strconv" "github.com/dustin/go-humanize" - "github.com/olekukonko/tablewriter" "gopkg.in/alecthomas/kingpin.v2" "github.com/xitonix/trubka/commands" "github.com/xitonix/trubka/internal" - "github.com/xitonix/trubka/internal/output" + "github.com/xitonix/trubka/internal/output/format" + "github.com/xitonix/trubka/internal/output/format/list" + "github.com/xitonix/trubka/internal/output/format/tabular" "github.com/xitonix/trubka/kafka" ) @@ -54,8 +53,7 @@ func (c *topics) run(_ *kingpin.ParseContext) error { } if len(topics) == 0 { - fmt.Println(internal.GetNotFoundMessage("topic", "topic", c.topicFilter)) - return nil + return internal.NotFoundError("topic", "topic", c.topicFilter) } sort.Sort(kafka.TopicsByName(topics)) @@ -70,39 +68,33 @@ func (c *topics) run(_ *kingpin.ParseContext) error { } func (c *topics) printPlainTextOutput(topics []kafka.Topic) { + b := list.NewBullet() + b.SetTitle(format.WithCount("Topics", len(topics))) var totalPartitions int64 - output.UnderlineWithCount("Topics", len(topics)) for _, topic := range topics { totalPartitions += int64(topic.NumberOfPartitions) - fmt.Printf("%s\n", topic) + b.AddItem(topic.Name) } - fmt.Println("\nTotal\n-----") - fmt.Printf("Number of topics: %s\n", humanize.Comma(int64(len(topics)))) - fmt.Printf("Number of partitions: %s", humanize.Comma(totalPartitions)) + caption := fmt.Sprintf("%s", format.Underline("Total")) + caption += fmt.Sprintf("\n Topics: %s", humanize.Comma(int64(len(topics)))) + caption += fmt.Sprintf("\nPartitions: %s", humanize.Comma(totalPartitions)) + b.SetCaption(caption) + b.Render() } func (c *topics) printTableOutput(topics []kafka.Topic) { - table := output.InitStaticTable(os.Stdout, - output.H("Topic", tablewriter.ALIGN_LEFT), - output.H("Number of Partitions", tablewriter.ALIGN_CENTER), - output.H("Replication Factor", tablewriter.ALIGN_CENTER), + table := tabular.NewTable(c.globalParams.EnableColor, + tabular.C("Topic").Align(tabular.AlignLeft), + tabular.C("Number of Partitions").FAlign(tabular.AlignCenter), + tabular.C("Replication Factor"), ) + table.SetTitle(format.WithCount("Topics", len(topics))) - rows := make([][]string, 0) var totalPartitions int64 - output.WithCount("Topics", len(topics)) for _, topic := range topics { - np := strconv.FormatInt(int64(topic.NumberOfPartitions), 10) - rf := strconv.FormatInt(int64(topic.ReplicationFactor), 10) totalPartitions += int64(topic.NumberOfPartitions) - rows = append(rows, []string{ - output.SpaceIfEmpty(topic.Name), - output.SpaceIfEmpty(np), - output.SpaceIfEmpty(rf), - }) + table.AddRow(topic.Name, topic.NumberOfPartitions, topic.ReplicationFactor) } - table.AppendBulk(rows) - table.SetFooter([]string{fmt.Sprintf("Total: %s", humanize.Comma(int64(len(topics)))), humanize.Comma(totalPartitions), " "}) - table.SetFooterAlignment(tablewriter.ALIGN_CENTER) + table.AddFooter(fmt.Sprintf("Total: %s", humanize.Comma(int64(len(topics)))), humanize.Comma(totalPartitions), " ") table.Render() } diff --git a/commands/produce/produce.go b/commands/produce/produce.go index c5b7da2..160904c 100644 --- a/commands/produce/produce.go +++ b/commands/produce/produce.go @@ -68,7 +68,7 @@ func produce(kafkaParams *commands.KafkaParameters, } err := producer.Close() if err != nil { - fmt.Println(internal.Err("Failed to close the publisher", globalParams.EnableColor)) + fmt.Println(internal.Red("Failed to close the publisher", globalParams.EnableColor)) } }() diff --git a/go.mod b/go.mod index 0fe52fb..d370ad3 100644 --- a/go.mod +++ b/go.mod @@ -3,28 +3,32 @@ module github.com/xitonix/trubka go 1.12 require ( - github.com/Shopify/sarama v1.26.1 - github.com/alecthomas/chroma v0.7.1 + github.com/Shopify/sarama v1.26.4 + github.com/alecthomas/chroma v0.7.3 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect - github.com/brianvoe/gofakeit/v4 v4.2.3 - github.com/dlclark/regexp2 v1.2.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect + github.com/brianvoe/gofakeit/v4 v4.3.0 github.com/dustin/go-humanize v1.0.0 - github.com/golang/protobuf v1.3.3 + github.com/go-openapi/errors v0.19.6 // indirect + github.com/go-openapi/strfmt v0.19.5 // indirect + github.com/golang/protobuf v1.4.2 github.com/google/btree v1.0.0 // indirect - github.com/gookit/color v1.2.2 - github.com/jhump/protoreflect v1.6.0 + github.com/jedib0t/go-pretty v1.0.1-0.20200513162803-d24d83bda5d4 + github.com/jhump/protoreflect v1.6.1 github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f - github.com/klauspost/compress v1.10.1 // indirect - github.com/mattn/go-runewidth v0.0.8 // indirect + github.com/klauspost/compress v1.10.9 // indirect github.com/mitchellh/go-homedir v1.1.0 - github.com/olekukonko/tablewriter v0.0.4 + github.com/mitchellh/mapstructure v1.3.2 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible - github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 + github.com/pierrec/lz4 v2.5.2+incompatible // indirect + github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c - golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 // indirect - golang.org/x/net v0.0.0-20200219183655-46282727080f // indirect - golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect - google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5 // indirect + go.mongodb.org/mongo-driver v1.3.4 // indirect + golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect + golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect + golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 // indirect + golang.org/x/text v0.3.3 // indirect + google.golang.org/genproto v0.0.0-20200619004808-3e7fca5c55db // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 ) diff --git a/go.sum b/go.sum index 6864105..edc2e65 100644 --- a/go.sum +++ b/go.sum @@ -1,33 +1,37 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Shopify/sarama v1.26.1 h1:3jnfWKD7gVwbB1KSy/lE0szA9duPuSFLViK0o/d3DgA= -github.com/Shopify/sarama v1.26.1/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= +github.com/Shopify/sarama v1.26.4 h1:+17TxUq/PJEAfZAll0T7XJjSgQWCpaQSoki/x5yN8o8= +github.com/Shopify/sarama v1.26.4/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.7.1 h1:G1i02OhUbRi2nJxcNkwJaY/J1gHXj9tt72qN6ZouLFQ= -github.com/alecthomas/chroma v0.7.1/go.mod h1:gHw09mkX1Qp80JlYbmN9L3+4R5o6DJJ3GRShh+AICNc= +github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI= +github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= -github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= +github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/brianvoe/gofakeit/v4 v4.2.3 h1:GhdYTF4DOOMXyxizWG5FvTZkhRGvs2y3uLaOWEYu7b0= -github.com/brianvoe/gofakeit/v4 v4.2.3/go.mod h1:GC/GhKWdGJ2eskBf4zGdjo3eHj8rX4E9hFLFg0bqK4s= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/brianvoe/gofakeit/v4 v4.3.0 h1:y8octMlc4cmDra6sIst89NHEjZuWYmZDl9H0i5wjTvY= +github.com/brianvoe/gofakeit/v4 v4.3.0/go.mod h1:GC/GhKWdGJ2eskBf4zGdjo3eHj8rX4E9hFLFg0bqK4s= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= -github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -44,6 +48,43 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/frankban/quicktest v1.7.2 h1:2QxQoC1TS09S7fhCPsrvqYdvP1H5M1P1ih5ABm3BTYk= github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= +github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= +github.com/go-openapi/errors v0.19.2 h1:a2kIyV3w+OS3S97zxUndRVD46+FhGOUBDFY7nmu4CsY= +github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/errors v0.19.6 h1:xZMThgv5SQ7SMbWtKFkCf9bBdvR2iEyw9k3zGZONuys= +github.com/go-openapi/errors v0.19.6/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= +github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.5 h1:0utjKrw+BAh8s57XE9Xz8DUBsVvPmRUB6styvl9wWIM= +github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -52,31 +93,48 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/gookit/color v1.2.2 h1:IPG03BHqn23rgU597fFC8UNdlvEbQrZxYQyZqE0wkDw= -github.com/gookit/color v1.2.2/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jedib0t/go-pretty v1.0.1-0.20200513162803-d24d83bda5d4 h1:i9e4kt2WG2kirZHLlWwk8GugUPEsS1oyuWVTBamsP8Y= +github.com/jedib0t/go-pretty v1.0.1-0.20200513162803-d24d83bda5d4/go.mod h1:u8eOgBmRM7Jk61BPoFrmqZzUYaq6Rgr90krRqWBCfvM= +github.com/jhump/protoreflect v1.6.1 h1:4/2yi5LyDPP7nN+Hiird1SAJ6YoxUm13/oxHGRnbPd8= +github.com/jhump/protoreflect v1.6.1/go.mod h1:RZQ/lnuN+zqeRVpQigTwO6o0AJUkxbnSnpuG7toUTG4= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f h1:dKccXx7xA56UNqOcFIbuqFjAWPVtP688j5QMgmo6OHU= github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f/go.mod h1:4rEELDSfUAlBSyUjPG0JnaNGjf13JySHFeRdD/3dLP0= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.8 h1:VMAMUUOh+gaxKTMk+zqbjsSjsIcUcL/LF4o63i82QyA= github.com/klauspost/compress v1.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.1 h1:a/QY0o9S6wCi0XhxaMX/QmusicNUqCqFugR6WKPOSoQ= -github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.10.9 h1:pPRt1Z78crspaHISkpSSHjDlx+Tt9suHe519dsI0vF4= +github.com/klauspost/compress v1.10.9/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= @@ -84,50 +142,91 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= -github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= -github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= -github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= +github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.4.1+incompatible h1:mFe7ttWaflA46Mhqh+jUfjp2qTbPYxLB2/OyBppH9dg= github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= +github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563 h1:dY6ETXrvDG7Sa4vE8ZQG4yqWg6UnOcbqTAahkV813vQ= github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c h1:u40Z8hqBAAQyv+vATcGgV0YCnDjqSL7/q/JyPhhJSPk= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v0.0.0-20180714160509-73f8eece6fdc/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xdg/stringprep v1.0.0 h1:d9X0esnoa3dFsV0FG35rAT0RIhYFlPq7MiP+DW89La0= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.mongodb.org/mongo-driver v1.0.3 h1:GKoji1ld3tw2aC+GX1wbr/J2fX13yNacEYoJ8Nhr0yU= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.3.4 h1:zs/dKNwX0gYUtzwrN9lLiR15hCO0nDwQj5xXx+vjCdE= +go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS3gZBapIE= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72 h1:+ELyKg6m8UBf0nPFSqD0mi7zUfwPyXo23HNjMnXPz7w= golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= +golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -135,29 +234,54 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJV golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200219183655-46282727080f h1:dB42wwhNuwPvh8f+5zZWNcU+F2Xs/B9wXXwvUCOH7r8= -golang.org/x/net v0.0.0-20200219183655-46282727080f/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -165,20 +289,35 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5 h1:jB9+PJSvu5tBfmJHy/OVapFdjDF3WvpkqRhxqrmzoEU= -google.golang.org/genproto v0.0.0-20200218151345-dad8c97a84f5/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200619004808-3e7fca5c55db h1:Q5+mRMPseAnmi+ah5YkFXuVnZqUTgxmQF6e4PnjSkcE= +google.golang.org/genproto v0.0.0-20200619004808-3e7fca5c55db/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/grpc v1.8.0 h1:HN69LlNA/SpyBIRxTfuU0QOntYfdeEeBWlVhRHRCOyw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/jcmturner/aescts.v1 v1.0.1 h1:cVVZBK2b1zY26haWB4vbBiZrfFQnfbTVrE3xZq6hrEw= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN5hSM= @@ -193,5 +332,8 @@ gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= +gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/color_palette.go b/internal/color_palette.go index 32dfd14..ec8641b 100644 --- a/internal/color_palette.go +++ b/internal/color_palette.go @@ -4,15 +4,14 @@ import ( "fmt" "strings" - "github.com/gookit/color" + "github.com/jedib0t/go-pretty/text" ) var ( - yellow = color.Warn.Render - green = color.Info.Render - err = color.Error.Render - bold = color.Bold.Render - red = color.Red.Render + yellow = text.FgHiYellow + green = text.FgHiGreen + bold = text.Bold + red = text.FgHiRed ) type painter func(a ...interface{}) string @@ -37,11 +36,6 @@ func Red(input interface{}, colorEnabled bool) interface{} { return colorIfEnabled(input, red, colorEnabled) } -// Err returns the input in red background if coloring is enabled. -func Err(input interface{}, colorEnabled bool) interface{} { - return colorIfEnabled(input, err, colorEnabled) -} - // RedIfTrue highlights the input in red, if coloring is enabled and the evaluation function returns true. func RedIfTrue(input interface{}, eval func() bool, colorEnabled bool) interface{} { return colorIfEnabled(input, red, colorEnabled && eval()) @@ -60,9 +54,9 @@ func HighlightGroupState(state string, colorEnabled bool) string { return fmt.Sprint(s) } -func colorIfEnabled(input interface{}, p painter, colorEnabled bool) interface{} { +func colorIfEnabled(input interface{}, color text.Color, colorEnabled bool) interface{} { if colorEnabled { - return p(input) + return text.Colors{color}.Sprint(input) } return input } diff --git a/internal/counter.go b/internal/counter.go index 6d609e0..5cd4f18 100644 --- a/internal/counter.go +++ b/internal/counter.go @@ -1,11 +1,9 @@ package internal import ( - "fmt" - "os" - "github.com/dustin/go-humanize" - "github.com/olekukonko/tablewriter" + + "github.com/xitonix/trubka/internal/output/format/tabular" ) type stats struct { @@ -26,17 +24,10 @@ func (c *Counter) PrintAsTable(highlight bool) { if c == nil || len(c.topicStats) == 0 { return } - table := tablewriter.NewWriter(os.Stdout) - headers := []string{"Topic", "Succeeded", "Failed"} - table.SetHeader(headers) - table.SetColumnAlignment([]int{ - tablewriter.ALIGN_LEFT, - tablewriter.ALIGN_CENTER, - tablewriter.ALIGN_CENTER, - }) - table.SetRowLine(true) - - rows := make([][]string, 0) + table := tabular.NewTable(highlight, + tabular.C("Topic").Align(tabular.AlignLeft), + tabular.C("Succeeded"), + tabular.C("Failed")) for topic, s := range c.topicStats { failed := RedIfTrue(humanize.Comma(s.failure), func() bool { @@ -46,10 +37,10 @@ func (c *Counter) PrintAsTable(highlight bool) { succeeded := GreenIfTrue(humanize.Comma(s.success), func() bool { return s.success > 0 }, highlight) - rows = append(rows, []string{topic, fmt.Sprint(succeeded), fmt.Sprint(failed)}) + table.AddRow(topic, succeeded, failed) } - fmt.Print("\nSUMMARY\n") - table.AppendBulk(rows) + table.SetTitle("SUMMARY") + table.TitleAlignment(tabular.AlignCenter) table.Render() } diff --git a/internal/output/format/formatters.go b/internal/output/format/formatters.go new file mode 100644 index 0000000..58794ae --- /dev/null +++ b/internal/output/format/formatters.go @@ -0,0 +1,85 @@ +package format + +import ( + "fmt" + "strings" + + "github.com/dustin/go-humanize" + "github.com/jedib0t/go-pretty/text" +) + +const stableGroupLabel = "Stable" + +func Bold(val interface{}, enableColor bool) interface{} { + if !enableColor { + return val + } + return text.Bold.Sprint(val) +} + +func GreenLabel(val interface{}, enableColor bool) interface{} { + if !enableColor { + return fmt.Sprintf("[%v]", val) + } + return text.Colors{text.Bold, text.BgGreen, text.FgWhite}.Sprintf(" %v ", val) +} + +func Warn(input int64, colorEnabled, greenOtherwise bool) interface{} { + humanised := humanize.Comma(input) + if !colorEnabled { + return humanised + } + if input > 0 { + return text.Colors{text.FgHiYellow, text.Bold}.Sprint(humanised) + } + if greenOtherwise { + return text.Colors{text.FgHiGreen, text.Bold}.Sprint(humanised) + } + return humanised +} + +func GroupStateLabel(state string, enableColor bool) string { + if strings.EqualFold(state, stableGroupLabel) { + return fmt.Sprint(GreenLabel(stableGroupLabel, enableColor)) + } + return state +} + +func BoldGreen(val interface{}, enableColor bool) interface{} { + if !enableColor { + return val + } + return text.Colors{text.Bold, text.FgHiGreen}.Sprint(val) +} + +func UnderlineLen(input string, length int) string { + return fmt.Sprintf("%s\n%s", input, underline(length)) +} + +func Underline(input string) string { + return UnderlineLen(input, len(input)) +} + +func UnderlinedTitleWithCount(title string, count int) string { + title = titleWithCount(title, count) + return fmt.Sprintf("%s\n%s", title, underline(len(title))) +} + +func WithCount(title string, count int) string { + return titleWithCount(title, count) +} + +func titleWithCount(title string, count int) string { + return fmt.Sprintf("%s (%d)", title, count) +} + +func underline(length int) string { + return strings.Repeat("─", length) +} + +func SpaceIfEmpty(in string) string { + if len(in) > 0 { + return in + } + return " " +} diff --git a/internal/output/format/list/bullet.go b/internal/output/format/list/bullet.go new file mode 100644 index 0000000..9997e7f --- /dev/null +++ b/internal/output/format/list/bullet.go @@ -0,0 +1,65 @@ +package list + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/jedib0t/go-pretty/list" +) + +type Bullet struct { + writer list.Writer + title string + caption string +} + +func NewBullet() *Bullet { + w := list.NewWriter() + w.SetOutputMirror(os.Stdout) + w.SetStyle(list.StyleBulletCircle) + w.Style().LinePrefix = " " + return &Bullet{ + writer: w, + } +} + +func (b *Bullet) SetTitle(title string) { + b.title = title +} + +func (b *Bullet) SetCaption(caption string) { + b.caption = " " + caption +} + +func (b *Bullet) SetOutput(out io.Writer) { + b.writer.SetOutputMirror(out) +} + +func (b *Bullet) Render() { + if len(b.title) > 0 { + underline := strings.Repeat("─", len(b.title)) + fmt.Printf("%s\n%s\n", b.title, underline) + } + b.writer.Render() + if len(b.caption) > 0 { + fmt.Printf("\n%s", b.caption) + } +} + +func (b *Bullet) AsTree() { + b.writer.SetStyle(list.StyleConnectedRounded) +} + +func (b *Bullet) AddItem(item interface{}) { + b.writer.AppendItem(item) +} + +func (b *Bullet) Intend() { + b.writer.Indent() +} + +func (b *Bullet) UnIntend() { + b.writer.UnIndent() +} diff --git a/internal/output/format/tabular/column.go b/internal/output/format/tabular/column.go new file mode 100644 index 0000000..23c4f9a --- /dev/null +++ b/internal/output/format/tabular/column.go @@ -0,0 +1,126 @@ +package tabular + +import ( + "fmt" + "strconv" + + "github.com/dustin/go-humanize" + "github.com/jedib0t/go-pretty/table" + "github.com/jedib0t/go-pretty/text" +) + +type Alignment int + +const ( + AlignLeft Alignment = iota + 1 + AlignCenter + AlignJustify + AlignRight +) + +type VAlignment int + +const ( + VAlignTop Alignment = iota + 1 + VAlignMiddle + VAlignBottom +) + +type Column struct { + Header string + humanize bool + greenOtherwise bool + warningLevel *int64 + config table.ColumnConfig +} + +func C(header string) *Column { + return &Column{ + Header: header, + config: table.ColumnConfig{ + Name: header, + Align: text.Align(AlignCenter), + AlignHeader: text.Align(AlignCenter), + VAlign: text.VAlign(VAlignTop), + AlignFooter: text.Align(AlignRight), + }, + } +} + +func (c *Column) configuration(enableColor bool) table.ColumnConfig { + if c.humanize || c.warningLevel != nil { + transformer := func(val interface{}) string { + switch value := val.(type) { + case int: + return c.renderNumber(enableColor, int64(value)) + case int64: + return c.renderNumber(enableColor, value) + } + + return fmt.Sprint(val) + } + c.config.Transformer = transformer + c.config.TransformerFooter = transformer + } + return c.config +} + +func (c *Column) Humanize() *Column { + c.humanize = true + return c +} + +func (c *Column) Warn(level int64, greenOtherwise bool) *Column { + c.warningLevel = &level + c.greenOtherwise = greenOtherwise + return c +} + +func (c *Column) HAlign(alignment Alignment) *Column { + c.config.AlignHeader = text.Align(alignment) + return c +} + +func (c *Column) FAlign(alignment Alignment) *Column { + c.config.AlignFooter = text.Align(alignment) + return c +} + +func (c *Column) Align(alignment Alignment) *Column { + c.config.Align = text.Align(alignment) + return c +} + +func (c *Column) VAlign(alignment VAlignment) *Column { + c.config.VAlign = text.VAlign(alignment) + return c +} + +func (c *Column) MinWidth(width int) *Column { + c.config.WidthMin = width + return c +} + +func (c *Column) MaxWidth(width int) *Column { + c.config.WidthMax = width + return c +} + +func (c *Column) renderNumber(enableColor bool, value int64) string { + var rendered string + if c.humanize { + rendered = humanize.Comma(value) + } else { + rendered = strconv.FormatInt(value, 10) + } + if enableColor && c.warningLevel != nil { + if value > *c.warningLevel { + return text.Colors{text.FgHiYellow, text.Bold}.Sprint(rendered) + } + if c.greenOtherwise { + return text.Colors{text.FgHiGreen, text.Bold}.Sprint(rendered) + } + } + + return rendered +} diff --git a/internal/output/format/tabular/table.go b/internal/output/format/tabular/table.go new file mode 100644 index 0000000..844c6e2 --- /dev/null +++ b/internal/output/format/tabular/table.go @@ -0,0 +1,78 @@ +package tabular + +import ( + "os" + "runtime" + + "github.com/jedib0t/go-pretty/table" + "github.com/jedib0t/go-pretty/text" +) + +type Table struct { + writer table.Writer + style *table.Style +} + +func NewTable(enableColor bool, columns ...*Column) *Table { + t := table.NewWriter() + if runtime.GOOS == "windows" { + t.SetStyle(table.StyleLight) + } else { + t.SetStyle(table.StyleRounded) + } + t.SetOutputMirror(os.Stdout) + headers := make(table.Row, len(columns)) + configs := make([]table.ColumnConfig, len(columns)) + for i, column := range columns { + headers[i] = column.Header + configs[i] = column.configuration(enableColor) + } + t.AppendHeader(headers) + t.SetColumnConfigs(configs) + style := t.Style() + style.Title.Align = text.AlignLeft + style.Options.SeparateRows = true + style.Format.Header = text.FormatDefault + style.Format.Footer = text.FormatDefault + return &Table{ + writer: t, + style: style, + } +} + +func (t *Table) TitleAlignment(alignment Alignment) { + t.style.Title.Align = text.Align(alignment) +} + +func (t *Table) AddRow(values ...interface{}) { + row := make(table.Row, len(values)) + for i, value := range values { + row[i] = value + } + t.writer.AppendRow(row) +} + +func (t *Table) SetTitle(title string) { + t.writer.SetTitle(title) +} + +func (t *Table) SetCaption(caption string) { + t.writer.SetCaption(" " + caption) +} + +func (t *Table) DisableRowSeparators() { + t.style.Options.SeparateRows = false +} + +// AddFooter use "" for the columns without any footer value. +func (t *Table) AddFooter(values ...interface{}) { + row := make(table.Row, len(values)) + for i, value := range values { + row[i] = value + } + t.writer.AppendFooter(row) +} + +func (t *Table) Render() { + t.writer.Render() +} diff --git a/internal/output/output.go b/internal/output/output.go index 6d9f677..9ac22f9 100644 --- a/internal/output/output.go +++ b/internal/output/output.go @@ -2,53 +2,10 @@ package output import ( "fmt" - "io" - "strings" - - "github.com/olekukonko/tablewriter" ) -func Underline(in string) string { - in = strings.TrimSpace(in) - if len(in) == 0 { - return "" - } - return in + "\n" + strings.Repeat("-", len(in)) -} - -func UnderlineWithCount(title string, count int) { - title = Underline(titledCounter(title, count)) - fmt.Printf("\n%s\n", title) -} - -func WithCount(title string, count int) { - fmt.Printf("\n%s\n", titledCounter(title, count)) -} - -func titledCounter(title string, count int) string { - return fmt.Sprintf("%s (%d)", title, count) -} - -func InitStaticTable(writer io.Writer, headers ...TableHeader) *tablewriter.Table { - table := tablewriter.NewWriter(writer) - headerTitles := make([]string, len(headers)) - alignments := make([]int, len(headers)) - var i int - for _, header := range headers { - headerTitles[i] = header.Key - alignments[i] = header.Alignment - i++ - } - table.SetHeader(headerTitles) - table.SetColumnAlignment(alignments) - table.SetAutoWrapText(false) - table.SetRowLine(true) - return table -} - -func SpaceIfEmpty(in string) string { - if len(in) > 0 { - return in +func NewLines(count int) { + for i := 0; i < count; i++ { + fmt.Println() } - return " " } diff --git a/internal/output/table_header.go b/internal/output/table_header.go deleted file mode 100644 index f4e95d2..0000000 --- a/internal/output/table_header.go +++ /dev/null @@ -1,13 +0,0 @@ -package output - -type TableHeader struct { - Key string - Alignment int -} - -func H(key string, alignment int) TableHeader { - return TableHeader{ - Key: key, - Alignment: alignment, - } -} diff --git a/internal/utils.go b/internal/utils.go index 6bdcbce..19557e8 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -1,6 +1,7 @@ package internal import ( + "errors" "fmt" "os" "os/signal" @@ -21,12 +22,12 @@ func FormatTime(t time.Time) string { return t.Format("02-01-2006T15:04:05.999999999") } -func GetNotFoundMessage(entity, filterName string, ex *regexp.Regexp) string { +func NotFoundError(entity, filterName string, ex *regexp.Regexp) error { msg := fmt.Sprintf("No %s has been found.", entity) if ex != nil { msg += fmt.Sprintf(" You might need to tweak the %s filter (%s).", filterName, ex.String()) } - return msg + return errors.New(msg) } func FormatTimeUTC(t time.Time) string { diff --git a/kafka/broker.go b/kafka/broker.go index 3c610cb..600cbae 100644 --- a/kafka/broker.go +++ b/kafka/broker.go @@ -1,13 +1,13 @@ package kafka import ( - "fmt" - "github.com/Shopify/sarama" "github.com/xitonix/trubka/internal" ) +const ControllerBrokerLabel = "★" + type Broker struct { Address string ID int32 @@ -28,12 +28,11 @@ func NewBroker(broker *sarama.Broker, controllerId int32) *Broker { } } -func (b *Broker) String() string { - var controller string +func (b *Broker) MarkedHostName() string { if b.IsController { - controller = " [C]" + return b.Host + ControllerBrokerLabel } - return fmt.Sprintf("%d: %s%s", b.ID, b.Host, controller) + return b.Host } type BrokersById []*Broker diff --git a/kafka/broker_meta.go b/kafka/broker_meta.go index ef036b6..f08b239 100644 --- a/kafka/broker_meta.go +++ b/kafka/broker_meta.go @@ -27,7 +27,7 @@ func newAPI(name string, key, minVer, maxVer int16) *API { } func (a *API) String() string { - return fmt.Sprintf("ver. %d ≤ %d%s ≤ ver. %d", a.MinVersion, a.Key, a.Name, a.MaxVersion) + return fmt.Sprintf("v%d ≤ [%2d] %s ≤ v%d", a.MinVersion, a.Key, a.Name, a.MaxVersion) } type APIByCode []*API diff --git a/kafka/consumer_group_details.go b/kafka/consumer_group_details.go index ceada6f..eeaed80 100644 --- a/kafka/consumer_group_details.go +++ b/kafka/consumer_group_details.go @@ -1,8 +1,6 @@ package kafka import ( - "fmt" - "github.com/Shopify/sarama" ) @@ -31,15 +29,6 @@ func (c ConsumerGroupDetailsByName) Less(i, j int) bool { return c[i].Name < c[j].Name } -func (c *ConsumerGroupDetails) String() string { - return fmt.Sprintf(" Name: %s\n Coordinator: %s\n State: %s\n Protocol: %s\nProtocol Type: %s", - c.Name, - c.Coordinator.Host, - c.State, - c.Protocol, - c.ProtocolType) -} - type GroupMemberDetails struct { ClientHost string TopicPartitions TopicPartitions diff --git a/kafka/manager.go b/kafka/manager.go index cf5828a..94ed057 100644 --- a/kafka/manager.go +++ b/kafka/manager.go @@ -301,8 +301,8 @@ func (m *Manager) DescribeGroup(ctx context.Context, group string, includeMember return nil, fmt.Errorf("failed to fetch the group coordinator details: %w", err) } result.Coordinator = Broker{ - Address: coordinator.Addr(), - ID: coordinator.ID(), + Host: internal.RemovePort(coordinator.Addr()), + ID: coordinator.ID(), } if includeMembers { for name, description := range d.Members { diff --git a/kafka/topic_partitions.go b/kafka/topic_partitions.go index 2d2ef37..eb314b0 100644 --- a/kafka/topic_partitions.go +++ b/kafka/topic_partitions.go @@ -8,17 +8,30 @@ import ( type TopicPartitions map[string][]int32 -func (t TopicPartitions) SortedPartitionsString(topic string) string { +func (t TopicPartitions) SortedPartitions(topic string) []int { partitions, ok := t[topic] if !ok { - return "" + return []int{} } - partStr := make([]string, len(partitions)) + + sorted := make([]int, len(partitions)) for i := 0; i < len(partitions); i++ { - partStr[i] = strconv.FormatInt(int64(partitions[i]), 10) + sorted[i] = int(partitions[i]) + } + sort.Ints(sorted) + return sorted +} + +func (t TopicPartitions) SortedPartitionsString(topic string) string { + sorted := t.SortedPartitions(topic) + if len(sorted) == 0 { + return "" + } + partitions := make([]string, len(sorted)) + for i, p := range sorted { + partitions[i] = strconv.Itoa(p) } - sort.Strings(partStr) - return strings.Join(partStr, ",") + return strings.Join(partitions, ",") } func (t TopicPartitions) SortedTopics() []string { diff --git a/main.go b/main.go index fe11a5e..8b248ee 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,6 @@ func main() { func exit(err error) { msg := fmt.Sprintf("ERROR: %s", internal.Title(err)) - fmt.Println(internal.Err(msg, enabledColor)) + fmt.Println(internal.Red(msg, enabledColor)) os.Exit(1) }