Skip to content

Commit 8122a9b

Browse files
committed
Reverse sorting when listing with flag --reverse
(issue #126)
1 parent 5354545 commit 8122a9b

File tree

6 files changed

+140
-48
lines changed

6 files changed

+140
-48
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
### Features
44

5+
- Sync overall speed up and massive reducing in memory consumption
56
- SSH `--through`: `awless ssh my-priv-inst --through my-pub-inst` allow you to connect to a private instance by going through a public one in ths same VPC. You need to have the same keypair (SSH key) on both instances.
67
- Flag `--profile-sync` on `awless sync` to enable live profiling. Will dump `mem` and `cpu` Go profiling files for later inspection
78
- [#109](https://github.com/wallix/awless/issues/109): Support caching of STS credentials for Multi-Factor Authentication.
89
- [#126](https://github.com/wallix/awless/issues/126): Flag `--no-alias` in `awless show` force the display of IDs in relations.
10+
- [#126](https://github.com/wallix/awless/issues/126): Reverse sorting when listing resources with flag `--reverse`
911
- [#120](https://github.com/wallix/awless/issues/120): Profile info is now included in execution logs and appended when suggesting revert action
1012
- [#82](https://github.com/wallix/awless/issues/82): Better template TAB completion (e.g. complete list of parameters)
1113

@@ -18,7 +20,7 @@
1820

1921
### Fixes
2022

21-
- Reduce usage of memory when syncing, mainly for access resources (see issue #116)
23+
- [#116](https://github.com/wallix/awless/issues/116) No more sync Out Of Memory
2224

2325
## v0.1.1 [2017-07-06]
2426

commands/list.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var (
4040
listOnlyIDs bool
4141
noHeadersFlag bool
4242
sortBy []string
43+
reverseFlag bool
4344
)
4445

4546
func init() {
@@ -69,6 +70,7 @@ func init() {
6970
listCmd.PersistentFlags().StringSliceVar(&listingTagValueFiltersFlag, "tag-value", []string{}, "Filter EC2 resources given a tag value only (case sensitive!). Ex: --tag-value Staging")
7071
listCmd.PersistentFlags().BoolVar(&listOnlyIDs, "ids", false, "List only ids")
7172
listCmd.PersistentFlags().BoolVar(&noHeadersFlag, "no-headers", false, "Do not display headers")
73+
listCmd.PersistentFlags().BoolVar(&reverseFlag, "reverse", false, "Use in conjunction with --sort to reverse sort")
7274
listCmd.PersistentFlags().StringSliceVar(&sortBy, "sort", []string{"Id"}, "Sort tables by column(s) name(s)")
7375
}
7476

@@ -138,6 +140,7 @@ func printResources(g *graph.Graph, resType string) {
138140
console.WithFormat(listingFormat),
139141
console.WithIDsOnly(listOnlyIDs),
140142
console.WithSortBy(sortBy...),
143+
console.WithReverseSort(reverseFlag),
141144
console.WithNoHeaders(noHeadersFlag),
142145
).SetSource(g).Build()
143146
exitOn(err)

console/displayer.go

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ type Displayer interface {
4545
type sorter interface {
4646
sort(table)
4747
columns() []int
48+
symbol() string
4849
}
4950

5051
type Builder struct {
@@ -56,6 +57,7 @@ type Builder struct {
5657
format string
5758
rdfType string
5859
sort []int
60+
reverseSort bool
5961
maxwidth int
6062
dataSource interface{}
6163
root *graph.Resource
@@ -115,7 +117,7 @@ func (b *Builder) buildGraphTagValueFilters() (funcs []graph.FilterFn) {
115117
}
116118

117119
func (b *Builder) Build() (Displayer, error) {
118-
base := fromGraphDisplayer{sorter: &defaultSorter{sortBy: b.sort}, rdfType: b.rdfType, headers: b.headers, maxwidth: b.maxwidth, noHeaders: b.noHeaders}
120+
base := fromGraphDisplayer{sorter: &defaultSorter{sortBy: b.sort, descending: b.reverseSort}, rdfType: b.rdfType, headers: b.headers, maxwidth: b.maxwidth, noHeaders: b.noHeaders}
119121

120122
switch b.dataSource.(type) {
121123
case *graph.Graph:
@@ -314,6 +316,13 @@ func WithSortBy(sortingBy ...string) optsFn {
314316
}
315317
}
316318

319+
func WithReverseSort(r bool) optsFn {
320+
return func(b *Builder) *Builder {
321+
b.reverseSort = r
322+
return b
323+
}
324+
}
325+
317326
func WithMaxWidth(maxwidth int) optsFn {
318327
return func(b *Builder) *Builder {
319328
b.maxwidth = maxwidth
@@ -387,7 +396,7 @@ func (d *csvDisplayer) Print(w io.Writer) error {
387396

388397
var head []string
389398
for _, h := range d.headers {
390-
head = append(head, h.title(false))
399+
head = append(head, h.title())
391400
}
392401

393402
if !d.noHeaders {
@@ -436,7 +445,7 @@ func (d *tsvDisplayer) Print(w io.Writer) error {
436445

437446
var head []string
438447
for _, h := range d.headers {
439-
head = append(head, h.title(false))
448+
head = append(head, h.title())
440449
}
441450

442451
if !d.noHeaders {
@@ -517,12 +526,16 @@ func (d *tableDisplayer) Print(w io.Writer) error {
517526
columnsToDisplay = []ColumnDefinition{}
518527
currentWidth := 1 // first border
519528
for j, h := range d.headers {
520-
colW := colWidth(j, values, h, j == markColumnAsc) + 3 // +3 (tables margin + border)
529+
var symbol string
530+
if markColumnAsc == j {
531+
symbol = d.sorter.symbol()
532+
}
533+
colW := colWidth(j, values, h, symbol) + 3 // +3 (tables margin + border)
521534
if currentWidth+colW > d.maxwidth {
522535
break
523536
}
524537
currentWidth += colW
525-
maxWidthNoWraping += colWidthNoWraping(j, values, h, j == markColumnAsc) + 3
538+
maxWidthNoWraping += colWidthNoWraping(j, values, h, symbol) + 3
526539
columnsToDisplay = append(columnsToDisplay, h)
527540
}
528541
}
@@ -535,7 +548,11 @@ func (d *tableDisplayer) Print(w io.Writer) error {
535548
if !d.noHeaders {
536549
var displayHeaders []string
537550
for i, h := range columnsToDisplay {
538-
displayHeaders = append(displayHeaders, h.title(i == markColumnAsc))
551+
var symbol string
552+
if markColumnAsc == i {
553+
symbol = d.sorter.symbol()
554+
}
555+
displayHeaders = append(displayHeaders, h.title(symbol))
539556
}
540557
table.SetHeader(displayHeaders)
541558
}
@@ -564,7 +581,7 @@ func (d *tableDisplayer) Print(w io.Writer) error {
564581
if len(columnsToDisplay) < len(d.headers) {
565582
var hiddenColumns []string
566583
for i := len(columnsToDisplay); i < len(d.headers); i++ {
567-
hiddenColumns = append(hiddenColumns, "'"+d.headers[i].title(false)+"'")
584+
hiddenColumns = append(hiddenColumns, "'"+d.headers[i].title()+"'")
568585
}
569586
if len(hiddenColumns) == 1 {
570587
fmt.Fprint(w, color.New(color.FgRed).SprintfFunc()("Column truncated to fit terminal: %s\n", hiddenColumns[0]))
@@ -653,21 +670,23 @@ func (d *multiResourcesTableDisplayer) Print(w io.Writer) error {
653670
var row [4]interface{}
654671
row[0] = t
655672
row[1] = nameOrID(res)
656-
row[2] = header.title(false)
673+
row[2] = header.title()
657674
row[3] = header.format(val)
658675
values = append(values, row[:])
659676
}
660677
}
661678
}
662-
sort.Sort(byCols{table: values, sortBy: []int{0, 1, 2, 3}})
679+
680+
ds := defaultSorter{sortBy: []int{0, 1, 2, 3}}
681+
ds.sort(values)
663682

664683
table := tablewriter.NewWriter(w)
665684
table.SetAutoMergeCells(true)
666685
table.SetAlignment(tablewriter.ALIGN_LEFT)
667686
table.SetColWidth(tableColWidth)
668687
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
669688
table.SetCenterSeparator("|")
670-
table.SetHeader([]string{"Type" + ascSymbol, "Name/Id", "Property", "Value"})
689+
table.SetHeader([]string{"Type" + ds.symbol(), "Name/Id", "Property", "Value"})
671690

672691
wraper := autoWraper{maxWidth: autowrapMaxSize, wrappingChar: " "}
673692

@@ -784,13 +803,15 @@ func (d *diffTableDisplayer) Print(w io.Writer) error {
784803
}
785804
}
786805

787-
sort.Sort(byCols{table: values, sortBy: []int{0, 1, 2, 3}})
806+
ds := defaultSorter{sortBy: []int{0, 1, 2, 3}}
807+
ds.sort(values)
808+
788809
table := tablewriter.NewWriter(w)
789810
table.SetAutoMergeCells(true)
790811
table.SetAlignment(tablewriter.ALIGN_LEFT)
791812
table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
792813
table.SetCenterSeparator("|")
793-
table.SetHeader([]string{"Type" + ascSymbol, "Name/Id", "Property", "Value"})
814+
table.SetHeader([]string{"Type" + ds.symbol(), "Name/Id", "Property", "Value"})
794815

795816
for i := range values {
796817
row := make([]string, len(values[i]))
@@ -868,34 +889,45 @@ func (d *diffTreeDisplayer) Print(w io.Writer) error {
868889
}
869890

870891
type defaultSorter struct {
871-
sortBy []int
892+
sortBy []int
893+
descending bool
872894
}
873895

874896
func (d *defaultSorter) sort(lines table) {
875-
sort.Sort(byCols{table: lines, sortBy: d.sortBy})
897+
var compare func(i, j int) bool
898+
if d.descending {
899+
compare = func(j, i int) bool {
900+
for _, col := range d.sortBy {
901+
if reflect.DeepEqual(lines[i][col], lines[j][col]) {
902+
continue
903+
}
904+
return valueLowerOrEqual(lines[i][col], lines[j][col])
905+
}
906+
return false
907+
}
908+
} else {
909+
compare = func(i, j int) bool {
910+
for _, col := range d.sortBy {
911+
if reflect.DeepEqual(lines[i][col], lines[j][col]) {
912+
continue
913+
}
914+
return valueLowerOrEqual(lines[i][col], lines[j][col])
915+
}
916+
return false
917+
}
918+
}
919+
sort.Slice(lines, compare)
876920
}
877921

878922
func (d *defaultSorter) columns() []int {
879923
return d.sortBy
880924
}
881925

882-
type byCols struct {
883-
table table
884-
sortBy []int
885-
}
886-
887-
func (b byCols) Len() int { return len(b.table) }
888-
func (b byCols) Swap(i, j int) {
889-
b.table[i], b.table[j] = b.table[j], b.table[i]
890-
}
891-
func (b byCols) Less(i, j int) bool {
892-
for _, col := range b.sortBy {
893-
if reflect.DeepEqual(b.table[i][col], b.table[j][col]) {
894-
continue
895-
}
896-
return valueLowerOrEqual(b.table[i][col], b.table[j][col])
926+
func (d *defaultSorter) symbol() string {
927+
if d.descending {
928+
return " ▼"
897929
}
898-
return false
930+
return " ▲"
899931
}
900932

901933
func valueLowerOrEqual(a, b interface{}) bool {
@@ -941,7 +973,7 @@ func resolveSortIndexes(headers []ColumnDefinition, sortingBy ...string) ([]int,
941973

942974
normalized := make(map[string]int)
943975
for i, h := range headers {
944-
normalized[strings.ToLower(h.title(false))] = i
976+
normalized[strings.ToLower(h.title())] = i
945977
}
946978

947979
var ids []int
@@ -956,8 +988,8 @@ func resolveSortIndexes(headers []ColumnDefinition, sortingBy ...string) ([]int,
956988
return ids, nil
957989
}
958990

959-
func colWidth(j int, t table, h ColumnDefinition, hasSortSign bool) int {
960-
max := tablewriter.DisplayWidth(h.title(hasSortSign))
991+
func colWidth(j int, t table, h ColumnDefinition, sortSymbol string) int {
992+
max := tablewriter.DisplayWidth(h.title(sortSymbol))
961993
wraper := autoWraper{maxWidth: autowrapMaxSize, wrappingChar: " "}
962994
for i := range t {
963995
val := wraper.Wrap(h.format(t[i][j]))
@@ -978,8 +1010,8 @@ func colWidth(j int, t table, h ColumnDefinition, hasSortSign bool) int {
9781010
return max
9791011
}
9801012

981-
func colWidthNoWraping(j int, t table, h ColumnDefinition, hasSortSign bool) int {
982-
max := tablewriter.DisplayWidth(h.title(hasSortSign))
1013+
func colWidthNoWraping(j int, t table, h ColumnDefinition, sortSymbol string) int {
1014+
max := tablewriter.DisplayWidth(h.title(sortSymbol))
9831015
for i := range t {
9841016
val := h.format(t[i][j])
9851017
valLen := tablewriter.DisplayWidth(val)

console/displayer_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,63 @@ func init() {
3131
color.NoColor = true
3232
}
3333

34+
func TestSorting(t *testing.T) {
35+
g := createInfraGraph()
36+
headers := []ColumnDefinition{
37+
StringColumnDefinition{Prop: "ID"},
38+
StringColumnDefinition{Prop: "Name"},
39+
StringColumnDefinition{Prop: "State"},
40+
StringColumnDefinition{Prop: "Type"},
41+
StringColumnDefinition{Prop: "PublicIP", Friendly: "Public IP"},
42+
}
43+
var w bytes.Buffer
44+
45+
t.Run("ascending", func(t *testing.T) {
46+
w.Reset()
47+
displayer, _ := BuildOptions(
48+
WithHeaders(headers),
49+
WithRdfType("instance"),
50+
WithFormat("csv"),
51+
WithSortBy("Name"),
52+
).SetSource(g).Build()
53+
54+
expected := "ID,Name,State,Type,Public IP\n" +
55+
"inst_3,apache,running,t2.xlarge,\n" +
56+
"inst_2,django,stopped,t2.medium,\n" +
57+
"inst_1,redis,running,t2.micro,1.2.3.4\n"
58+
59+
if err := displayer.Print(&w); err != nil {
60+
t.Fatal(err)
61+
}
62+
if got, want := w.String(), expected; got != want {
63+
t.Fatalf("got \n%q\n\nwant\n\n%q\n", got, want)
64+
}
65+
})
66+
67+
t.Run("descending", func(t *testing.T) {
68+
w.Reset()
69+
displayer, _ := BuildOptions(
70+
WithHeaders(headers),
71+
WithRdfType("instance"),
72+
WithFormat("csv"),
73+
WithSortBy("Name"),
74+
WithReverseSort(true),
75+
).SetSource(g).Build()
76+
77+
expected := "ID,Name,State,Type,Public IP\n" +
78+
"inst_1,redis,running,t2.micro,1.2.3.4\n" +
79+
"inst_2,django,stopped,t2.medium,\n" +
80+
"inst_3,apache,running,t2.xlarge,\n"
81+
82+
if err := displayer.Print(&w); err != nil {
83+
t.Fatal(err)
84+
}
85+
if got, want := w.String(), expected; got != want {
86+
t.Fatalf("got \n%q\n\nwant\n\n%q\n", got, want)
87+
}
88+
})
89+
}
90+
3491
func TestJSONDisplays(t *testing.T) {
3592
g := createInfraGraph()
3693
var w bytes.Buffer

console/headers.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ import (
2727
"github.com/wallix/awless/graph"
2828
)
2929

30-
const ascSymbol = " ▲"
31-
3230
type TimeFormat int
3331

3432
const (
@@ -39,7 +37,7 @@ const (
3937

4038
type ColumnDefinition interface {
4139
propKey() string
42-
title(bool) string
40+
title(...string) string
4341
format(i interface{}) string
4442
}
4543

@@ -49,7 +47,7 @@ func (d ColumnDefinitions) resolveKey(name string) string {
4947
low := strings.ToLower(name)
5048
for _, def := range d {
5149
switch low {
52-
case strings.ToLower(def.propKey()), strings.ToLower(def.title(false)):
50+
case strings.ToLower(def.propKey()), strings.ToLower(def.title()):
5351
return def.propKey()
5452
}
5553
}
@@ -67,13 +65,13 @@ func (h StringColumnDefinition) format(i interface{}) string {
6765
return fmt.Sprint(i)
6866
}
6967
func (h StringColumnDefinition) propKey() string { return h.Prop }
70-
func (h StringColumnDefinition) title(displayAscSymbol bool) string {
68+
func (h StringColumnDefinition) title(suffix ...string) string {
7169
t := h.Friendly
7270
if t == "" {
7371
t = h.Prop
7472
}
75-
if displayAscSymbol {
76-
t += ascSymbol
73+
if len(suffix) > 0 {
74+
t += suffix[0]
7775
}
7876
return t
7977
}

0 commit comments

Comments
 (0)