diff options
Diffstat (limited to 'internal/mapr')
| -rw-r--r-- | internal/mapr/globalgroupset.go | 4 | ||||
| -rw-r--r-- | internal/mapr/groupsetresult.go | 101 | ||||
| -rw-r--r-- | internal/mapr/groupsetresult_renderer_test.go | 113 | ||||
| -rw-r--r-- | internal/mapr/result_renderer.go | 34 |
4 files changed, 177 insertions, 75 deletions
diff --git a/internal/mapr/globalgroupset.go b/internal/mapr/globalgroupset.go index 2b12898..4f1e5ba 100644 --- a/internal/mapr/globalgroupset.go +++ b/internal/mapr/globalgroupset.go @@ -86,8 +86,8 @@ func (g *GlobalGroupSet) WriteResult(query *Query, finalResult bool) error { } // Result returns the result of the mapreduce aggregation as a string. -func (g *GlobalGroupSet) Result(query *Query, rowsLimit int) (string, int, error) { +func (g *GlobalGroupSet) Result(query *Query, rowsLimit int, renderer ResultRenderer) (string, int, error) { g.semaphore <- struct{}{} defer func() { <-g.semaphore }() - return g.GroupSet.Result(query, rowsLimit) + return g.GroupSet.Result(query, rowsLimit, renderer) } diff --git a/internal/mapr/groupsetresult.go b/internal/mapr/groupsetresult.go index 26e4b12..c87c22f 100644 --- a/internal/mapr/groupsetresult.go +++ b/internal/mapr/groupsetresult.go @@ -6,15 +6,13 @@ import ( "os" "strings" - "github.com/mimecast/dtail/internal/color" - "github.com/mimecast/dtail/internal/config" "github.com/mimecast/dtail/internal/io/dlog" "github.com/mimecast/dtail/internal/io/pool" "github.com/mimecast/dtail/internal/protocol" ) // Result returns a nicely formated result of the query from the group set. -func (g *GroupSet) Result(query *Query, rowsLimit int) (string, int, error) { +func (g *GroupSet) Result(query *Query, rowsLimit int, renderer ResultRenderer) (string, int, error) { rows, columnWidths, err := g.result(query, true) if err != nil { return "", 0, err @@ -27,97 +25,69 @@ func (g *GroupSet) Result(query *Query, rowsLimit int) (string, int, error) { sb := pool.BuilderBuffer.Get().(*strings.Builder) defer pool.RecycleBuilderBuffer(sb) - g.resultWriteFormattedHeader(query, sb, lastColumn, rowsLimit, columnWidths) - g.resultWriteFormattedHeaderRowSeparator(query, sb, lastColumn, columnWidths) - g.resultWriteFormattedData(query, sb, lastColumn, rowsLimit, columnWidths, rows) + if renderer == nil { + renderer = PlainResultRenderer() + } + + g.resultWriteFormattedHeader(query, renderer, sb, lastColumn, rowsLimit, columnWidths) + g.resultWriteFormattedHeaderRowSeparator(query, renderer, sb, lastColumn, columnWidths) + g.resultWriteFormattedData(query, renderer, sb, lastColumn, rowsLimit, columnWidths, rows) return sb.String(), len(rows), nil } // Write a nicely formatted header for the result data. -func (g *GroupSet) resultWriteFormattedHeader(query *Query, sb *strings.Builder, +func (g *GroupSet) resultWriteFormattedHeader(query *Query, renderer ResultRenderer, sb *strings.Builder, lastColumn, rowsLimit int, columnWidths []int) { for i, sc := range query.Select { format := fmt.Sprintf(" %%%ds ", columnWidths[i]) str := fmt.Sprintf(format, sc.FieldStorage) - g.resultWriteFormattedHeaderEntry(query, sb, sc, str) + g.resultWriteFormattedHeaderEntry(query, renderer, sb, sc, str) if i == lastColumn { continue } - g.resultWriteFormattedHeaderEntrySeparator(query, sb) + g.resultWriteFormattedHeaderEntrySeparator(renderer, sb) } sb.WriteString("\n") } -func (g *GroupSet) resultWriteFormattedHeaderEntry(query *Query, sb *strings.Builder, +func (g *GroupSet) resultWriteFormattedHeaderEntry(query *Query, renderer ResultRenderer, sb *strings.Builder, sc selectCondition, str string) { - if config.Client.TermColorsEnable { - attrs := []color.Attribute{config.Client.TermColors.MaprTable.HeaderAttr} - if sc.FieldStorage == query.OrderBy { - attrs = append(attrs, config.Client.TermColors.MaprTable.HeaderSortKeyAttr) - } - for _, groupBy := range query.GroupBy { - if sc.FieldStorage == groupBy { - attrs = append(attrs, config.Client.TermColors.MaprTable.HeaderGroupKeyAttr) - break - } + isGroupKey := false + for _, groupBy := range query.GroupBy { + if sc.FieldStorage == groupBy { + isGroupKey = true + break } - color.PaintWithAttrs(sb, str, - config.Client.TermColors.MaprTable.HeaderFg, - config.Client.TermColors.MaprTable.HeaderBg, - attrs) - - } else { - sb.WriteString(str) } + renderer.WriteHeaderEntry(sb, str, sc.FieldStorage == query.OrderBy, isGroupKey) } -func (g *GroupSet) resultWriteFormattedHeaderEntrySeparator(query *Query, sb *strings.Builder) { - if config.Client.TermColorsEnable { - color.PaintWithAttr(sb, protocol.FieldDelimiter, - config.Client.TermColors.MaprTable.HeaderDelimiterFg, - config.Client.TermColors.MaprTable.HeaderDelimiterBg, - config.Client.TermColors.MaprTable.HeaderDelimiterAttr) - } else { - sb.WriteString(protocol.FieldDelimiter) - } +func (g *GroupSet) resultWriteFormattedHeaderEntrySeparator(renderer ResultRenderer, sb *strings.Builder) { + renderer.WriteHeaderDelimiter(sb, protocol.FieldDelimiter) } // This writes a nicely formatted line separating the header and the data. -func (g *GroupSet) resultWriteFormattedHeaderRowSeparator(query *Query, sb *strings.Builder, +func (g *GroupSet) resultWriteFormattedHeaderRowSeparator(query *Query, renderer ResultRenderer, sb *strings.Builder, lastColumn int, columnWidths []int) { for i := 0; i < len(query.Select); i++ { str := fmt.Sprintf("-%s-", strings.Repeat("-", columnWidths[i])) - if config.Client.TermColorsEnable { - color.PaintWithAttr(sb, str, - config.Client.TermColors.MaprTable.HeaderDelimiterFg, - config.Client.TermColors.MaprTable.HeaderDelimiterBg, - config.Client.TermColors.MaprTable.HeaderDelimiterAttr) - } else { - sb.WriteString(str) - } + renderer.WriteHeaderDelimiter(sb, str) if i == lastColumn { continue } - if config.Client.TermColorsEnable { - color.PaintWithAttr(sb, protocol.FieldDelimiter, - config.Client.TermColors.MaprTable.HeaderDelimiterFg, - config.Client.TermColors.MaprTable.HeaderDelimiterBg, - config.Client.TermColors.MaprTable.HeaderDelimiterAttr) - } else { - sb.WriteString(protocol.FieldDelimiter) - } + renderer.WriteHeaderDelimiter(sb, protocol.FieldDelimiter) } sb.WriteString("\n") } // Write the result data nicely formatted. -func (g *GroupSet) resultWriteFormattedData(query *Query, sb *strings.Builder, +func (g *GroupSet) resultWriteFormattedData(query *Query, renderer ResultRenderer, sb *strings.Builder, lastColumn, rowsLimit int, columnWidths []int, rows []result) { for i, r := range rows { @@ -125,37 +95,22 @@ func (g *GroupSet) resultWriteFormattedData(query *Query, sb *strings.Builder, break } for j, value := range r.values { - g.resultWriteFormattedDataEntry(query, sb, columnWidths, j, value) + g.resultWriteFormattedDataEntry(renderer, sb, columnWidths, j, value) if j == lastColumn { continue } - // Now, write the data entry separator. - if config.Client.TermColorsEnable { - color.PaintWithAttr(sb, protocol.FieldDelimiter, - config.Client.TermColors.MaprTable.DelimiterFg, - config.Client.TermColors.MaprTable.DelimiterBg, - config.Client.TermColors.MaprTable.DelimiterAttr) - } else { - sb.WriteString(protocol.FieldDelimiter) - } + renderer.WriteDataDelimiter(sb, protocol.FieldDelimiter) } sb.WriteString("\n") } } -func (g *GroupSet) resultWriteFormattedDataEntry(query *Query, sb *strings.Builder, +func (g *GroupSet) resultWriteFormattedDataEntry(renderer ResultRenderer, sb *strings.Builder, columnWidths []int, j int, value string) { format := fmt.Sprintf(" %%%ds ", columnWidths[j]) str := fmt.Sprintf(format, value) - if config.Client.TermColorsEnable { - color.PaintWithAttr(sb, str, - config.Client.TermColors.MaprTable.DataFg, - config.Client.TermColors.MaprTable.DataBg, - config.Client.TermColors.MaprTable.DataAttr) - } else { - sb.WriteString(str) - } + renderer.WriteDataEntry(sb, str) } func (*GroupSet) writeQueryFile(query *Query) error { diff --git a/internal/mapr/groupsetresult_renderer_test.go b/internal/mapr/groupsetresult_renderer_test.go new file mode 100644 index 0000000..53f45d5 --- /dev/null +++ b/internal/mapr/groupsetresult_renderer_test.go @@ -0,0 +1,113 @@ +package mapr + +import ( + "strings" + "testing" +) + +func TestGroupSetResultUsesProvidedRenderer(t *testing.T) { + query, err := NewQuery("select host,count(value) from stats group by host order by count(value)") + if err != nil { + t.Fatalf("Unable to parse query: %v", err) + } + + groupSet := NewGroupSet() + set := groupSet.GetSet("host-a") + if err := set.Aggregate("host", Last, "host-a", false); err != nil { + t.Fatalf("Unable to aggregate host field: %v", err) + } + if err := set.Aggregate("count(value)", Count, "", false); err != nil { + t.Fatalf("Unable to aggregate count field: %v", err) + } + + renderer := &recordingRenderer{} + result, numRows, err := groupSet.Result(query, 10, renderer) + if err != nil { + t.Fatalf("Unable to render result: %v", err) + } + if numRows != 1 { + t.Fatalf("Expected one row, got %d", numRows) + } + if len(renderer.headerCalls) != 2 { + t.Fatalf("Expected two header calls, got %d", len(renderer.headerCalls)) + } + if renderer.headerCalls[0].isSortKey || !renderer.headerCalls[0].isGroupKey { + t.Fatalf("Unexpected flags for group key header: %+v", renderer.headerCalls[0]) + } + if !renderer.headerCalls[1].isSortKey || renderer.headerCalls[1].isGroupKey { + t.Fatalf("Unexpected flags for sort key header: %+v", renderer.headerCalls[1]) + } + if len(renderer.headerDelimiters) == 0 { + t.Fatal("Expected header delimiters to be rendered") + } + if len(renderer.dataDelimiters) == 0 { + t.Fatal("Expected data delimiters to be rendered") + } + if !strings.Contains(result, "host-a") || !strings.Contains(result, "1") { + t.Fatalf("Expected rendered output to contain row data, got %q", result) + } +} + +func TestGroupSetResultFallsBackToPlainRenderer(t *testing.T) { + query, err := NewQuery("select count(value) from stats") + if err != nil { + t.Fatalf("Unable to parse query: %v", err) + } + + groupSet := NewGroupSet() + set := groupSet.GetSet("") + if err := set.Aggregate("count(value)", Count, "", false); err != nil { + t.Fatalf("Unable to aggregate count field: %v", err) + } + + result, numRows, err := groupSet.Result(query, 10, nil) + if err != nil { + t.Fatalf("Unable to render result with nil renderer: %v", err) + } + if numRows != 1 { + t.Fatalf("Expected one row, got %d", numRows) + } + if !strings.Contains(result, "count(value)") || !strings.Contains(result, "1") { + t.Fatalf("Expected plain rendered output, got %q", result) + } + if strings.Contains(result, "\x1b[") { + t.Fatalf("Expected plain output without ANSI escapes, got %q", result) + } +} + +type recordingRenderer struct { + headerCalls []headerCall + headerDelimiters []string + dataEntries []string + dataDelimiters []string +} + +type headerCall struct { + text string + isSortKey bool + isGroupKey bool +} + +func (r *recordingRenderer) WriteHeaderEntry(sb *strings.Builder, text string, isSortKey, isGroupKey bool) { + r.headerCalls = append(r.headerCalls, headerCall{ + text: text, + isSortKey: isSortKey, + isGroupKey: isGroupKey, + }) + sb.WriteString(text) +} + +func (r *recordingRenderer) WriteHeaderDelimiter(sb *strings.Builder, text string) { + r.headerDelimiters = append(r.headerDelimiters, text) + sb.WriteString(text) +} + +func (r *recordingRenderer) WriteDataEntry(sb *strings.Builder, text string) { + r.dataEntries = append(r.dataEntries, text) + sb.WriteString(text) +} + +func (r *recordingRenderer) WriteDataDelimiter(sb *strings.Builder, text string) { + r.dataDelimiters = append(r.dataDelimiters, text) + sb.WriteString(text) +} diff --git a/internal/mapr/result_renderer.go b/internal/mapr/result_renderer.go new file mode 100644 index 0000000..e03ed9a --- /dev/null +++ b/internal/mapr/result_renderer.go @@ -0,0 +1,34 @@ +package mapr + +import "strings" + +// ResultRenderer formats terminal table output for mapreduce results. +type ResultRenderer interface { + WriteHeaderEntry(sb *strings.Builder, text string, isSortKey, isGroupKey bool) + WriteHeaderDelimiter(sb *strings.Builder, text string) + WriteDataEntry(sb *strings.Builder, text string) + WriteDataDelimiter(sb *strings.Builder, text string) +} + +type plainResultRenderer struct{} + +// PlainResultRenderer returns a renderer that writes uncolored terminal output. +func PlainResultRenderer() ResultRenderer { + return plainResultRenderer{} +} + +func (plainResultRenderer) WriteHeaderEntry(sb *strings.Builder, text string, _, _ bool) { + sb.WriteString(text) +} + +func (plainResultRenderer) WriteHeaderDelimiter(sb *strings.Builder, text string) { + sb.WriteString(text) +} + +func (plainResultRenderer) WriteDataEntry(sb *strings.Builder, text string) { + sb.WriteString(text) +} + +func (plainResultRenderer) WriteDataDelimiter(sb *strings.Builder, text string) { + sb.WriteString(text) +} |
