-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathdecode.go
139 lines (123 loc) · 3.31 KB
/
decode.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
package asciitable
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
)
// Unmarshal parses an ASCII table into a slice of the specified type.
func Unmarshal[E any](asciiTable string, v E) ([]string, []E, error) {
var delimiter = "*+|"
var skip = []string{"---"}
lines := strings.Split(strings.TrimSpace(asciiTable), "\n")
if len(lines) < 4 {
return nil, nil, errors.New("invalid ascii table format, must have at least 4 lines")
}
// Look for headers in the first 10 lines
var headers []string
var headerLineNumber int
var defaultLines = 10
if len(lines) < defaultLines {
defaultLines = len(lines) - 1
}
for i, line := range lines[0:defaultLines] {
headerLine := strings.Trim(line, delimiter+" ")
if skipLine(headerLine, skip) {
continue
}
headers = splitRow(headerLine, delimiter)
if len(headers) > 1 {
headerLineNumber = i
break
}
}
// Reflect on the type of E
var results []E
elemType := reflect.TypeOf(v)
if elemType.Kind() != reflect.Struct {
return nil, nil, errors.New("v must be a struct type")
}
// Process rows
for _, line := range lines[headerLineNumber+1 : len(lines)-1] { // Begin after header
if skipLine(line, skip) {
continue
}
row := splitRow(strings.Trim(line, delimiter+" "), delimiter)
if len(row) != len(headers) {
return nil, nil, fmt.Errorf("row length does not match header length: %v", row)
}
// Map header to struct fields
elem := reflect.New(elemType).Elem()
for i, header := range headers {
for j := 0; j < elem.NumField(); j++ {
field := elemType.Field(j)
tag := field.Tag.Get("asciitable")
if tag == header {
value := row[i]
err := setFieldValue(elem.Field(j), value)
if err != nil {
return nil, nil, fmt.Errorf("failed to set value for field '%s': %v", field.Name, err)
}
}
}
}
// Append to results
results = append(results, elem.Interface().(E))
}
return headers, results, nil
}
// Helper function to split a row by the first character in the delimiter string and trim whitespace.
func splitRow(row string, delimiter string) []string {
var cells []string
for _, char := range strings.Split(delimiter, "") {
cells = strings.Split(row, char)
for i := range cells {
cells[i] = strings.TrimSpace(cells[i])
}
if len(cells) > 1 {
break
}
}
return cells
}
// Helper function to set a field value with proper type conversion
func setFieldValue(field reflect.Value, value string) error {
if !field.CanSet() {
return errors.New("cannot set value to the field")
}
switch field.Kind() {
case reflect.Bool:
boolVal, err := strconv.ParseBool(value)
if err != nil {
return err
}
field.SetBool(boolVal)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
intVal, err := strconv.Atoi(value)
if err != nil {
return err
}
field.SetInt(int64(intVal))
case reflect.Float32, reflect.Float64:
floatVal, err := strconv.ParseFloat(value, 64)
if err != nil {
return err
}
field.SetFloat(floatVal)
case reflect.String:
field.SetString(value)
default:
return fmt.Errorf("unsupported field type: %s", field.Kind())
}
return nil
}
// Return `true` if any string in the skip slice is in the line.
func skipLine(line string, skip []string) bool {
for _, s := range skip {
if strings.Contains(line, s) {
return true
}
}
return false
}