diff --git a/.travis.yml b/.travis.yml index 9035075..d9a3bc5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.9.x - 1.10.x - 1.11.x + - 1.12.x - stable script: - go get -t ./... diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e95c99..518dff7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The widgetapi.Widget.Draw method now accepts a second argument which provides widgets with additional metadata. This affects all implemented widgets. +- Termdash now requires at least Go version 1.10, which allows us to utilize + `math.Round` instead of our own implementation and `strings.Builder` instead + of `bytes.Buffer`. ## [0.8.0] - 30-Mar-2019 diff --git a/internal/draw/text.go b/internal/draw/text.go index c940ee5..521bbfa 100644 --- a/internal/draw/text.go +++ b/internal/draw/text.go @@ -17,9 +17,9 @@ package draw // text.go contains code that prints UTF-8 encoded strings on the canvas. import ( - "bytes" "fmt" "image" + "strings" "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/internal/canvas" @@ -124,7 +124,7 @@ func TrimText(text string, maxCells int, om OverrunMode) (string, error) { return "", fmt.Errorf("unsupported overrun mode %d", om) } - var b bytes.Buffer + var b strings.Builder cur := 0 for _, r := range text { rw := runewidth.RuneWidth(r) diff --git a/internal/faketerm/diff.go b/internal/faketerm/diff.go index 3a178a7..80b20e8 100644 --- a/internal/faketerm/diff.go +++ b/internal/faketerm/diff.go @@ -17,10 +17,10 @@ package faketerm // diff.go provides functions that highlight differences between fake terminals. import ( - "bytes" "fmt" "image" "reflect" + "strings" "github.com/kylelemons/godebug/pretty" "github.com/mum4k/termdash/cell" @@ -43,7 +43,7 @@ func Diff(want, got *Terminal) string { return "" } - var b bytes.Buffer + var b strings.Builder b.WriteString("found differences between the two fake terminals.\n") b.WriteString(" got:\n") b.WriteString(got.String()) diff --git a/internal/faketerm/faketerm.go b/internal/faketerm/faketerm.go index 8617a9a..6d3145b 100644 --- a/internal/faketerm/faketerm.go +++ b/internal/faketerm/faketerm.go @@ -16,11 +16,11 @@ package faketerm import ( - "bytes" "context" "fmt" "image" "log" + "strings" "sync" "github.com/mum4k/termdash/cell" @@ -118,7 +118,7 @@ func (t *Terminal) BackBuffer() buffer.Buffer { // Implements fmt.Stringer. func (t *Terminal) String() string { size := t.Size() - var b bytes.Buffer + var b strings.Builder for row := 0; row < size.Y; row++ { for col := 0; col < size.X; col++ { r := t.buffer[col][row].Rune diff --git a/internal/numbers/numbers.go b/internal/numbers/numbers.go index e62f346..e91620f 100644 --- a/internal/numbers/numbers.go +++ b/internal/numbers/numbers.go @@ -96,17 +96,6 @@ func zeroBeforeDecimal(f float64) float64 { return (f - floor) * sign } -// Round returns the nearest integer, rounding half away from zero. -// Copied from the math package of Go 1.10 for backwards compatibility with Go -// 1.8 where the math.Round function doesn't exist yet. -func Round(x float64) float64 { - t := math.Trunc(x) - if math.Abs(x-t) >= 0.5 { - return t + math.Copysign(1, x) - } - return t -} - // MinMax returns the smallest and the largest value among the provided values. // Returns (0, 0) if there are no values. // Ignores NaN values. Allowing NaN values could lead to a corner case where all @@ -169,7 +158,7 @@ func DegreesToRadians(degrees int) float64 { // RadiansToDegrees converts radians to the equivalent in degrees. func RadiansToDegrees(radians float64) int { - d := int(Round(radians * 180 / math.Pi)) + d := int(math.Round(radians * 180 / math.Pi)) if d < 0 { d += 360 } @@ -227,7 +216,7 @@ func SplitByRatio(n int, ratio image.Point) image.Point { sum := float64(sr.X + sr.Y) fact := fn / sum return image.Point{ - int(Round(fact * float64(sr.X))), - int(Round(fact * float64(sr.Y))), + int(math.Round(fact * float64(sr.X))), + int(math.Round(fact * float64(sr.Y))), } } diff --git a/internal/numbers/numbers_test.go b/internal/numbers/numbers_test.go index f5955b5..99b6d85 100644 --- a/internal/numbers/numbers_test.go +++ b/internal/numbers/numbers_test.go @@ -120,61 +120,6 @@ func alike(a, b float64) bool { return false } -var round = []float64{ - 5, - 8, - math.Copysign(0, -1), - -5, - 10, - 3, - 5, - 3, - 2, - -9, -} - -var vf = []float64{ - 4.9790119248836735e+00, - 7.7388724745781045e+00, - -2.7688005719200159e-01, - -5.0106036182710749e+00, - 9.6362937071984173e+00, - 2.9263772392439646e+00, - 5.2290834314593066e+00, - 2.7279399104360102e+00, - 1.8253080916808550e+00, - -8.6859247685756013e+00, -} - -var vfroundSC = [][2]float64{ - {0, 0}, - {1.390671161567e-309, 0}, // denormal - {0.49999999999999994, 0}, // 0.5-epsilon - {0.5, 1}, - {0.5000000000000001, 1}, // 0.5+epsilon - {-1.5, -2}, - {-2.5, -3}, - {math.NaN(), math.NaN()}, - {math.Inf(1), math.Inf(1)}, - {2251799813685249.5, 2251799813685250}, // 1 bit fraction - {2251799813685250.5, 2251799813685251}, - {4503599627370495.5, 4503599627370496}, // 1 bit fraction, rounding to 0 bit fraction - {4503599627370497, 4503599627370497}, // large integer -} - -func TestRound(t *testing.T) { - for i := 0; i < len(vf); i++ { - if f := Round(vf[i]); !alike(round[i], f) { - t.Errorf("Round(%g) = %g, want %g", vf[i], f, round[i]) - } - } - for i := 0; i < len(vfroundSC); i++ { - if f := Round(vfroundSC[i][0]); !alike(vfroundSC[i][1], f) { - t.Errorf("Round(%g) = %g, want %g", vfroundSC[i][0], f, vfroundSC[i][1]) - } - } -} - func TestMinMax(t *testing.T) { tests := []struct { desc string diff --git a/internal/numbers/trig/trig.go b/internal/numbers/trig/trig.go index 1a2da34..1e908d3 100644 --- a/internal/numbers/trig/trig.go +++ b/internal/numbers/trig/trig.go @@ -30,9 +30,9 @@ import ( func CirclePointAtAngle(degrees int, mid image.Point, radius int) image.Point { angle := numbers.DegreesToRadians(degrees) r := float64(radius) - x := mid.X + int(numbers.Round(r*math.Cos(angle))) + x := mid.X + int(math.Round(r*math.Cos(angle))) // Y coordinates grow down on the canvas. - y := mid.Y - int(numbers.Round(r*math.Sin(angle))) + y := mid.Y - int(math.Round(r*math.Sin(angle))) return image.Point{x, y} } diff --git a/internal/segdisp/sixteen/attributes.go b/internal/segdisp/sixteen/attributes.go index 5af1a7c..fa535ae 100644 --- a/internal/segdisp/sixteen/attributes.go +++ b/internal/segdisp/sixteen/attributes.go @@ -56,7 +56,7 @@ var diaSegType = map[Segment]segment.DiagonalType{ func segmentSize(ar image.Rectangle) int { // widthPerc is the relative width of a segment to the width of the canvas. const widthPerc = 9 - s := int(numbers.Round(float64(ar.Dx()) * widthPerc / 100)) + s := int(math.Round(float64(ar.Dx()) * widthPerc / 100)) if s > 3 && s%2 == 0 { // Segments with odd number of pixels in their width/height look // better, since the spike at the top of their slopes has only one @@ -146,16 +146,16 @@ func newAttributes(bcAr image.Rectangle) *attributes { twoSegLeg := twoSegHypo / math.Sqrt2 edgeSegGap := twoSegLeg - segPeakDist - spaces := int(numbers.Round(2*edgeSegGap + peakToPeak)) + spaces := int(math.Round(2*edgeSegGap + peakToPeak)) shortLen := (bcAr.Dx()-spaces)/2 - 1 longLen := (bcAr.Dy()-spaces)/2 - 1 - ptp := int(numbers.Round(peakToPeak)) - horizLeftX := int(numbers.Round(edgeSegGap)) + ptp := int(math.Round(peakToPeak)) + horizLeftX := int(math.Round(edgeSegGap)) // Refer to doc/segment_placement.svg. // Diagram labeled "A mid point". - offset := int(numbers.Round(diaLeg - segPeakDist)) + offset := int(math.Round(diaLeg - segPeakDist)) horizMidX := horizLeftX + shortLen + offset horizRightX := horizLeftX + shortLen + ptp + shortLen + offset @@ -292,9 +292,9 @@ func (a *attributes) diaBetween(top, left, right, bottom Segment) image.Rectangl const hvToDiaGapPerc = 30 hvToDiaGap := a.diaGap * hvToDiaGapPerc / 100 - startX := int(numbers.Round(float64(topAr.Min.X) + a.segPeakDist - a.diaLeg + hvToDiaGap)) - startY := int(numbers.Round(float64(leftAr.Min.Y) + a.segPeakDist - a.diaLeg + hvToDiaGap)) - endX := int(numbers.Round(float64(bottomAr.Max.X) - a.segPeakDist + a.diaLeg - hvToDiaGap)) - endY := int(numbers.Round(float64(rightAr.Max.Y) - a.segPeakDist + a.diaLeg - hvToDiaGap)) + startX := int(math.Round(float64(topAr.Min.X) + a.segPeakDist - a.diaLeg + hvToDiaGap)) + startY := int(math.Round(float64(leftAr.Min.Y) + a.segPeakDist - a.diaLeg + hvToDiaGap)) + endX := int(math.Round(float64(bottomAr.Max.X) - a.segPeakDist + a.diaLeg - hvToDiaGap)) + endY := int(math.Round(float64(rightAr.Max.Y) - a.segPeakDist + a.diaLeg - hvToDiaGap)) return image.Rect(startX, startY, endX, endY) } diff --git a/internal/segdisp/sixteen/sixteen.go b/internal/segdisp/sixteen/sixteen.go index dcab6d6..fb52575 100644 --- a/internal/segdisp/sixteen/sixteen.go +++ b/internal/segdisp/sixteen/sixteen.go @@ -40,10 +40,10 @@ The following outlines segments in the display and their names. package sixteen import ( - "bytes" "fmt" "image" "math" + "strings" "github.com/mum4k/termdash/cell" "github.com/mum4k/termdash/internal/area" @@ -250,7 +250,7 @@ func SupportsChars(s string) (bool, []rune) { // Sanitize returns a copy of the string, replacing all unsupported characters // with a space character. func Sanitize(s string) string { - var b bytes.Buffer + var b strings.Builder for _, r := range s { if _, ok := characterSegments[r]; !ok { b.WriteRune(' ') diff --git a/internal/wrap/wrap.go b/internal/wrap/wrap.go index 0fe3a1c..b0ead04 100644 --- a/internal/wrap/wrap.go +++ b/internal/wrap/wrap.go @@ -16,9 +16,9 @@ package wrap import ( - "bytes" "errors" "fmt" + "strings" "unicode" "github.com/mum4k/termdash/internal/canvas/buffer" @@ -83,7 +83,7 @@ func ValidText(text string) error { // ValidCells validates the provided cells for wrapping. // The text in the cells must follow the same rules as described for ValidText. func ValidCells(cells []*buffer.Cell) error { - var b bytes.Buffer + var b strings.Builder for _, c := range cells { b.WriteRune(c.Rune) } @@ -208,7 +208,7 @@ func (cs *cellScanner) wordCells() []*buffer.Cell { // wordWidth returns the width of the current word in cells when printed on the // terminal. func (cs *cellScanner) wordWidth() int { - var b bytes.Buffer + var b strings.Builder for _, wc := range cs.wordCells() { b.WriteRune(wc.Rune) } diff --git a/internal/wrap/wrap_test.go b/internal/wrap/wrap_test.go index 4ac7d69..da712f8 100644 --- a/internal/wrap/wrap_test.go +++ b/internal/wrap/wrap_test.go @@ -15,8 +15,8 @@ package wrap import ( - "bytes" "fmt" + "strings" "testing" "unicode" @@ -42,7 +42,7 @@ func TestValidTextAndCells(t *testing.T) { { desc: "all printable ASCII characters are allowed", text: func() string { - var b bytes.Buffer + var b strings.Builder for i := 0; i < unicode.MaxASCII; i++ { r := rune(i) if unicode.IsPrint(r) { @@ -55,7 +55,7 @@ func TestValidTextAndCells(t *testing.T) { { desc: "all printable Unicode characters in the Latin-1 space are allowed", text: func() string { - var b bytes.Buffer + var b strings.Builder for i := 0; i < unicode.MaxLatin1; i++ { r := rune(i) if unicode.IsPrint(r) { diff --git a/widgets/donut/circle.go b/widgets/donut/circle.go index d9a586a..780a716 100644 --- a/widgets/donut/circle.go +++ b/widgets/donut/circle.go @@ -18,9 +18,9 @@ package donut import ( "image" + "math" "github.com/mum4k/termdash/internal/canvas/braille" - "github.com/mum4k/termdash/internal/numbers" ) // startEndAngles given progress indicators and the desired start angle and @@ -33,12 +33,12 @@ func startEndAngles(current, total, startAngle, direction int) (start, end int) } mult := float64(current) / float64(total) - angleSize := numbers.Round(float64(360) * mult) + angleSize := math.Round(float64(360) * mult) if angleSize == fullCircle { return 0, fullCircle } - end = startAngle + int(numbers.Round(float64(direction)*angleSize)) + end = startAngle + int(math.Round(float64(direction)*angleSize)) if end < 0 { end += fullCircle diff --git a/widgets/donut/donut.go b/widgets/donut/donut.go index 6863ea5..031525d 100644 --- a/widgets/donut/donut.go +++ b/widgets/donut/donut.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "image" + "math" "sync" "github.com/mum4k/termdash/align" @@ -27,7 +28,6 @@ import ( "github.com/mum4k/termdash/internal/canvas" "github.com/mum4k/termdash/internal/canvas/braille" "github.com/mum4k/termdash/internal/draw" - "github.com/mum4k/termdash/internal/numbers" "github.com/mum4k/termdash/internal/runewidth" "github.com/mum4k/termdash/terminal/terminalapi" "github.com/mum4k/termdash/widgetapi" @@ -155,7 +155,7 @@ func (d *Donut) progressText() string { // holeRadius calculates the radius of the "hole" in the donut. // Returns zero if no hole should be drawn. func (d *Donut) holeRadius(donutRadius int) int { - r := int(numbers.Round(float64(donutRadius) / 100 * float64(d.opts.donutHolePercent))) + r := int(math.Round(float64(donutRadius) / 100 * float64(d.opts.donutHolePercent))) if r < 2 { // Smallest possible circle radius. return 0 } diff --git a/widgets/gauge/gauge.go b/widgets/gauge/gauge.go index 1d01ce5..feca8fb 100644 --- a/widgets/gauge/gauge.go +++ b/widgets/gauge/gauge.go @@ -16,10 +16,10 @@ package gauge import ( - "bytes" "errors" "fmt" "image" + "strings" "sync" "github.com/mum4k/termdash/cell" @@ -176,7 +176,7 @@ func (g *Gauge) progressText() string { // gaugeText returns full text to be displayed within the gauge, i.e. the // progress text and the optional label. func (g *Gauge) gaugeText() string { - var b bytes.Buffer + var b strings.Builder b.WriteString(g.progressText()) if g.opts.textLabel != "" { if b.Len() > 0 { diff --git a/widgets/linechart/internal/axes/scale.go b/widgets/linechart/internal/axes/scale.go index 202e696..63b2448 100644 --- a/widgets/linechart/internal/axes/scale.go +++ b/widgets/linechart/internal/axes/scale.go @@ -18,9 +18,9 @@ package axes import ( "fmt" + "math" "github.com/mum4k/termdash/internal/canvas/braille" - "github.com/mum4k/termdash/internal/numbers" ) // YScaleMode determines whether the Y scale is anchored to the zero value. @@ -166,7 +166,7 @@ func (ys *YScale) ValueToPixel(v float64) (int, error) { diff := -1 * ys.Min.Value v += diff } - pos := int(numbers.Round(v / ys.Step.Rounded)) + pos := int(math.Round(v / ys.Step.Rounded)) return positionToY(pos, ys.brailleHeight) } @@ -282,7 +282,7 @@ func (xs *XScale) ValueToPixel(v int) (int, error) { if xs.Min.Value > 0 { fv -= xs.Min.Value } - return int(numbers.Round(fv / xs.Step.Rounded)), nil + return int(math.Round(fv / xs.Step.Rounded)), nil } // ValueToCell given a value, determines the X coordinate of the cell that @@ -306,7 +306,7 @@ func (xs *XScale) CellLabel(x int) (*Value, error) { if err != nil { return nil, err } - return NewValue(numbers.Round(v), xs.Min.NonZeroDecimals), nil + return NewValue(math.Round(v), xs.Min.NonZeroDecimals), nil } // positionToY, given a position within the height, returns the Y coordinate of diff --git a/widgets/segmentdisplay/segmentdisplay.go b/widgets/segmentdisplay/segmentdisplay.go index d60e993..a8be19e 100644 --- a/widgets/segmentdisplay/segmentdisplay.go +++ b/widgets/segmentdisplay/segmentdisplay.go @@ -17,10 +17,10 @@ package segmentdisplay import ( - "bytes" "errors" "fmt" "image" + "strings" "sync" "github.com/mum4k/termdash/internal/alignfor" @@ -43,7 +43,7 @@ import ( // Implements widgetapi.Widget. This object is thread-safe. type SegmentDisplay struct { // buff contains the text to be displayed. - buff bytes.Buffer + buff strings.Builder // givenWOpts are write options given for the text in buff. givenWOpts []*writeOptions diff --git a/widgets/sparkline/sparks.go b/widgets/sparkline/sparks.go index 9e7f900..029478d 100644 --- a/widgets/sparkline/sparks.go +++ b/widgets/sparkline/sparks.go @@ -19,8 +19,8 @@ package sparkline import ( "fmt" + "math" - "github.com/mum4k/termdash/internal/numbers" "github.com/mum4k/termdash/internal/runewidth" ) @@ -75,7 +75,7 @@ func toBlocks(value, max, vertCells int) blocks { scale := float64(cellSparks) * float64(vertCells) / float64(max) // How many smallest spark elements are needed to represent the value. - elements := int(numbers.Round(float64(value) * scale)) + elements := int(math.Round(float64(value) * scale)) b := blocks{ full: elements / cellSparks,