Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jedib0t/go-pretty
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v6.5.4
Choose a base ref
...
head repository: jedib0t/go-pretty
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v6.5.5
Choose a head ref
  • 2 commits
  • 7 files changed
  • 2 contributors

Commits on Mar 13, 2024

  1. replace imports of ioutil with io (#304)

    Skeeve authored Mar 13, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    699cbbf View commit details

Commits on Mar 14, 2024

  1. table: mixed-mode alignment and sorting (#306)

    - table: Sort should now work on missing/empty cells
    - table: Sort supports `AlphaNumeric` modes
    - text: `AlignAuto` to align numbers Right and everything else Left
    jedib0t authored Mar 14, 2024

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    142bdf4 View commit details
Showing with 260 additions and 67 deletions.
  1. +2 −2 bench_test.go
  2. +2 −2 cmd/profile-progress/profile.go
  3. +28 −0 table/render_test.go
  4. +104 −22 table/sort.go
  5. +66 −16 table/sort_test.go
  6. +43 −25 text/align.go
  7. +15 −0 text/align_test.go
4 changes: 2 additions & 2 deletions bench_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package gopretty

import (
"io/ioutil"
"io"
"testing"
"time"

@@ -52,7 +52,7 @@ func BenchmarkProgress_Render(b *testing.B) {
for i := 0; i < b.N; i++ {
pw := progress.NewWriter()
pw.SetAutoStop(true)
pw.SetOutputWriter(ioutil.Discard)
pw.SetOutputWriter(io.Discard)
go trackSomething(pw, &tracker1)
go trackSomething(pw, &tracker2)
go trackSomething(pw, &tracker3)
4 changes: 2 additions & 2 deletions cmd/profile-progress/profile.go
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ package main

import (
"fmt"
"io/ioutil"
"io"
"os"
"strconv"
"time"
@@ -36,7 +36,7 @@ func profileRender(profiler func(profile2 *profile.Profile), n int) {
for i := 0; i < n; i++ {
pw := progress.NewWriter()
pw.SetAutoStop(true)
pw.SetOutputWriter(ioutil.Discard)
pw.SetOutputWriter(io.Discard)
go trackSomething(pw, &tracker1)
go trackSomething(pw, &tracker2)
go trackSomething(pw, &tracker3)
28 changes: 28 additions & 0 deletions table/render_test.go
Original file line number Diff line number Diff line change
@@ -111,6 +111,34 @@ func TestTable_Render(t *testing.T) {
A Song of Ice and Fire`)
}

func TestTable_Render_Align(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(Row{500, "Jamie", "Lannister", "Kingslayer", "The things I do for love."})
tw.AppendRow(Row{1000, "Tywin", "Lannister", nil})
tw.AppendFooter(testFooter)
tw.SetColumnConfigs([]ColumnConfig{
{Name: "First Name", Align: text.AlignLeft, AlignHeader: text.AlignLeft, AlignFooter: text.AlignLeft},
{Name: "Last Name", Align: text.AlignRight, AlignHeader: text.AlignRight, AlignFooter: text.AlignRight},
{Name: "Salary", Align: text.AlignAuto, AlignHeader: text.AlignRight, AlignFooter: text.AlignAuto},
{Number: 5, Align: text.AlignJustify, AlignHeader: text.AlignJustify, AlignFooter: text.AlignJustify},
})

compareOutput(t, tw.Render(), `
+------+------------+-----------+------------+-----------------------------+
| # | FIRST NAME | LAST NAME | SALARY | |
+------+------------+-----------+------------+-----------------------------+
| 1 | Arya | Stark | 3000 | |
| 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
| 300 | Tyrion | Lannister | 5000 | |
| 500 | Jamie | Lannister | Kingslayer | The things I do for love. |
| 1000 | Tywin | Lannister | <nil> | |
+------+------------+-----------+------------+-----------------------------+
| | | TOTAL | 10000 | |
+------+------------+-----------+------------+-----------------------------+`)
}

func TestTable_Render_AutoIndex(t *testing.T) {
tw := NewWriter()
for rowIdx := 0; rowIdx < 10; rowIdx++ {
126 changes: 104 additions & 22 deletions table/sort.go
Original file line number Diff line number Diff line change
@@ -25,12 +25,24 @@ type SortMode int
const (
// Asc sorts the column in Ascending order alphabetically.
Asc SortMode = iota
// AscAlphaNumeric sorts the column in Ascending order alphabetically and
// then numerically.
AscAlphaNumeric
// AscNumeric sorts the column in Ascending order numerically.
AscNumeric
// AscNumericAlpha sorts the column in Ascending order numerically and
// then alphabetically.
AscNumericAlpha
// Dsc sorts the column in Descending order alphabetically.
Dsc
// DscAlphaNumeric sorts the column in Descending order alphabetically and
// then numerically.
DscAlphaNumeric
// DscNumeric sorts the column in Descending order numerically.
DscNumeric
// DscNumericAlpha sorts the column in Descending order numerically and
// then alphabetically.
DscNumericAlpha
)

type rowsSorter struct {
@@ -93,35 +105,105 @@ func (rs rowsSorter) Swap(i, j int) {

func (rs rowsSorter) Less(i, j int) bool {
realI, realJ := rs.sortedIndices[i], rs.sortedIndices[j]
for _, col := range rs.sortBy {
rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], col.Number-1
if colIdx < len(rowI) && colIdx < len(rowJ) {
shouldContinue, returnValue := rs.lessColumns(rowI, rowJ, colIdx, col)
if !shouldContinue {
return returnValue
}
for _, sortBy := range rs.sortBy {
// extract the values/cells from the rows for comparison
rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], sortBy.Number-1
iVal, jVal := "", ""
if colIdx < len(rowI) {
iVal = rowI[colIdx]
}
if colIdx < len(rowJ) {
jVal = rowJ[colIdx]
}

// compare and choose whether to continue
shouldContinue, returnValue := less(iVal, jVal, sortBy.Mode)
if !shouldContinue {
return returnValue
}
}
return false
}

func (rs rowsSorter) lessColumns(rowI rowStr, rowJ rowStr, colIdx int, col SortBy) (bool, bool) {
if rowI[colIdx] == rowJ[colIdx] {
func less(iVal string, jVal string, mode SortMode) (bool, bool) {
if iVal == jVal {
return true, false
} else if col.Mode == Asc {
return false, rowI[colIdx] < rowJ[colIdx]
} else if col.Mode == Dsc {
return false, rowI[colIdx] > rowJ[colIdx]
}

iVal, iErr := strconv.ParseFloat(rowI[colIdx], 64)
jVal, jErr := strconv.ParseFloat(rowJ[colIdx], 64)
if iErr == nil && jErr == nil {
if col.Mode == AscNumeric {
return false, iVal < jVal
} else if col.Mode == DscNumeric {
return false, jVal < iVal
}
switch mode {
case Asc, Dsc:
return lessAlphabetic(iVal, jVal, mode)
case AscNumeric, DscNumeric:
return lessNumeric(iVal, jVal, mode)
default: // AscAlphaNumeric, AscNumericAlpha, DscAlphaNumeric, DscNumericAlpha
return lessMixedMode(iVal, jVal, mode)
}
}

func lessAlphabetic(iVal string, jVal string, mode SortMode) (bool, bool) {
switch mode {
case Asc, AscAlphaNumeric, AscNumericAlpha:
return false, iVal < jVal
default: // Dsc, DscAlphaNumeric, DscNumericAlpha
return false, iVal > jVal
}
}

func lessAlphaNumericI(mode SortMode) (bool, bool) {
// i == "abc"; j == 5
switch mode {
case AscAlphaNumeric, DscAlphaNumeric:
return false, true
default: // AscNumericAlpha, DscNumericAlpha
return false, false
}
}

func lessAlphaNumericJ(mode SortMode) (bool, bool) {
// i == 5; j == "abc"
switch mode {
case AscAlphaNumeric, DscAlphaNumeric:
return false, false
default: // AscNumericAlpha, DscNumericAlpha:
return false, true
}
}

func lessMixedMode(iVal string, jVal string, mode SortMode) (bool, bool) {
iNumVal, iErr := strconv.ParseFloat(iVal, 64)
jNumVal, jErr := strconv.ParseFloat(jVal, 64)
if iErr != nil && jErr != nil { // both are alphanumeric
return lessAlphabetic(iVal, jVal, mode)
}
if iErr != nil { // iVal is alphabetic, jVal is numeric
return lessAlphaNumericI(mode)
}
if jErr != nil { // iVal is numeric, jVal is alphabetic
return lessAlphaNumericJ(mode)
}
// both values numeric
return lessNumericVal(iNumVal, jNumVal, mode)
}

func lessNumeric(iVal string, jVal string, mode SortMode) (bool, bool) {
iNumVal, iErr := strconv.ParseFloat(iVal, 64)
jNumVal, jErr := strconv.ParseFloat(jVal, 64)
if iErr != nil || jErr != nil {
return false, false
}

return lessNumericVal(iNumVal, jNumVal, mode)
}

func lessNumericVal(iVal float64, jVal float64, mode SortMode) (bool, bool) {
if iVal == jVal {
return true, false
}

switch mode {
case AscNumeric, AscAlphaNumeric, AscNumericAlpha:
return false, iVal < jVal
default: // DscNumeric, DscAlphaNumeric, DscNumericAlpha
return false, iVal > jVal
}
return true, false
}
82 changes: 66 additions & 16 deletions table/sort_test.go
Original file line number Diff line number Diff line change
@@ -6,6 +6,72 @@ import (
"github.com/stretchr/testify/assert"
)

func TestTable_sortRows_MissingCells(t *testing.T) {
table := Table{}
table.AppendRows([]Row{
{1, "Arya", "Stark", 3000, 9},
{11, "Sansa", "Stark", 3000},
{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
{300, "Tyrion", "Lannister", 5000, 7},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by "First Name"
table.SortBy([]SortBy{{Number: 5, Mode: Asc}})
assert.Equal(t, []int{1, 3, 0, 2}, table.getSortedRowIndices())
}

func TestTable_sortRows_InvalidMode(t *testing.T) {
table := Table{}
table.AppendRows([]Row{
{1, "Arya", "Stark", 3000},
{11, "Sansa", "Stark", 3000},
{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
{300, "Tyrion", "Lannister", 5000},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by "First Name"
table.SortBy([]SortBy{{Number: 2, Mode: AscNumeric}})
assert.Equal(t, []int{0, 1, 2, 3}, table.getSortedRowIndices())
}

func TestTable_sortRows_MixedMode(t *testing.T) {
table := Table{}
table.AppendHeader(Row{"#", "First Name", "Last Name", "Salary"})
table.AppendRows([]Row{
/* 0 */ {1, "Arya", "Stark", 3000, 4},
/* 1 */ {11, "Sansa", "Stark", 3000},
/* 2 */ {20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
/* 3 */ {300, "Tyrion", "Lannister", 5000, -7.54},
/* 4 */ {400, "Jamie", "Lannister", 5000, nil},
/* 5 */ {500, "Tywin", "Lannister", 5000, "-7.540"},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by nothing
assert.Equal(t, []int{0, 1, 2, 3, 4, 5}, table.getSortedRowIndices())

// sort column #5 in Ascending order alphabetically and then numerically
table.SortBy([]SortBy{{Number: 5, Mode: AscAlphaNumeric}, {Number: 1, Mode: AscNumeric}})
assert.Equal(t, []int{1, 4, 2, 3, 5, 0}, table.getSortedRowIndices())

// sort column #5 in Ascending order numerically and then alphabetically
table.SortBy([]SortBy{{Number: 5, Mode: AscNumericAlpha}, {Number: 1, Mode: AscNumeric}})
assert.Equal(t, []int{3, 5, 0, 1, 4, 2}, table.getSortedRowIndices())

// sort column #5 in Descending order alphabetically and then numerically
table.SortBy([]SortBy{{Number: 5, Mode: DscAlphaNumeric}, {Number: 1, Mode: AscNumeric}})
assert.Equal(t, []int{2, 4, 1, 0, 3, 5}, table.getSortedRowIndices())

// sort column #5 in Descending order numerically and then alphabetically
table.SortBy([]SortBy{{Number: 5, Mode: DscNumericAlpha}, {Number: 1, Mode: AscNumeric}})
assert.Equal(t, []int{0, 3, 5, 2, 4, 1}, table.getSortedRowIndices())
}

func TestTable_sortRows_WithName(t *testing.T) {
table := Table{}
table.AppendHeader(Row{"#", "First Name", "Last Name", "Salary"})
@@ -130,19 +196,3 @@ func TestTable_sortRows_WithoutName(t *testing.T) {
table.SortBy(nil)
assert.Equal(t, []int{0, 1, 2, 3}, table.getSortedRowIndices())
}

func TestTable_sortRows_InvalidMode(t *testing.T) {
table := Table{}
table.AppendRows([]Row{
{1, "Arya", "Stark", 3000},
{11, "Sansa", "Stark", 3000},
{20, "Jon", "Snow", 2000, "You know nothing, Jon Snow!"},
{300, "Tyrion", "Lannister", 5000},
})
table.SetStyle(StyleDefault)
table.initForRenderRows()

// sort by "First Name"
table.SortBy([]SortBy{{Number: 2, Mode: AscNumeric}})
assert.Equal(t, []int{0, 1, 2, 3}, table.getSortedRowIndices())
}
Loading