Skip to content

Commit 5994546

Browse files
authored
table: ImportGrid to import a 1d or 2d grid as rows (#343)
1 parent ada7500 commit 5994546

File tree

6 files changed

+209
-2
lines changed

6 files changed

+209
-2
lines changed

table/render_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import (
1010
"github.com/stretchr/testify/assert"
1111
)
1212

13-
func compareOutput(t *testing.T, out string, expectedOut string) {
13+
func compareOutput(t *testing.T, out string, expectedOut string, message ...interface{}) {
1414
if strings.HasPrefix(expectedOut, "\n") {
1515
expectedOut = strings.Replace(expectedOut, "\n", "", 1)
1616
}
17-
assert.Equal(t, expectedOut, out)
17+
assert.Equal(t, expectedOut, out, message...)
1818
if out != expectedOut {
1919
fmt.Printf("Expected:\n%s\nActual:\n%s\n", expectedOut, out)
2020
} else {

table/table.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,25 @@ func (t *Table) AppendSeparator() {
186186
}
187187
}
188188

189+
// ImportGrid helps import 1d or 2d arrays as rows.
190+
func (t *Table) ImportGrid(grid interface{}) bool {
191+
rows := objAsSlice(grid)
192+
if rows == nil {
193+
return false
194+
}
195+
addedRows := false
196+
for _, row := range rows {
197+
rowAsSlice := objAsSlice(row)
198+
if rowAsSlice != nil {
199+
t.AppendRow(rowAsSlice)
200+
} else if row != nil {
201+
t.AppendRow(Row{row})
202+
}
203+
addedRows = true
204+
}
205+
return addedRows
206+
}
207+
189208
// Length returns the number of rows to be rendered.
190209
func (t *Table) Length() int {
191210
return len(t.rowsRaw)

table/table_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package table
22

33
import (
4+
"fmt"
5+
"github.com/stretchr/testify/require"
46
"strings"
57
"testing"
68
"unicode/utf8"
@@ -150,6 +152,127 @@ func TestTable_AppendRows(t *testing.T) {
150152
assert.True(t, table.rowsConfigMap[3].AutoMerge)
151153
}
152154

155+
func TestTable_ImportGrid(t *testing.T) {
156+
t.Run("invalid grid", func(t *testing.T) {
157+
table := Table{}
158+
159+
assert.False(t, table.ImportGrid(nil))
160+
require.Len(t, table.rowsRaw, 0)
161+
162+
assert.False(t, table.ImportGrid(123))
163+
require.Len(t, table.rowsRaw, 0)
164+
165+
assert.False(t, table.ImportGrid("abc"))
166+
require.Len(t, table.rowsRaw, 0)
167+
168+
assert.False(t, table.ImportGrid(Table{}))
169+
require.Len(t, table.rowsRaw, 0)
170+
171+
assert.False(t, table.ImportGrid(&Table{}))
172+
require.Len(t, table.rowsRaw, 0)
173+
})
174+
175+
a, b, c := 1, 2, 3
176+
d, e, f := 4, 5, 6
177+
g, h, i := 7, 8, 9
178+
179+
t.Run("valid 1d", func(t *testing.T) {
180+
inputs := []interface{}{
181+
[3]int{a, b, c}, // array
182+
[]int{a, b, c}, // slice
183+
&[]int{a, b, c}, // pointer to slice
184+
[]*int{&a, &b, &c}, // slice of pointers-to-slices
185+
&[]*int{&a, &b, &c}, // pointer to slice of pointers
186+
}
187+
188+
for _, grid := range inputs {
189+
message := fmt.Sprintf("grid: %#v", grid)
190+
191+
table := Table{}
192+
table.Style().Options.SeparateRows = true
193+
assert.True(t, table.ImportGrid(grid), message)
194+
compareOutput(t, table.Render(), `
195+
+---+
196+
| 1 |
197+
+---+
198+
| 2 |
199+
+---+
200+
| 3 |
201+
+---+`, message)
202+
}
203+
})
204+
205+
t.Run("valid 2d", func(t *testing.T) {
206+
inputs := []interface{}{
207+
[3][3]int{{a, b, c}, {d, e, f}, {g, h, i}}, // array of arrays
208+
[3][]int{{a, b, c}, {d, e, f}, {g, h, i}}, // array of slices
209+
[][]int{{a, b, c}, {d, e, f}, {g, h, i}}, // slice of slices
210+
&[][]int{{a, b, c}, {d, e, f}, {g, h, i}}, // pointer-to-slice of slices
211+
[]*[]int{{a, b, c}, {d, e, f}, {g, h, i}}, // slice of pointers-to-slices
212+
&[]*[]int{{a, b, c}, {d, e, f}, {g, h, i}}, // pointer-to-slice of pointers-to-slices
213+
&[]*[]*int{{&a, &b, &c}, {&d, &e, &f}, {&g, &h, &i}}, // pointer-to-slice of pointers-to-slices of pointers
214+
}
215+
216+
for _, grid := range inputs {
217+
message := fmt.Sprintf("grid: %#v", grid)
218+
219+
table := Table{}
220+
table.Style().Options.SeparateRows = true
221+
assert.True(t, table.ImportGrid(grid), message)
222+
compareOutput(t, table.Render(), `
223+
+---+---+---+
224+
| 1 | 2 | 3 |
225+
+---+---+---+
226+
| 4 | 5 | 6 |
227+
+---+---+---+
228+
| 7 | 8 | 9 |
229+
+---+---+---+`, message)
230+
}
231+
})
232+
233+
t.Run("valid 2d with nil rows", func(t *testing.T) {
234+
inputs := []interface{}{
235+
[]*[]int{{a, b, c}, {d, e, f}, nil}, // slice of pointers-to-slices
236+
&[]*[]int{{a, b, c}, {d, e, f}, nil}, // pointer-to-slice of pointers-to-slices
237+
&[]*[]*int{{&a, &b, &c}, {&d, &e, &f}, nil}, // pointer-to-slice of pointers-to-slices of pointers
238+
}
239+
240+
for _, grid := range inputs {
241+
message := fmt.Sprintf("grid: %#v", grid)
242+
243+
table := Table{}
244+
table.Style().Options.SeparateRows = true
245+
assert.True(t, table.ImportGrid(grid), message)
246+
compareOutput(t, table.Render(), `
247+
+---+---+---+
248+
| 1 | 2 | 3 |
249+
+---+---+---+
250+
| 4 | 5 | 6 |
251+
+---+---+---+`, message)
252+
}
253+
})
254+
255+
t.Run("valid 2d with nil columns and rows", func(t *testing.T) {
256+
inputs := []interface{}{
257+
&[]*[]*int{{&a, &b, &c}, {&d, &e, nil}, nil},
258+
}
259+
260+
for _, grid := range inputs {
261+
message := fmt.Sprintf("grid: %#v", grid)
262+
263+
table := Table{}
264+
table.Style().Options.SeparateRows = true
265+
assert.True(t, table.ImportGrid(grid), message)
266+
compareOutput(t, table.Render(), `
267+
+---+---+---+
268+
| 1 | 2 | 3 |
269+
+---+---+---+
270+
| 4 | 5 | |
271+
+---+---+---+`, message)
272+
}
273+
})
274+
}
275+
153276
func TestTable_Length(t *testing.T) {
154277
table := Table{}
155278
assert.Zero(t, table.Length())

table/util.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,46 @@ func (m mergedColumnIndices) safeAppend(colIdx, otherColIdx int) {
6767
}
6868
m[otherColIdx][colIdx] = true
6969
}
70+
71+
func objAsSlice(in interface{}) []interface{} {
72+
var out []interface{}
73+
if in != nil {
74+
// dereference pointers
75+
val := reflect.ValueOf(in)
76+
if val.Kind() == reflect.Ptr && !val.IsNil() {
77+
in = val.Elem().Interface()
78+
}
79+
80+
if objIsSlice(in) {
81+
v := reflect.ValueOf(in)
82+
for i := 0; i < v.Len(); i++ {
83+
// dereference pointers
84+
v2 := v.Index(i)
85+
if v2.Kind() == reflect.Ptr && !v2.IsNil() {
86+
v2 = reflect.ValueOf(v2.Elem().Interface())
87+
}
88+
89+
out = append(out, v2.Interface())
90+
}
91+
}
92+
}
93+
94+
// remove trailing nil pointers
95+
tailIdx := len(out)
96+
for i := len(out) - 1; i >= 0; i-- {
97+
val := reflect.ValueOf(out[i])
98+
if val.Kind() != reflect.Ptr || !val.IsNil() {
99+
break
100+
}
101+
tailIdx = i
102+
}
103+
return out[:tailIdx]
104+
}
105+
106+
func objIsSlice(in interface{}) bool {
107+
if in == nil {
108+
return false
109+
}
110+
k := reflect.TypeOf(in).Kind()
111+
return k == reflect.Slice || k == reflect.Array
112+
}

table/util_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,24 @@ func TestIsNumber(t *testing.T) {
5151
assert.False(t, isNumber("1"))
5252
assert.False(t, isNumber(nil))
5353
}
54+
55+
func Test_objAsSlice(t *testing.T) {
56+
a, b, c := 1, 2, 3
57+
assert.Equal(t, "[1 2 3]", fmt.Sprint(objAsSlice([]int{a, b, c})))
58+
assert.Equal(t, "[1 2 3]", fmt.Sprint(objAsSlice(&[]int{a, b, c})))
59+
assert.Equal(t, "[1 2 3]", fmt.Sprint(objAsSlice(&[]*int{&a, &b, &c})))
60+
assert.Equal(t, "[1 2]", fmt.Sprint(objAsSlice(&[]*int{&a, &b, nil})))
61+
assert.Equal(t, "[1]", fmt.Sprint(objAsSlice(&[]*int{&a, nil, nil})))
62+
assert.Equal(t, "[]", fmt.Sprint(objAsSlice(&[]*int{nil, nil, nil})))
63+
assert.Equal(t, "[<nil> 2]", fmt.Sprint(objAsSlice(&[]*int{nil, &b, nil})))
64+
}
65+
66+
func Test_objIsSlice(t *testing.T) {
67+
assert.True(t, objIsSlice([]int{}))
68+
assert.True(t, objIsSlice([]*int{}))
69+
assert.False(t, objIsSlice(&[]int{}))
70+
assert.False(t, objIsSlice(&[]*int{}))
71+
assert.False(t, objIsSlice(Table{}))
72+
assert.False(t, objIsSlice(&Table{}))
73+
assert.False(t, objIsSlice(nil))
74+
}

table/writer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type Writer interface {
1111
AppendRow(row Row, configs ...RowConfig)
1212
AppendRows(rows []Row, configs ...RowConfig)
1313
AppendSeparator()
14+
ImportGrid(grid interface{}) bool
1415
Length() int
1516
Pager(opts ...PagerOption) Pager
1617
Render() string

0 commit comments

Comments
 (0)