-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathshape.go
More file actions
159 lines (154 loc) · 6.08 KB
/
Copy pathshape.go
File metadata and controls
159 lines (154 loc) · 6.08 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
package simplecsv
// AddEmptyColumnAt inserts an empty column at position index and returns a
// new csv. index must be between 0 and the number of columns (inclusive):
// inserting at the number of columns appends, like AddEmptyColumn. The
// header cell of the new column is columnName and every data cell is
// empty. The original csv is not modified and the result shares no data
// with it. If the csv is empty, the column name already exists or index is
// out of range, it returns a copy of the csv and false.
func (s SimpleCsv) AddEmptyColumnAt(columnName string, index int) (SimpleCsv, bool) {
if len(s) == 0 || s.GetHeaderPosition(columnName) != -1 || index < 0 || index > len(s[0]) {
return s.fail()
}
newCsv := make(SimpleCsv, len(s))
for i, row := range s {
newRow := make([]string, 0, len(row)+1)
newRow = append(newRow, row[:index]...)
if i == 0 {
newRow = append(newRow, columnName)
} else {
newRow = append(newRow, "")
}
newRow = append(newRow, row[index:]...)
newCsv[i] = newRow
}
return newCsv, true
}
// AddColumnByField appends a column with the header name columnName and
// the data cells values, and returns a new csv. len(values) must equal the
// number of data rows (len(s)-1), because every data row must keep exactly
// one cell in the column; a csv with only the header row takes an empty
// values slice. The values are copied: changes to the original slice don't
// affect the csv. The original csv is not modified and the result shares
// no data with it. If the csv is empty, the column name already exists or
// the length of values does not match, it returns a copy of the csv and
// false.
func (s SimpleCsv) AddColumnByField(columnName string, values []string) (SimpleCsv, bool) {
if len(s) == 0 || s.GetHeaderPosition(columnName) != -1 || len(values) != len(s)-1 {
return s.fail()
}
newCsv := make(SimpleCsv, len(s))
newCsv[0] = append(copyRow(s[0]), columnName)
for i, row := range s[1:] {
newRow := make([]string, len(row)+1)
copy(newRow, row)
newRow[len(row)] = values[i]
newCsv[i+1] = newRow
}
return newCsv, true
}
// AddComputedColumn appends a column with the header name columnName whose
// data cells are computed by fn from the values of the other columns, and
// returns a new csv. fn is called once per data row, in csv order, with a
// map from header name to cell value: the map is built fresh for every row
// and is a copy, so the callback can read field names instead of column
// indexes, and mutating the map does not affect the csv. The string fn
// returns becomes the cell of the new column. A csv with only the header
// row takes the new header cell only and fn is not called. The original
// csv is not modified and the result shares no data with it. If the csv is
// empty or the column name already exists, it returns a copy of the csv
// and false.
func (s SimpleCsv) AddComputedColumn(columnName string, fn func(row map[string]string) string) (SimpleCsv, bool) {
if len(s) == 0 || s.GetHeaderPosition(columnName) != -1 {
return s.fail()
}
newCsv := make(SimpleCsv, len(s))
newCsv[0] = append(copyRow(s[0]), columnName)
headers := s[0]
for i := 1; i < len(s); i++ {
rowMap := make(map[string]string, len(headers))
for j, h := range headers {
rowMap[h] = s[i][j]
}
newRow := make([]string, len(s[i])+1)
copy(newRow, s[i])
newRow[len(s[i])] = fn(rowMap)
newCsv[i] = newRow
}
return newCsv, true
}
// RenameHeaders renames several headers in one call and returns a new csv.
// Every key of pairs must be an existing header, and the result must not
// contain duplicate header names: a rename fails if a key does not exist,
// two keys map to the same new name, or a renamed column collides with a
// header that is not renamed. The renames are applied simultaneously: the
// new name of every column is looked up from the original header row, so a
// swap ("a" -> "b", "b" -> "a") is applied correctly and map iteration
// order does not matter. Renaming a header to its own name is a no-op for
// that column. An empty pairs map is a no-op that returns a copy of the
// csv and true. The original csv is not modified and the result shares no
// data with it. If the csv is empty, it returns a copy of the csv and
// false.
func (s SimpleCsv) RenameHeaders(pairs map[string]string) (SimpleCsv, bool) {
if len(s) == 0 {
return s.fail()
}
if len(pairs) == 0 {
return copyRows(s), true
}
positions := s.headerIndex()
for old := range pairs {
if _, exists := positions[old]; !exists {
return s.fail()
}
}
headers := s[0]
newHeaders := make([]string, len(headers))
for i, h := range headers {
if newName, renamed := pairs[h]; renamed {
newHeaders[i] = newName
} else {
newHeaders[i] = h
}
}
if hasDuplicateHeaders(newHeaders) {
return s.fail()
}
newCsv := copyRows(s)
newCsv[0] = newHeaders
return newCsv, true
}
// MoveColumn moves the column with the header name columnName to position
// newIndex and returns a new csv. The columns between the old and the new
// position shift by one, and every row keeps its cells: the header and
// data cells of the moved column move together. newIndex must be a valid
// column position (0 to len(headers)-1); moving a column to its own
// position is a no-op and is allowed. The original csv is not modified and
// the result shares no data with it. If the csv is empty, the column name
// does not exist or newIndex is out of range, it returns a copy of the csv
// and false.
func (s SimpleCsv) MoveColumn(columnName string, newIndex int) (SimpleCsv, bool) {
if len(s) == 0 || newIndex < 0 || newIndex >= len(s[0]) {
return s.fail()
}
position := s.GetHeaderPosition(columnName)
if position == -1 {
return s.fail()
}
if position == newIndex {
return copyRows(s), true
}
newCsv := make(SimpleCsv, len(s))
for i, row := range s {
moved := row[position]
rest := make([]string, 0, len(row)-1)
rest = append(rest, row[:position]...)
rest = append(rest, row[position+1:]...)
newRow := make([]string, 0, len(row))
newRow = append(newRow, rest[:newIndex]...)
newRow = append(newRow, moved)
newRow = append(newRow, rest[newIndex:]...)
newCsv[i] = newRow
}
return newCsv, true
}