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
|
package config
import (
"bufio"
"errors"
"fmt"
"io"
"slices"
)
// Config holds all parsed configuration entries from a Git config file.
//
// A Config preserves the ordering of entries as they appeared in the source.
//
// Lookups are matched case-insensitively for section and key names,
// and subsections must match exactly.
//
// Includes aren't supported yet; they will be supported in a later revision.
//
// Labels: MT-Safe.
type Config struct {
entries []Entry
}
// Parse reads and parses Git configuration entries from r.
func Parse(r io.Reader) (*Config, error) {
parser := &configParser{
reader: bufio.NewReader(r),
lineNum: 1,
currentSection: "",
currentSubsec: "",
peeked: 0,
hasPeeked: false,
}
return parser.parse()
}
// Entry represents a single parsed configuration directive.
type Entry struct {
// Section is the section name in canonical lowercase form.
Section string
// Subsection is the subsection name,
// retaining the exact form parsed from the input.
Subsection string
// Key is the key name in canonical lowercase form.
Key string
// Kind reports whether this entry has no value or an explicit value.
Kind Kind
// Value is the interpreted value of the configuration entry,
// including unescaped characters where appropriate.
Value string
}
// Entries returns a copy of all parsed configuration entries
// in the order they appeared.
//
// Modifying the returned slice does not affect the Config.
func (config *Config) Entries() []Entry {
return slices.Clone(config.entries)
}
type configParser struct {
reader *bufio.Reader
lineNum int
currentSection string
currentSubsec string
peeked byte
hasPeeked bool
}
func (p *configParser) parse() (*Config, error) {
cfg := &Config{
entries: nil,
}
err := p.skipBOM()
if err != nil {
return nil, err
}
for {
ch, err := p.nextChar()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
return nil, err
}
// Skip leading whitespace between entries.
if isWhitespace(ch) {
continue
}
// Comments
if ch == '#' || ch == ';' {
err := p.skipToEOL()
if err != nil && !errors.Is(err, io.EOF) {
return nil, err
}
continue
}
// Section header
if ch == '[' {
err := p.parseSection()
if err != nil {
return nil, err
}
continue
}
// Key-value pair
if isLetter(ch) {
p.unreadChar(ch)
err := p.parseKeyValue(cfg)
if err != nil {
return nil, err
}
continue
}
return nil, p.parseError(fmt.Sprintf("unexpected character %q", ch))
}
return cfg, nil
}
func (p *configParser) parseError(reason string) error {
return &ParseError{
Line: p.lineNum,
reason: reason,
}
}
|