-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrows.go
More file actions
320 lines (296 loc) · 9.93 KB
/
Copy pathrows.go
File metadata and controls
320 lines (296 loc) · 9.93 KB
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
package simplecsv
// GetNumberRows returns the number of rows, including the header row
func (s SimpleCsv) GetNumberRows() int {
return len(s)
}
// GetNumberDataRows returns the number of data rows, excluding the header
// row. For an empty csv or a csv with only the header row it returns 0.
// Use this instead of GetNumberRows when counting data: the header row is
// not data.
func (s SimpleCsv) GetNumberDataRows() int {
if len(s) > 0 {
return len(s) - 1
}
return 0
}
// GetDataRows returns copies of all the data rows (rows 1 to len(s)-1),
// excluding the header row. The caller can mutate the returned rows without
// affecting the csv. If the csv is empty or has only the header row, it
// returns an empty slice.
func (s SimpleCsv) GetDataRows() [][]string {
if len(s) <= 1 {
return [][]string{}
}
dataRows := make([][]string, 0, len(s)-1)
for i := 1; i < len(s); i++ {
dataRows = append(dataRows, copyRow(s[i]))
}
return dataRows
}
// EachDataRow calls fn for each data row (rowIndex >= 1), in csv order,
// with a copy of the row and a map view of it (header name to cell value).
// The row is a copy, so mutating it inside fn does not affect the csv; the
// map is rebuilt per row and is independent too. fn may return false to
// stop the iteration early. The header row is never passed to fn: an empty
// csv or a csv with only the header row calls fn zero times.
func (s SimpleCsv) EachDataRow(fn func(rowIndex int, row []string, asMap map[string]string) bool) {
headers := s.GetHeaders()
for i := 1; i < len(s); i++ {
row := copyRow(s[i])
rowAsMap := make(map[string]string, len(headers))
for k, v := range headers {
if k < len(row) {
rowAsMap[v] = row[k]
} else {
rowAsMap[v] = ""
}
}
if !fn(i, row, rowAsMap) {
return
}
}
}
// Head returns a new csv with the header row and the first n data rows, or
// all the data rows if there are fewer than n. If n is negative or zero,
// the result has no data rows (the header row only, if present). The
// original csv is not modified and the result shares no data with it.
func (s SimpleCsv) Head(n int) SimpleCsv {
if n < 0 {
n = 0
}
if n > len(s)-1 {
n = len(s) - 1
}
return s.sliceDataRows(0, n)
}
// Tail returns a new csv with the header row and the last n data rows, or
// all the data rows if there are fewer than n. If n is negative or zero,
// the result has no data rows (the header row only, if present). The
// original csv is not modified and the result shares no data with it.
func (s SimpleCsv) Tail(n int) SimpleCsv {
if n < 0 {
n = 0
}
if n > len(s)-1 {
n = len(s) - 1
}
return s.sliceDataRows(len(s)-1-n, len(s)-1)
}
// sliceDataRows returns a new csv with the header row and the data rows in
// the half-open range [dataStart, dataEnd), where 0 is the first data row
// (csv row 1). The result shares no data with s. On an empty csv the
// result is empty.
func (s SimpleCsv) sliceDataRows(dataStart, dataEnd int) SimpleCsv {
newCsv := SimpleCsv{}
if len(s) == 0 {
return newCsv
}
newCsv = append(newCsv, copyRow(s[0]))
for i := 1 + dataStart; i < 1+dataEnd && i < len(s); i++ {
newCsv = append(newCsv, copyRow(s[i]))
}
return newCsv
}
// SliceRows returns a new csv with the header row and the data rows whose
// csv row index is in the half-open range [start, end) (end is not
// included; data rows have csv index >= 1). If end is beyond the last row,
// all the available data rows are included. An empty range (end == start)
// is valid and returns the header row only. The original csv is not
// modified and the result shares no data with it. If the csv is empty,
// start is less than 1 or end is less than start, it returns a copy of the
// csv and false.
func (s SimpleCsv) SliceRows(start, end int) (SimpleCsv, bool) {
if len(s) == 0 || start < 1 || end < start {
return s.fail()
}
if end > len(s) {
end = len(s)
}
newCsv := make(SimpleCsv, 0, end-start+1)
newCsv = append(newCsv, copyRow(s[0]))
for i := start; i < end; i++ {
newCsv = append(newCsv, copyRow(s[i]))
}
return newCsv, true
}
// AppendRows returns a new csv with the data rows of other appended after
// the data rows of s. other must have exactly the same header names in the
// same order as s (other's header row itself is not appended). The original
// csvs are not modified and the result shares no data with them. If s or
// other is empty, or their headers differ, it returns a copy of s and
// false.
func (s SimpleCsv) AppendRows(other SimpleCsv) (SimpleCsv, bool) {
if len(s) == 0 || len(other) == 0 || !s.sameHeaders(other) {
return s.fail()
}
newCsv := copyRows(s)
for i := 1; i < len(other); i++ {
newCsv = append(newCsv, copyRow(other[i]))
}
return newCsv, true
}
// copyRow returns a copy of a row
func copyRow(row []string) []string {
newRow := make([]string, len(row))
copy(newRow, row)
return newRow
}
// copyRows returns a new csv with a copy of each row of s,
// so the result shares no data with s
func copyRows(s SimpleCsv) SimpleCsv {
newCsv := make(SimpleCsv, len(s))
for i, row := range s {
newCsv[i] = copyRow(row)
}
return newCsv
}
// fail returns an independent copy of s and false, so failure paths never
// alias the receiver.
func (s SimpleCsv) fail() (SimpleCsv, bool) {
return copyRows(s), false
}
// failE returns an independent copy of s and an error, so failure paths
// never alias the receiver.
func (s SimpleCsv) failE(err error) (SimpleCsv, error) {
return copyRows(s), err
}
// GetRow returns a copy of the row rowNumber
// If rowNumber does not exist, it returns an empty slice and false
func (s SimpleCsv) GetRow(rowNumber int) ([]string, bool) {
if rowNumber >= 0 && rowNumber < len(s) {
return copyRow(s[rowNumber]), true
}
return []string{}, false
}
// GetRowAsMap returns the row as a map
// If the row does not exist, returns nil and false
// If the row is shorter than the headers, missing cells are empty strings
func (s SimpleCsv) GetRowAsMap(rowNumber int) (map[string]string, bool) {
if rowNumber < 0 || rowNumber >= len(s) {
return nil, false
}
RowAsMap := make(map[string]string)
headers := s.GetHeaders()
for k, v := range headers {
if k < len(s[rowNumber]) {
RowAsMap[v] = s[rowNumber][k]
} else {
RowAsMap[v] = ""
}
}
return RowAsMap, true
}
// AddRow adds a row at the end of the csv and returns a new csv
// The row is copied, changes to the original slice don't affect the csv
// The original csv is not modified
func (s SimpleCsv) AddRow(rowValue []string) (SimpleCsv, bool) {
if len(s) == 0 || len(rowValue) != len(s[0]) {
return s.fail()
}
newCsv := append(copyRows(s), copyRow(rowValue))
return newCsv, true
}
// AddRowFromMap adds map values to a row.
// Ignores keys with unexisting headers
// Fills blank where the key does not exists
func (s SimpleCsv) AddRowFromMap(rowValue map[string]string) (SimpleCsv, bool) {
headers := s.GetHeaders()
var added bool
var newRow []string
for _, v := range headers {
value, _ := rowValue[v]
newRow = append(newRow, value)
}
s, added = s.AddRow(newRow)
return s, added
}
// SetRow updates a row in the csv and returns a new csv
// The row is copied, changes to the original slice don't affect the csv
// The original csv is not modified. Setting row 0 to a value with duplicate
// header names is rejected.
func (s SimpleCsv) SetRow(rowNumber int, rowValue []string) (SimpleCsv, bool) {
if len(s) == 0 || len(rowValue) != len(s[0]) || rowNumber >= len(s) || rowNumber < 0 {
return s.fail()
}
if rowNumber == 0 && hasDuplicateHeaders(rowValue) {
return s.fail()
}
newCsv := copyRows(s)
newCsv[rowNumber] = copyRow(rowValue)
return newCsv, true
}
// SetRowFromMap replaces a row by the maps value
// Ignores keys with unexisting headers
// Fills blank where the key does not exists
func (s SimpleCsv) SetRowFromMap(rowNumber int, rowValue map[string]string) (SimpleCsv, bool) {
headers := s.GetHeaders()
var newRow []string
var modified bool
for _, v := range headers {
value, _ := rowValue[v]
newRow = append(newRow, value)
}
s, modified = s.SetRow(rowNumber, newRow)
return s, modified
}
// UpdateRowCellsFromMap updates the cells whose column name is a key in the map
// and maintains the value of all the others.
// Ignores keys with unexisting headers.
// A key with an empty string value sets the cell to an empty string.
func (s SimpleCsv) UpdateRowCellsFromMap(rowNumber int, rowValue map[string]string) (SimpleCsv, bool) {
headers := s.GetHeaders()
positions := s.headerIndex()
var newRow []string
var modified bool
for _, v := range headers {
value, keyExists := rowValue[v]
if keyExists {
newRow = append(newRow, value)
continue
}
oldValue := ""
if column := positions[v]; rowNumber >= 0 && rowNumber < len(s) && column < len(s[rowNumber]) {
oldValue = s[rowNumber][column]
}
newRow = append(newRow, oldValue)
}
s, modified = s.SetRow(rowNumber, newRow)
return s, modified
}
// DeleteRow deletes the row and returns a new csv
// The original csv is not modified. Deleting row 0 is allowed only when the
// promoted row (if any) has unique header names.
func (s SimpleCsv) DeleteRow(rowNumber int) (SimpleCsv, bool) {
if rowNumber >= 0 && rowNumber < len(s) {
newCsv := make(SimpleCsv, 0, len(s)-1)
for i, row := range s {
if i != rowNumber {
newCsv = append(newCsv, copyRow(row))
}
}
if rowNumber == 0 && len(newCsv) > 0 && hasDuplicateHeaders(newCsv[0]) {
return s.fail()
}
return newCsv, true
}
return s.fail()
}
// FilterRows returns a new csv with copies of the rows where predicate
// returns true. If header is true, the first row is kept as the header and is
// not filtered. The original csv is not modified. The predicate receives a
// copy of each row and cannot mutate the source.
func (s SimpleCsv) FilterRows(predicate func(row []string) bool, header bool) SimpleCsv {
newCsv := SimpleCsv{}
start := 0
if header && len(s) > 0 {
newCsv = append(newCsv, copyRow(s[0]))
start = 1
}
for i := start; i < len(s); i++ {
row := copyRow(s[i])
if predicate(row) {
newCsv = append(newCsv, row)
}
}
return newCsv
}