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
|
package ini
import (
"fmt"
"strings"
"github.com/wiggin77/merror"
)
// LF is linefeed
const LF byte = 0x0A
// CR is carriage return
const CR byte = 0x0D
// getSections parses an INI formatted string, or string containing just name/value pairs,
// returns map of `Section`'s.
//
// Any name/value pairs appearing before a section name are added to the section named
// with an empty string (""). Also true for Linux-style config files where all props
// are outside a named section.
//
// Any errors encountered are aggregated and returned, along with the partially parsed
// sections.
func getSections(str string) (map[string]*Section, error) {
merr := merror.New()
mapSections := make(map[string]*Section)
lines := buildLineArray(str)
section := newSection("")
for _, line := range lines {
name, ok := parseSection(line)
if ok {
// A section name encountered. Stop processing the current one.
// Don't add the current section to the map if the section name is blank
// and the prop map is empty.
nameCurr := section.GetName()
if nameCurr != "" || section.hasKeys() {
mapSections[nameCurr] = section
}
// Start processing a new section.
section = newSection(name)
} else {
// Parse the property and add to the current section, or ignore if comment.
if k, v, comment, err := parseProp(line); !comment && err == nil {
section.setProp(k, v)
} else if err != nil {
merr.Append(err) // aggregate errors
}
}
}
// If the current section is not empty, add it.
if section.hasKeys() {
mapSections[section.GetName()] = section
}
return mapSections, merr.ErrorOrNil()
}
// buildLineArray parses the given string buffer and creates a list of strings,
// one for each line in the string buffer.
//
// A line is considered to be terminated by any one of a line feed ('\n'),
// a carriage return ('\r'), or a carriage return followed immediately by a
// linefeed.
//
// Lines prefixed with ';' or '#' are considered comments and skipped.
func buildLineArray(str string) []string {
arr := make([]string, 0, 10)
str = str + "\n"
iLen := len(str)
iPos, iBegin := 0, 0
var ch byte
for iPos < iLen {
ch = str[iPos]
if ch == LF || ch == CR {
sub := str[iBegin:iPos]
sub = strings.TrimSpace(sub)
if sub != "" && !strings.HasPrefix(sub, ";") && !strings.HasPrefix(sub, "#") {
arr = append(arr, sub)
}
iPos++
if ch == CR && iPos < iLen && str[iPos] == LF {
iPos++
}
iBegin = iPos
} else {
iPos++
}
}
return arr
}
// parseSection parses the specified string for a section name enclosed in square brackets.
// Returns the section name found, or `ok=false` if `str` is not a section header.
func parseSection(str string) (name string, ok bool) {
str = strings.TrimSpace(str)
if !strings.HasPrefix(str, "[") {
return "", false
}
iCloser := strings.Index(str, "]")
if iCloser == -1 {
return "", false
}
return strings.TrimSpace(str[1:iCloser]), true
}
// parseProp parses the specified string and extracts a key/value pair.
//
// If the string is a comment (prefixed with ';' or '#') then `comment=true`
// and key will be empty.
func parseProp(str string) (key string, val string, comment bool, err error) {
iLen := len(str)
iEqPos := strings.Index(str, "=")
if iEqPos == -1 {
return "", "", false, fmt.Errorf("not a key/value pair:'%s'", str)
}
key = str[0:iEqPos]
key = strings.TrimSpace(key)
if iEqPos+1 < iLen {
val = str[iEqPos+1:]
val = strings.TrimSpace(val)
}
// Check that the key has at least 1 char.
if key == "" {
return "", "", false, fmt.Errorf("key is empty for '%s'", str)
}
// Check if this line is a comment that just happens
// to have an equals sign in it. Not an error, but not a
// useable line either.
if strings.HasPrefix(key, ";") || strings.HasPrefix(key, "#") {
key = ""
val = ""
comment = true
}
return key, val, comment, err
}
|