From 1f903aec4ceb305d1d768527d1e68b217f1f062d Mon Sep 17 00:00:00 2001 From: Chris LaPointe Date: Tue, 25 Jul 2023 22:10:40 -0400 Subject: [PATCH] Bar & histo logscale (#98) Add graph-scaling support to bargraphs and histograms --- cmd/bargraph.go | 12 ++++- cmd/bargraph_test.go | 12 ++++- cmd/histo.go | 13 +++-- cmd/histo_test.go | 2 + docs/cli-help.md | Bin 15410 -> 15566 bytes docs/usage/aggregators.md | 4 ++ docs/usage/examples.md | 21 ++++++--- docs/usage/expressions.md | 4 +- pkg/expressions/stdlib/drawing.go | 26 ++++++++-- pkg/expressions/stdlib/drawing_test.go | 15 ++++-- pkg/multiterm/termrenderers/bargraph.go | 5 +- pkg/multiterm/termrenderers/bargraph_test.go | 22 ++++++++- pkg/multiterm/termrenderers/histoWriter.go | 8 +++- pkg/multiterm/termscaler/scale.go | 10 ++++ pkg/multiterm/termscaler/scale_test.go | 16 +++++++ pkg/multiterm/termunicode/bars.go | 34 +++----------- pkg/multiterm/termunicode/bars_test.go | 47 +++++++++++++++---- 17 files changed, 187 insertions(+), 64 deletions(-) diff --git a/cmd/bargraph.go b/cmd/bargraph.go index 666b55dc..f3ec5cff 100644 --- a/cmd/bargraph.go +++ b/cmd/bargraph.go @@ -16,14 +16,21 @@ go run . bars -sz -m "\[(.+?)\].*\" (\d+)" -e "{$ {buckettime {1} year nginx} {2 func bargraphFunction(c *cli.Context) error { var ( - stacked = c.Bool("stacked") - sortName = c.String(helpers.DefaultSortFlag.Name) + stacked = c.Bool("stacked") + sortName = c.String(helpers.DefaultSortFlag.Name) + scaleName = c.String(helpers.ScaleFlag.Name) ) vt := helpers.BuildVTermFromArguments(c) counter := aggregation.NewSubKeyCounter() writer := termrenderers.NewBarGraph(vt) writer.Stacked = stacked + if c.IsSet(helpers.ScaleFlag.Name) { + if stacked { + return cli.Exit("Unable to set graph scale on stacked graphs", helpers.ExitCodeInvalidUsage) + } + writer.Scaler = helpers.BuildScalerOrFail(scaleName) + } batcher := helpers.BuildBatcherFromArguments(c) ext := helpers.BuildExtractorFromArguments(c, batcher) @@ -72,6 +79,7 @@ func bargraphCommand() *cli.Command { helpers.SnapshotFlag, helpers.NoOutFlag, helpers.CSVFlag, + helpers.ScaleFlag, }, }) } diff --git a/cmd/bargraph_test.go b/cmd/bargraph_test.go index 597b904b..989a7ece 100644 --- a/cmd/bargraph_test.go +++ b/cmd/bargraph_test.go @@ -1,10 +1,20 @@ package cmd -import "testing" +import ( + "testing" + + "github.com/stretchr/testify/assert" +) func TestBarGraph(t *testing.T) { testCommandSet(t, bargraphCommand(), `-m "(.+) (\d+)" -e "{$ {1} {2}}" testdata/graph.txt`, `-o - -m "(.+) (\d+)" -e "{$ {1} {2}}" testdata/graph.txt`, + `-o - -m "(.+) (\d+)" -e "{$ {1} {2}}" --scale log10 testdata/graph.txt`, ) } + +func TestBarGraphCantScaleAndStack(t *testing.T) { + err := testCommand(bargraphCommand(), "--stacked --scale log10 testdata/graph.txt") + assert.Error(t, err) +} diff --git a/cmd/histo.go b/cmd/histo.go index 478ac1cb..98bd2f34 100644 --- a/cmd/histo.go +++ b/cmd/histo.go @@ -29,11 +29,12 @@ func writeHistoOutput(writer *termrenderers.HistoWriter, counter *aggregation.Ma func histoFunction(c *cli.Context) error { var ( - topItems = c.Int("n") - atLeast = c.Int64("atleast") - extra = c.Bool("extra") - all = c.Bool("all") - sortName = c.String(helpers.DefaultSortFlag.Name) + topItems = c.Int("n") + atLeast = c.Int64("atleast") + extra = c.Bool("extra") + all = c.Bool("all") + sortName = c.String(helpers.DefaultSortFlag.Name) + scalerName = c.String(helpers.ScaleFlag.Name) ) vt := helpers.BuildVTermFromArguments(c) @@ -41,6 +42,7 @@ func histoFunction(c *cli.Context) error { writer := termrenderers.NewHistogram(vt, topItems) writer.ShowBar = c.Bool("bars") || extra writer.ShowPercentage = c.Bool("percentage") || extra + writer.Scaler = helpers.BuildScalerOrFail(scalerName) batcher := helpers.BuildBatcherFromArguments(c) ext := helpers.BuildExtractorFromArguments(c, batcher) @@ -130,6 +132,7 @@ func histogramCommand() *cli.Command { helpers.SnapshotFlag, helpers.NoOutFlag, helpers.CSVFlag, + helpers.ScaleFlag, }, }) } diff --git a/cmd/histo_test.go b/cmd/histo_test.go index 22544fa0..3e98af7d 100644 --- a/cmd/histo_test.go +++ b/cmd/histo_test.go @@ -10,6 +10,8 @@ func TestHistogram(t *testing.T) { testCommandSet(t, histogramCommand(), `-m (\d+) testdata/log.txt`, `-m (\d+) testdata/graph.txt`, + `-m (\d+) --all testdata/graph.txt`, + `-m (\d+) --scale log10 testdata/graph.txt`, `-o - -m (\d+) testdata/graph.txt`, `-z -m (\d+) testdata/log.txt.gz`, ) diff --git a/docs/cli-help.md b/docs/cli-help.md index 1ab54d8e84436454e57c18da829b5b2e443a5064..3119c2f40633c0b95cb5ca08e432ee1a959e0cdd 100644 GIT binary patch delta 37 rcmdl~ajtShroiNCiRY8+6hbE-(C3+KCbeiYtIz{pkl^Mzg)PzmLO&2C delta 29 jcmX?Cxv649roiM({h6Bs1RwK(7?T48LpSFu#!CYL%3};J diff --git a/docs/usage/aggregators.md b/docs/usage/aggregators.md index c0fbe81b..cfeb46a7 100644 --- a/docs/usage/aggregators.md +++ b/docs/usage/aggregators.md @@ -49,6 +49,8 @@ of an extracted match. That is to say, on every line a regex will be matched (or not), and the matched groups can be used to extract and build a key, that will act as the bucketing name. +Supports [alternative scales](#alternative-scales) + ### Example Extract HTTP verb, URL and status code. Key off of status code and verb. @@ -78,6 +80,8 @@ rare help bargraph Similar to histogram or table, bargraph can generate a stacked or grouped bargraph by one or two keys. +Supports [alternative scales](#alternative-scales) + ### Example !!! note "Color Coded Keys" diff --git a/docs/usage/examples.md b/docs/usage/examples.md index e19ea58a..d9050792 100644 --- a/docs/usage/examples.md +++ b/docs/usage/examples.md @@ -50,12 +50,21 @@ Matched: 6 / 6 (Groups: 4) # Or with Bars/percentages ./rare histo --match "(\d+)" -e "{1}" -x simple.log -1 3 [50.0%] |||||||||||||||||||||||||||||||||||||||||||||||||| -0 1 [16.7%] |||||||||||||||| -2 1 [16.7%] |||||||||||||||| -3 1 [16.7%] |||||||||||||||| +1 7 [58.3%] |||||||||||||||||||||||||||||||||||||||||||||||||| +3 2 [16.7%] |||||||||||||| +2 2 [16.7%] |||||||||||||| +0 1 [ 8.3%] ||||||| -Matched: 6 / 6 (Groups: 4) +Matched: 12 / 12 (Groups: 4) + +# Logarithmic Scale +./rare histo --match "(\d+)" -e "{1}" --scale log10 -x simple.log +1 7 [58.3%] |||||||||||||||||||||||||||||||||||||||||| +3 2 [16.7%] ||||||||||||||| +2 2 [16.7%] ||||||||||||||| +0 1 [ 8.3%] + +Matched: 12 / 12 (Groups: 4) ``` ## Nginx @@ -106,7 +115,7 @@ This shows an example of how to bucket the values into size of `1000`. In this c sense to see the histogram by number of bytes, but we might want to know the ratio of various orders-of-magnitudes. ```sh -$ rare histo -m '"(\w{3,4}) ([A-Za-z0-9/.]+).*" (\d{3}) (\d+)' -e "{bucket {4} 10000}" -n 10 access.log -b +$ rare histo -m '"(\w{3,4}) ([A-Za-z0-9/.]+).*" (\d{3}) (\d+)' -e "{bucket {4} 10000}" -n 10 -b access.log 0 144239 |||||||||||||||||||||||||||||||||||||||||||||||||| 190000 2599 10000 1290 diff --git a/docs/usage/expressions.md b/docs/usage/expressions.md index aeed2baf..07227488 100644 --- a/docs/usage/expressions.md +++ b/docs/usage/expressions.md @@ -356,12 +356,14 @@ Colorizes the 2nd argument. #### Bars -Syntax: `{bar {val} "maxVal" "length"}` +Syntax: `{bar {val} "maxVal" "length" ["scale"]}` Note: If unicode is disabled, will use pipe character Draws a "bar" with the length `(val / maxVal) * length` +Scale can be `linear`, `log10`, or `log2` (Default: `lienar`) + ### Paths Syntax: `{basename a/b/c}`, `{dirname a/b/c}`, `{extname a/b/c.jpg}` diff --git a/pkg/expressions/stdlib/drawing.go b/pkg/expressions/stdlib/drawing.go index 43cccd13..e90c17bc 100644 --- a/pkg/expressions/stdlib/drawing.go +++ b/pkg/expressions/stdlib/drawing.go @@ -3,6 +3,7 @@ package stdlib import ( "rare/pkg/color" . "rare/pkg/expressions" //lint:ignore ST1001 Legacy + "rare/pkg/multiterm/termscaler" "rare/pkg/multiterm/termunicode" "strconv" "strings" @@ -48,26 +49,41 @@ func kfRepeat(args []KeyBuilderStage) (KeyBuilderStage, error) { }), nil } -// {bar {val} "maxVal" "len"} +// {bar {val} "maxVal" "len" ["scaler"]} func kfBar(args []KeyBuilderStage) (KeyBuilderStage, error) { - if len(args) != 3 { - return stageErrArgCount(args, 3) + if !isArgCountBetween(args, 3, 4) { + return stageErrArgRange(args, "3-4") } maxVal, maxValOk := EvalStageInt64(args[1]) if !maxValOk { return stageArgError(ErrNum, 1) } - maxLen, maxLenOk := EvalStageInt64(args[2]) + maxLen, maxLenOk := EvalStageInt(args[2]) if !maxLenOk { return stageArgError(ErrNum, 2) } + scaler := termscaler.ScalerLinear + if len(args) >= 4 { + if name, ok := EvalStaticStage(args[3]); ok { + var scalerOk bool + if scaler, scalerOk = termscaler.ScalerByName(name); !scalerOk { + return stageArgError(ErrEnum, 3) + } + } else { + return stageArgError(ErrConst, 3) + } + } + return KeyBuilderStage(func(context KeyBuilderContext) string { val, err := strconv.ParseInt(args[0](context), 10, 64) if err != nil { return ErrorNum } - return termunicode.BarString(val, maxVal, maxLen) + + var sb strings.Builder + termunicode.BarWrite(&sb, scaler.Scale(val, 0, maxVal), maxLen) + return sb.String() }), nil } diff --git a/pkg/expressions/stdlib/drawing_test.go b/pkg/expressions/stdlib/drawing_test.go index cf53fda3..028174f4 100644 --- a/pkg/expressions/stdlib/drawing_test.go +++ b/pkg/expressions/stdlib/drawing_test.go @@ -1,6 +1,8 @@ package stdlib import ( + "rare/pkg/multiterm/termunicode" + "rare/pkg/testutil" "testing" ) @@ -29,10 +31,15 @@ func TestAddingColor(t *testing.T) { } func TestBarGraph(t *testing.T) { - testExpression(t, - mockContext(), - "{bar 2 5 5}", - "██") + testutil.SwitchGlobal(&termunicode.UnicodeEnabled, false) + defer testutil.RestoreGlobals() + + testExpression(t, mockContext(), "{bar 2 5 5}", "||") + testExpression(t, mockContext(), "{bar 10 100 10}", "|") + testExpression(t, mockContext(), "{bar 10 100 10 log10}", "|||||") + testExpressionErr(t, mockContext(), "{bar 2 {0} 5}", "", ErrNum) + testExpressionErr(t, mockContext(), "{bar 10 100 10 badlog}", "", ErrEnum) + testExpressionErr(t, mockContext(), "{bar 10 100 10 {0}}", "", ErrConst) } func TestBarGraphErrorCases(t *testing.T) { diff --git a/pkg/multiterm/termrenderers/bargraph.go b/pkg/multiterm/termrenderers/bargraph.go index 5869eec0..e902e3cf 100644 --- a/pkg/multiterm/termrenderers/bargraph.go +++ b/pkg/multiterm/termrenderers/bargraph.go @@ -5,6 +5,7 @@ import ( "rare/pkg/color" "rare/pkg/humanize" "rare/pkg/multiterm" + "rare/pkg/multiterm/termscaler" "rare/pkg/multiterm/termunicode" "strings" ) @@ -26,6 +27,7 @@ type BarGraph struct { BarSize int Stacked bool + Scaler termscaler.Scaler } func NewBarGraph(term multiterm.MultilineTerm) *BarGraph { @@ -34,6 +36,7 @@ func NewBarGraph(term multiterm.MultilineTerm) *BarGraph { maxKeyLength: 4, Stacked: false, BarSize: 50, + Scaler: termscaler.ScalerLinear, } } @@ -143,7 +146,7 @@ func (s *BarGraph) writeBarGrouped(idx int, key string, vals ...int64) { sb.WriteString(strings.Repeat(" ", s.maxKeyLength+2)) } color.Write(&sb, color.GroupColors[i%len(color.GroupColors)], func(w io.StringWriter) { - termunicode.BarWrite(w, vals[i], s.maxLineVal, int64(s.BarSize)) + termunicode.BarWrite(w, s.Scaler.Scale(vals[i], 0, s.maxLineVal), s.BarSize) }) sb.WriteString(" ") sb.WriteString(humanize.Hi(vals[i])) diff --git a/pkg/multiterm/termrenderers/bargraph_test.go b/pkg/multiterm/termrenderers/bargraph_test.go index 79807c78..7c887b49 100644 --- a/pkg/multiterm/termrenderers/bargraph_test.go +++ b/pkg/multiterm/termrenderers/bargraph_test.go @@ -2,6 +2,7 @@ package termrenderers import ( "rare/pkg/multiterm" + "rare/pkg/multiterm/termscaler" "testing" "github.com/stretchr/testify/assert" @@ -25,6 +26,25 @@ func TestBargraphRendering(t *testing.T) { assert.Equal(t, "abc", v.Get(5)) } +func TestBargraphRenderingLog10(t *testing.T) { + v := multiterm.NewVirtualTerm() + bg := NewBarGraph(v) + bg.Stacked = false + bg.Scaler = termscaler.ScalerLog10 + + bg.SetKeys("a", "b") + bg.WriteBar(0, "test", 10, 100) + bg.WriteBar(1, "tes2", 50, 500) + bg.WriteFooter(0, "abc") + + assert.Equal(t, " 0 a 1 b", v.Get(0)) + assert.Equal(t, "test ████████████████▊ 10", v.Get(1)) + assert.Equal(t, " █████████████████████████████████▍ 100", v.Get(2)) + assert.Equal(t, "tes2 ████████████████████████████▎ 50", v.Get(3)) + assert.Equal(t, " █████████████████████████████████████████████ 500", v.Get(4)) + assert.Equal(t, "abc", v.Get(5)) +} + func TestBargraphStackedRendering(t *testing.T) { v := multiterm.NewVirtualTerm() bg := NewBarGraph(v) @@ -59,7 +79,7 @@ func TestBargraphBadSubkeys(t *testing.T) { assert.Equal(t, "abc", v.Get(4)) } -func TestBargraphUnicode(t *testing.T) { +func TestBargraphUnicodeStacked(t *testing.T) { v := multiterm.NewVirtualTerm() bg := NewBarGraph(v) bg.Stacked = true diff --git a/pkg/multiterm/termrenderers/histoWriter.go b/pkg/multiterm/termrenderers/histoWriter.go index edc10940..cdd82c34 100644 --- a/pkg/multiterm/termrenderers/histoWriter.go +++ b/pkg/multiterm/termrenderers/histoWriter.go @@ -2,9 +2,11 @@ package termrenderers import ( "fmt" + "io" "rare/pkg/color" "rare/pkg/humanize" "rare/pkg/multiterm" + "rare/pkg/multiterm/termscaler" "rare/pkg/multiterm/termunicode" "strings" ) @@ -23,6 +25,7 @@ type HistoWriter struct { ShowBar bool ShowPercentage bool + Scaler termscaler.Scaler } func NewHistogram(term multiterm.MultilineTerm, maxLines int) *HistoWriter { @@ -30,6 +33,7 @@ func NewHistogram(term multiterm.MultilineTerm, maxLines int) *HistoWriter { writer: term, ShowBar: true, ShowPercentage: true, + Scaler: termscaler.ScalerLinear, textSpacing: 16, items: make([]histoPair, maxLines), } @@ -98,7 +102,9 @@ func (s *HistoWriter) writeLine(line int, key string, val int64) { if s.ShowBar && s.maxVal > 0 { sb.WriteString(" ") - sb.WriteString(color.Wrap(color.Blue, termunicode.BarString(val, s.maxVal, 50))) + color.Write(&sb, color.Blue, func(w io.StringWriter) { + termunicode.BarWrite(w, s.Scaler.Scale(val, 0, s.maxVal), 50) + }) } s.writer.WriteForLine(line, sb.String()) diff --git a/pkg/multiterm/termscaler/scale.go b/pkg/multiterm/termscaler/scale.go index d7e5746d..259d55a9 100644 --- a/pkg/multiterm/termscaler/scale.go +++ b/pkg/multiterm/termscaler/scale.go @@ -72,11 +72,21 @@ func Bucket(buckets int, unitVal float64) int { return int(unitVal * float64(buckets-1)) } +// Return [0, maxLen] +func LengthVal(maxLen int, unitVal float64) int { + return int(unitVal * float64(maxLen)) +} + // Return [0, bucket-1] func (s Scaler) Bucket(buckets int, val, min, max int64) int { return Bucket(buckets, s.Scale(val, min, max)) } +// Return [0, maxLen] +func (s Scaler) LengthVal(maxLen int, val, min, max int64) int { + return LengthVal(maxLen, s.Scale(val, min, max)) +} + // Returns scaled bucket values. May return less buckets than requested for small or high-curve ranges (won't return dupes) func (s Scaler) ScaleKeys(buckets, min, max int64) []int64 { minf10, maxf10 := s.remapMinMax(min, max) diff --git a/pkg/multiterm/termscaler/scale_test.go b/pkg/multiterm/termscaler/scale_test.go index 80bb55d0..12b30857 100644 --- a/pkg/multiterm/termscaler/scale_test.go +++ b/pkg/multiterm/termscaler/scale_test.go @@ -69,6 +69,22 @@ func TestLogScale2(t *testing.T) { assert.Equal(t, []int64{1, 3, 6, 11, 21, 39, 73, 135, 251, 464, 857, 1584, 2928, 5411, 10000}, s.ScaleKeys(16, 0, 10000)) } +func TestLengthVal(t *testing.T) { + s, _ := ScalerByName("linear") + assert.Equal(t, 0, s.LengthVal(5, -5, 0, 10)) + assert.Equal(t, 0, s.LengthVal(5, 0, 0, 10)) + assert.Equal(t, 2, s.LengthVal(5, 5, 0, 10)) + assert.Equal(t, 5, s.LengthVal(5, 10, 0, 10)) + assert.Equal(t, 5, s.LengthVal(5, 20, 0, 10)) + assert.Equal(t, 1, s.LengthVal(5, 120, 100, 200)) + assert.Equal(t, 3, s.LengthVal(5, 175, 100, 200)) + + // Edge cases + assert.Equal(t, 0, s.LengthVal(5, 20, 20, 10)) + assert.Equal(t, 5, s.LengthVal(5, 20, 10, 10)) + assert.Equal(t, 0, s.LengthVal(5, -100, 0, -10)) +} + func TestLinearKeySet(t *testing.T) { assert.Equal(t, []int64{0, 25, 50, 75, 100}, ScalerLinear.ScaleKeys(5, 0, 100)) assert.Equal(t, []int64{50, 62, 75, 87, 100}, ScalerLinear.ScaleKeys(5, 50, 100)) diff --git a/pkg/multiterm/termunicode/bars.go b/pkg/multiterm/termunicode/bars.go index c204ece7..c252faa5 100644 --- a/pkg/multiterm/termunicode/bars.go +++ b/pkg/multiterm/termunicode/bars.go @@ -3,10 +3,11 @@ package termunicode import ( "io" "rare/pkg/color" - "strings" + "rare/pkg/multiterm/termscaler" ) const nonUnicodeBlock rune = '|' +const nonUnicodeBlockStr string = string(nonUnicodeBlock) const fullBlock rune = '\u2588' @@ -41,7 +42,7 @@ var barAscii = [...]rune{ 'F', } -const barUnicodePartCount int64 = int64(len(barUnicode)) +const barUnicodePartCount = len(barUnicode) // write a length of runes for a given bar parameters func barWriteRunes(w io.StringWriter, blockChar rune, val, maxVal, maxLen int64) { @@ -56,24 +57,10 @@ func barWriteRunes(w io.StringWriter, blockChar rune, val, maxVal, maxLen int64) } } -// BarWriteFull does not write partial bars to the end. Useful for stacking -func BarWriteFull(w io.StringWriter, val, maxVal, maxLen int64) { - var blockChar rune = nonUnicodeBlock - if UnicodeEnabled { - blockChar = fullBlock - } - - barWriteRunes(w, blockChar, val, maxVal, maxLen) -} - // Write a bar, possibly with partial runes. Not to be used with stacking -func BarWrite(w io.StringWriter, val, maxVal, maxLen int64) { - if val > maxVal { - val = maxVal - } - +func BarWrite(w io.StringWriter, val float64, maxLen int) { if UnicodeEnabled { - remainingBlocks := val * maxLen * barUnicodePartCount / maxVal + remainingBlocks := termscaler.LengthVal(maxLen*barUnicodePartCount, val) for remainingBlocks >= barUnicodePartCount { w.WriteString(string(fullBlock)) remainingBlocks -= barUnicodePartCount @@ -82,21 +69,14 @@ func BarWrite(w io.StringWriter, val, maxVal, maxLen int64) { w.WriteString(string(barUnicode[remainingBlocks])) } } else { - blocks := val * maxLen / maxVal + blocks := termscaler.LengthVal(maxLen, val) for blocks > 0 { - w.WriteString(string(nonUnicodeBlock)) + w.WriteString(nonUnicodeBlockStr) blocks-- } } } -// BarWrite, but to a string -func BarString(val, maxVal, maxLen int64) string { - var sb strings.Builder - BarWrite(&sb, val, maxVal, maxLen) - return sb.String() -} - /* Draws various bars. Because of various outputs, there are different styles: - Color, Unicode: Uses full/partial unicode blocks, with coloring to stack or diff --git a/pkg/multiterm/termunicode/bars_test.go b/pkg/multiterm/termunicode/bars_test.go index 2f467219..14860032 100644 --- a/pkg/multiterm/termunicode/bars_test.go +++ b/pkg/multiterm/termunicode/bars_test.go @@ -2,6 +2,7 @@ package termunicode import ( "rare/pkg/color" + "rare/pkg/multiterm/termscaler" "rare/pkg/testutil" "strings" "testing" @@ -11,26 +12,25 @@ import ( func TestWriteBar(t *testing.T) { var sb strings.Builder - BarWrite(&sb, 1, 8, 1) + BarWrite(&sb, termscaler.ScalerLinear.Scale(1, 0, 8), 1) assert.Equal(t, string(barUnicode[1]), sb.String()) sb.Reset() - BarWrite(&sb, 10, 8, 1) + BarWrite(&sb, termscaler.ScalerLinear.Scale(10, 0, 8), 1) assert.Equal(t, string(fullBlock), sb.String()) - assert.Equal(t, string(barUnicode[5]), BarString(5, 8, 1)) - sb.Reset() - BarWriteFull(&sb, 1, 8, 10) - assert.Equal(t, string(fullBlock), sb.String()) + BarWrite(&sb, termscaler.ScalerLinear.Scale(5, 0, 8), 1) + assert.Equal(t, string(barUnicode[5]), sb.String()) } func TestWriteBarFallbacks(t *testing.T) { - UnicodeEnabled = false - - assert.Equal(t, "|||||", BarString(5, 10, 10)) + testutil.SwitchGlobal(&UnicodeEnabled, false) + defer testutil.RestoreGlobals() - UnicodeEnabled = true + var sb strings.Builder + BarWrite(&sb, termscaler.ScalerLinear.Scale(5, 0, 10), 10) + assert.Equal(t, "|||||", sb.String()) } func TestWriteBarStacked(t *testing.T) { @@ -57,6 +57,33 @@ func TestWriteBarStacked(t *testing.T) { assert.Equal(t, "\x1b[31m█\x1b[0m\x1b[32m███\x1b[0m\x1b[33m██\x1b[0m", sb.String()) } +func TestBarWriteScaled(t *testing.T) { + defer testutil.RestoreGlobals() + testutil.SwitchGlobal(&UnicodeEnabled, false) + var sb strings.Builder + + BarWrite(&sb, 0.0, 6) + assert.Equal(t, "", sb.String()) + sb.Reset() + + BarWrite(&sb, 0.5, 6) + assert.Equal(t, "|||", sb.String()) + sb.Reset() + + BarWrite(&sb, 1.0, 6) + assert.Equal(t, "||||||", sb.String()) + sb.Reset() + + testutil.SwitchGlobal(&UnicodeEnabled, true) + BarWrite(&sb, 0.45, 16) + assert.Equal(t, "███████▏", sb.String()) + sb.Reset() + + BarWrite(&sb, 1.0, 16) + assert.Equal(t, "████████████████", sb.String()) + sb.Reset() +} + func TestBarKeyChar(t *testing.T) { defer testutil.RestoreGlobals()