-
Notifications
You must be signed in to change notification settings - Fork 4
/
format.go
184 lines (171 loc) · 4.91 KB
/
format.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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package table
import (
"bytes"
"encoding/csv"
"errors"
"fmt"
"os"
"strings"
"github.com/grokify/mogo/math/mathutil"
"github.com/grokify/mogo/text/markdown"
)
func (tbl *Table) Markdown(newline string, escPipe bool) string {
var md string
if len(tbl.Columns) > 0 {
md += markdown.TableRowToMarkdown(tbl.Columns, escPipe) + newline
if len(tbl.Rows) > 0 {
md += markdown.TableSeparator(uint(len(tbl.Columns))) + newline
}
}
return md + markdown.TableRowsToMarkdown(tbl.Rows, newline, escPipe, false)
}
func (tbl *Table) WriteMarkdown(filename string, perm os.FileMode, newline string, escPipe bool) error {
md := tbl.Markdown(newline, escPipe)
return os.WriteFile(filename, []byte(md), perm)
}
// Pivot takes a "straight table" where the columnn names
// and values are in a single column and lays it out as a standard tabular data.
func (tbl *Table) Pivot(colCount uint, haveColumns bool) (Table, error) {
newTbl := NewTable(tbl.Name)
if len(tbl.Columns) != 0 {
return newTbl, fmt.Errorf("has defined columns count [%d]", len(tbl.Columns))
}
isWellFormed, colCountActual, _ := tbl.IsWellFormed()
if !isWellFormed {
return newTbl, errors.New("table is not well-defined")
} else if colCountActual != 1 {
return newTbl, fmt.Errorf("has non-1 column count [%d]", colCountActual)
}
rowCount := len(tbl.Rows)
_, remainder := mathutil.DivideInt64(int64(rowCount), int64(colCount))
if remainder != 0 {
return newTbl, fmt.Errorf("row count [%d] is not a multiple of col count [%d]", rowCount, colCount)
}
addedColumns := false
newRow := []string{}
for i, row := range tbl.Rows {
_, remainder := mathutil.DivideInt64(int64(i), int64(colCount))
if remainder == 0 {
if len(newRow) > 0 {
if haveColumns && !addedColumns {
newTbl.Columns = newRow
addedColumns = true
} else {
newTbl.Rows = append(newTbl.Rows, newRow)
}
newRow = []string{}
}
}
newRow = append(newRow, row[0])
}
if len(newRow) > 0 {
if haveColumns && !addedColumns {
newTbl.Columns = newRow
} else {
newTbl.Rows = append(newTbl.Rows, newRow)
}
}
return newTbl, nil
}
/*
// FormatColumn takes a function to format all cell values.
func (tbl *Table) FormatColumn(colIdx uint, conv func(cellVal string) (string, error)) error {
colInt := int(colIdx)
for i, row := range tbl.Rows {
if colInt >= len(row) {
return fmt.Errorf("row [%d] is len [%d] without col index [%d]", i, len(row), colInt)
}
newVal, err := conv(row[colInt])
if err != nil {
return err
}
tbl.Rows[i][colInt] = newVal
}
return nil
}
*/
// FormatRows formats row cells using a start and ending column index and a convert function.
// The `format.ConvertDecommify()` and `format.ConvertRemoveControls()` functions are available to use.
func (tbl *Table) FormatRows(colIdxMinInc, colIdxMaxInc int, conv func(cellVal string) (string, error)) error {
err := tbl.formatRowsTry(colIdxMinInc, colIdxMaxInc, conv, false)
if err != nil {
return err
}
return tbl.formatRowsTry(colIdxMinInc, colIdxMaxInc, conv, true)
}
func (tbl *Table) formatRowsTry(colIdxMinInc, colIdxMaxInc int, conv func(cellVal string) (string, error), exec bool) error {
if len(tbl.Rows) == 0 {
return nil
}
if colIdxMinInc < 0 {
colIdxMinInc = 0
}
// test and return errors
for y, row := range tbl.Rows {
if colIdxMinInc >= len(row) {
continue
}
rowMaxIdxInc := colIdxMaxInc
if rowMaxIdxInc < 0 || rowMaxIdxInc >= len(row) {
rowMaxIdxInc = len(row) - 1
}
for x := colIdxMinInc; x <= rowMaxIdxInc; x++ {
val, err := conv(row[x])
if err != nil {
return err
}
if exec {
row[x] = val
}
}
if exec {
tbl.Rows[y] = row
}
}
return nil
}
// String writes the table out to a CSV string.
func (tbl *Table) String(comma rune, useCRLF bool) (string, error) {
var b bytes.Buffer
w := csv.NewWriter(&b)
w.Comma = comma
w.UseCRLF = useCRLF
if len(tbl.Columns) > 0 {
if err := w.Write(tbl.Columns); err != nil {
return "", fmt.Errorf("error writing columns to csv [%s]",
strings.Join(tbl.Columns, ","))
}
}
for i, row := range tbl.Rows {
if err := w.Write(row); err != nil {
return "", fmt.Errorf("error writing row to csv: idx [%d] content [%s]",
i, strings.Join(row, ","))
}
}
w.Flush()
return b.String(), w.Error()
}
// Transpose creates a new table by transposing the matrix data.
// In the new table, it does not set anything other than than `Name`, `Columns`, and `Rows`.
func (tbl *Table) Transpose() (Table, error) {
tbl2 := NewTable(tbl.Name)
isWellFormed, _, _ := tbl.IsWellFormed()
if !isWellFormed {
return tbl2, errors.New("can only transpose well formed table")
}
for x := 0; x < len(tbl.Columns); x++ {
newRow := []string{}
if len(tbl.Columns) > 0 {
newRow = append(newRow, tbl.Columns[x])
}
for y := 0; y < len(tbl.Rows); y++ {
newRow = append(newRow, tbl.Rows[y][x])
}
if x == 0 {
tbl2.Columns = newRow
} else {
tbl2.Rows = append(tbl2.Rows, newRow)
}
}
return tbl2, nil
}