Skip to content

Commit

Permalink
table: support sorting by any of the columns (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t committed May 22, 2018
1 parent 844e6f1 commit 090b0af
Show file tree
Hide file tree
Showing 14 changed files with 454 additions and 145 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Pretty-print tables into ASCII/Unicode strings.
- Custom (horizontal) Align per column
- Custom (vertical) VAlign per column (and multi-line column support)
- Mirror output to an io.Writer object (like os.StdOut)
- Sort by any of the Columns (by Column Name or Number)
- Completely customizable styles
- Many ready-to-use styles: [table/style.go](table/style.go)
- Colorize Headers/Body/Footers using [text/color](text/color.go)
Expand Down
83 changes: 0 additions & 83 deletions progress/tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package progress

import (
"fmt"
"sort"
"time"
)

Expand Down Expand Up @@ -147,85 +146,3 @@ func (tu Units) sprintBytes(value int64) string {
}
return fmt.Sprintf("%.2fPB", float64(value)/1000000000000000.0)
}

// SortBy helps sort a list of Trackers by various means.
type SortBy int

const (
// SortByNone doesn't do any sorting == sort by insertion order.
SortByNone SortBy = iota

// SortByMessage sorts by the Message alphabetically in ascending order.
SortByMessage

// SortByMessageDsc sorts by the Message alphabetically in descending order.
SortByMessageDsc

// SortByPercent sorts by the Percentage complete in ascending order.
SortByPercent

// SortByPercentDsc sorts by the Percentage complete in descending order.
SortByPercentDsc

// SortByValue sorts by the Value in ascending order.
SortByValue

// SortByValueDsc sorts by the Value in descending order.
SortByValueDsc
)

// Sort applies the sorting method defined by SortBy.
func (ts SortBy) Sort(trackers []*Tracker) {
switch ts {
case SortByMessage:
sort.Sort(sortByMessage(trackers))
case SortByMessageDsc:
sort.Sort(sortByMessageDsc(trackers))
case SortByPercent:
sort.Sort(sortByPercent(trackers))
case SortByPercentDsc:
sort.Sort(sortByPercentDsc(trackers))
case SortByValue:
sort.Sort(sortByValue(trackers))
case SortByValueDsc:
sort.Sort(sortByValueDsc(trackers))
default:
// no sort
}
}

type sortByMessage []*Tracker

func (ta sortByMessage) Len() int { return len(ta) }
func (ta sortByMessage) Swap(i, j int) { ta[i], ta[j] = ta[j], ta[i] }
func (ta sortByMessage) Less(i, j int) bool { return ta[i].Message < ta[j].Message }

type sortByMessageDsc []*Tracker

func (ta sortByMessageDsc) Len() int { return len(ta) }
func (ta sortByMessageDsc) Swap(i, j int) { ta[i], ta[j] = ta[j], ta[i] }
func (ta sortByMessageDsc) Less(i, j int) bool { return ta[i].Message > ta[j].Message }

type sortByPercent []*Tracker

func (ta sortByPercent) Len() int { return len(ta) }
func (ta sortByPercent) Swap(i, j int) { ta[i], ta[j] = ta[j], ta[i] }
func (ta sortByPercent) Less(i, j int) bool { return ta[i].PercentDone() < ta[j].PercentDone() }

type sortByPercentDsc []*Tracker

func (ta sortByPercentDsc) Len() int { return len(ta) }
func (ta sortByPercentDsc) Swap(i, j int) { ta[i], ta[j] = ta[j], ta[i] }
func (ta sortByPercentDsc) Less(i, j int) bool { return ta[i].PercentDone() > ta[j].PercentDone() }

type sortByValue []*Tracker

func (ta sortByValue) Len() int { return len(ta) }
func (ta sortByValue) Swap(i, j int) { ta[i], ta[j] = ta[j], ta[i] }
func (ta sortByValue) Less(i, j int) bool { return ta[i].value < ta[j].value }

type sortByValueDsc []*Tracker

func (ta sortByValueDsc) Len() int { return len(ta) }
func (ta sortByValueDsc) Swap(i, j int) { ta[i], ta[j] = ta[j], ta[i] }
func (ta sortByValueDsc) Less(i, j int) bool { return ta[i].value > ta[j].value }
85 changes: 85 additions & 0 deletions progress/tracker_sort.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package progress

import "sort"

// SortBy helps sort a list of Trackers by various means.
type SortBy int

const (
// SortByNone doesn't do any sorting == sort by insertion order.
SortByNone SortBy = iota

// SortByMessage sorts by the Message alphabetically in ascending order.
SortByMessage

// SortByMessageDsc sorts by the Message alphabetically in descending order.
SortByMessageDsc

// SortByPercent sorts by the Percentage complete in ascending order.
SortByPercent

// SortByPercentDsc sorts by the Percentage complete in descending order.
SortByPercentDsc

// SortByValue sorts by the Value in ascending order.
SortByValue

// SortByValueDsc sorts by the Value in descending order.
SortByValueDsc
)

// Sort applies the sorting method defined by SortBy.
func (sb SortBy) Sort(trackers []*Tracker) {
switch sb {
case SortByMessage:
sort.Sort(sortByMessage(trackers))
case SortByMessageDsc:
sort.Sort(sortByMessageDsc(trackers))
case SortByPercent:
sort.Sort(sortByPercent(trackers))
case SortByPercentDsc:
sort.Sort(sortByPercentDsc(trackers))
case SortByValue:
sort.Sort(sortByValue(trackers))
case SortByValueDsc:
sort.Sort(sortByValueDsc(trackers))
default:
// no sort
}
}

type sortByMessage []*Tracker

func (sb sortByMessage) Len() int { return len(sb) }
func (sb sortByMessage) Swap(i, j int) { sb[i], sb[j] = sb[j], sb[i] }
func (sb sortByMessage) Less(i, j int) bool { return sb[i].Message < sb[j].Message }

type sortByMessageDsc []*Tracker

func (sb sortByMessageDsc) Len() int { return len(sb) }
func (sb sortByMessageDsc) Swap(i, j int) { sb[i], sb[j] = sb[j], sb[i] }
func (sb sortByMessageDsc) Less(i, j int) bool { return sb[i].Message > sb[j].Message }

type sortByPercent []*Tracker

func (sb sortByPercent) Len() int { return len(sb) }
func (sb sortByPercent) Swap(i, j int) { sb[i], sb[j] = sb[j], sb[i] }
func (sb sortByPercent) Less(i, j int) bool { return sb[i].PercentDone() < sb[j].PercentDone() }

type sortByPercentDsc []*Tracker

func (sb sortByPercentDsc) Len() int { return len(sb) }
func (sb sortByPercentDsc) Swap(i, j int) { sb[i], sb[j] = sb[j], sb[i] }
func (sb sortByPercentDsc) Less(i, j int) bool { return sb[i].PercentDone() > sb[j].PercentDone() }

type sortByValue []*Tracker

func (sb sortByValue) Len() int { return len(sb) }
func (sb sortByValue) Swap(i, j int) { sb[i], sb[j] = sb[j], sb[i] }
func (sb sortByValue) Less(i, j int) bool { return sb[i].value < sb[j].value }

type sortByValueDsc []*Tracker

func (sb sortByValueDsc) Len() int { return len(sb) }
func (sb sortByValueDsc) Swap(i, j int) { sb[i], sb[j] = sb[j], sb[i] }
func (sb sortByValueDsc) Less(i, j int) bool { return sb[i].value > sb[j].value }
50 changes: 50 additions & 0 deletions progress/tracker_sort_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package progress

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestSortBy(t *testing.T) {
trackers := []*Tracker{
{Message: "Downloading File # 2", Total: 1000, value: 300},
{Message: "Downloading File # 1", Total: 1000, value: 100},
{Message: "Downloading File # 3", Total: 1000, value: 500},
}

SortByNone.Sort(trackers)
assert.Equal(t, "Downloading File # 2", trackers[0].Message)
assert.Equal(t, "Downloading File # 1", trackers[1].Message)
assert.Equal(t, "Downloading File # 3", trackers[2].Message)

SortByMessage.Sort(trackers)
assert.Equal(t, "Downloading File # 1", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 3", trackers[2].Message)

SortByMessageDsc.Sort(trackers)
assert.Equal(t, "Downloading File # 3", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 1", trackers[2].Message)

SortByPercent.Sort(trackers)
assert.Equal(t, "Downloading File # 1", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 3", trackers[2].Message)

SortByPercentDsc.Sort(trackers)
assert.Equal(t, "Downloading File # 3", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 1", trackers[2].Message)

SortByValue.Sort(trackers)
assert.Equal(t, "Downloading File # 1", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 3", trackers[2].Message)

SortByValueDsc.Sort(trackers)
assert.Equal(t, "Downloading File # 3", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 1", trackers[2].Message)
}
45 changes: 1 addition & 44 deletions progress/tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package progress

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"time"
)

func TestTracker_Increment(t *testing.T) {
Expand Down Expand Up @@ -114,46 +114,3 @@ func TestUnits_Sprint(t *testing.T) {
assert.Equal(t, "£1.50Q", UnitsCurrencyPound.Sprint(1500000000000000))
assert.Equal(t, "£1500.00Q", UnitsCurrencyPound.Sprint(1500000000000000000))
}

func TestSortBy(t *testing.T) {
trackers := []*Tracker{
{Message: "Downloading File # 2", Total: 1000, value: 300},
{Message: "Downloading File # 1", Total: 1000, value: 100},
{Message: "Downloading File # 3", Total: 1000, value: 500},
}

SortByNone.Sort(trackers)
assert.Equal(t, "Downloading File # 2", trackers[0].Message)
assert.Equal(t, "Downloading File # 1", trackers[1].Message)
assert.Equal(t, "Downloading File # 3", trackers[2].Message)

SortByMessage.Sort(trackers)
assert.Equal(t, "Downloading File # 1", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 3", trackers[2].Message)

SortByMessageDsc.Sort(trackers)
assert.Equal(t, "Downloading File # 3", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 1", trackers[2].Message)

SortByPercent.Sort(trackers)
assert.Equal(t, "Downloading File # 1", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 3", trackers[2].Message)

SortByPercentDsc.Sort(trackers)
assert.Equal(t, "Downloading File # 3", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 1", trackers[2].Message)

SortByValue.Sort(trackers)
assert.Equal(t, "Downloading File # 1", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 3", trackers[2].Message)

SortByValueDsc.Sort(trackers)
assert.Equal(t, "Downloading File # 3", trackers[0].Message)
assert.Equal(t, "Downloading File # 2", trackers[1].Message)
assert.Equal(t, "Downloading File # 1", trackers[2].Message)
}
1 change: 1 addition & 0 deletions table/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Pretty-print tables into ASCII/Unicode strings.
- Custom (horizontal) Align per column
- Custom (vertical) VAlign per column (and multi-line column support)
- Mirror output to an io.Writer object (like os.StdOut)
- Sort by any of the Columns (by Column Name or Number)
- Completely customizable styles
- Many ready-to-use styles: [style.go](style.go)
- Colorize Headers/Body/Footers using [../text/color.go](../text/color.go)
Expand Down
9 changes: 7 additions & 2 deletions table/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,13 @@ func (t *Table) renderRow(out *strings.Builder, rowNum int, row rowStr, colors [
}

func (t *Table) renderRows(out *strings.Builder, rows []rowStr, colors []text.Colors, format text.Format, hint renderHint) {
for idx, row := range rows {
t.renderRow(out, idx+1, row, colors, format, hint)
for idx := range rows {
sortedIdx := idx
if hint.isRegularRow() {
sortedIdx = t.sortedRowIndices[idx]
}

t.renderRow(out, idx+1, rows[sortedIdx], colors, format, hint)
if t.style.Options.SeparateRows && idx < len(rows)-1 {
t.renderRowSeparator(out, hint)
}
Expand Down
23 changes: 23 additions & 0 deletions table/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,29 @@ func TestTable_Render_Empty(t *testing.T) {
assert.Empty(t, tw.Render())
}

func TestTable_Render_Sorted(t *testing.T) {
tw := NewWriter()
tw.AppendHeader(testHeader)
tw.AppendRows(testRows)
tw.AppendRow(Row{11, "Sansa", "Stark", 6000})
tw.AppendFooter(testFooter)
tw.SetStyle(StyleLight)
tw.SortBy([]SortBy{{Name: "Last Name", Mode: Asc}, {Name: "First Name", Mode: Asc}})

expectedOut := `┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
│ # │ FIRST NAME │ LAST NAME │ SALARY │ │
├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
│ 300 │ Tyrion │ Lannister │ 5000 │ │
│ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
│ 1 │ Arya │ Stark │ 3000 │ │
│ 11 │ Sansa │ Stark │ 6000 │ │
├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
│ │ │ TOTAL │ 10000 │ │
└─────┴────────────┴───────────┴────────┴─────────────────────────────┘`
fmt.Println(tw.Render())
assert.Equal(t, expectedOut, tw.Render())
}

func TestTable_Render_TableWithinTable(t *testing.T) {
twInner := NewWriter()
twInner.AppendHeader(testHeader)
Expand Down

0 comments on commit 090b0af

Please sign in to comment.