/
textcol.go
121 lines (103 loc) · 2.91 KB
/
textcol.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// borrowed from: https://github.com/acarl005/textcol/blob/master/textcol.go
package textcol
import (
"fmt"
"io"
"math"
"os"
"os/exec"
"strconv"
"strings"
"unicode/utf8"
"github.com/acarl005/stripansi"
)
var Output io.Writer = os.Stdout
func PrintColumns(strs *[]string, margin int) {
// get the longest string the columns need to contain
maxLength := 0
marginStr := strings.Repeat(" ", margin)
// also keep track of each individual length to easily calculate padding
lengths := []int{}
for _, str := range *strs {
colorless := stripansi.Strip(str)
// len() is insufficient here, as it counts emojis as 4 characters each
length := utf8.RuneCountInString(colorless)
maxLength = max(maxLength, length)
lengths = append(lengths, length)
}
// see how wide the terminal is
width := getTermWidth()
// calculate the dimensions of the columns
numCols, numRows := calculateTableSize(width, margin, maxLength, len(*strs))
// if we're forced into a single column, fall back to simple printing (one per line)
if numCols == 1 {
for _, str := range *strs {
fmt.Fprintln(Output, str)
}
return
}
// `i` will be a left-to-right index. this will need to get converted to a top-to-bottom index
for i := 0; i < numCols*numRows; i++ {
// treat output like a "table" with (x, y) coordinates as an intermediate representation
// first calculate (x, y) from i
x, y := rowIndexToTableCoords(i, numCols)
// then convery (x, y) to `j`, the top-to-bottom index
j := tableCoordsToColIndex(x, y, numRows)
// try to access the array, but the table might have more cells than array elements, so only try to access if within bounds
strLen := 0
str := ""
if j < len(lengths) {
strLen = lengths[j]
str = (*strs)[j]
}
// calculate the amount of padding required
numSpacesRequired := maxLength - strLen
spaceStr := strings.Repeat(" ", numSpacesRequired)
// print the item itself
fmt.Fprintf(Output, str)
// if we're at the last column, print a line break
if x+1 == numCols {
fmt.Fprintf(Output, "\n")
} else {
fmt.Fprintf(Output, spaceStr)
fmt.Fprintf(Output, marginStr)
}
}
}
func getTermWidth() int {
cmd := exec.Command("stty", "size")
cmd.Stdin = os.Stdin
out, err1 := cmd.Output()
check(err1)
numsStr := strings.Trim(string(out), "\n ")
width, err2 := strconv.Atoi(strings.Split(numsStr, " ")[1])
check(err2)
return width
}
func calculateTableSize(width, margin, maxLength, numCells int) (int, int) {
numCols := (width + margin) / (maxLength + margin)
if numCols == 0 {
numCols = 1
}
numRows := int(math.Ceil(float64(numCells) / float64(numCols)))
return numCols, numRows
}
func rowIndexToTableCoords(i, numCols int) (int, int) {
x := i % numCols
y := i / numCols
return x, y
}
func tableCoordsToColIndex(x, y, numRows int) int {
return y + numRows*x
}
func max(a int, b int) int {
if a > b {
return a
}
return b
}
func check(err error) {
if err != nil {
panic(err)
}
}