-
Notifications
You must be signed in to change notification settings - Fork 1
/
ns.go
142 lines (120 loc) · 3.03 KB
/
ns.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
140
141
142
package namespace
// Enables you do define clauses that will be added to separate namespaces.
// By default anything in an undefined namespace is added to the global namespace.
// A namespace is defined by:
// namespace := "[" + token "]"
// token := [^/](ie not "/") + ("/" + token)*
// ie (non "/" character) + (optionally "/" + non "/" character)
//
// For example:
// [global]
// [pull/push]
//
// Empty spaces are skipped over and not
import (
"fmt"
"io"
"strings"
"github.com/odeke-em/go-utils/fread"
)
const (
commonCommandDelim = "/"
lBrace = "["
rBrace = "]"
GlobalNamespaceKey = "global"
)
type type_ uint
const (
tUnknown type_ = iota
tNamespace
tClause
)
func classify(line string) type_ {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, lBrace) {
return tNamespace
}
return tClause
}
type Namespace map[string][]string
func Parse(r io.Reader) (Namespace, error) {
// Expecting the form
// [command]
// key=value
lines := fread.Fread(r)
return ParseCh(lines)
}
func ParseCh(lines <-chan string) (Namespace, error) {
type nsSetter func(values ...string)
ns := make(Namespace)
makeNSSetter := func(nsKeys ...string) nsSetter {
return func(values ...string) {
for _, nsKey := range nsKeys {
if nsKey == "" {
nsKey = GlobalNamespaceKey
}
ns[nsKey] = append(ns[nsKey], values...)
}
}
}
globalNSSetter := makeNSSetter(GlobalNamespaceKey)
var lastNSSetter nsSetter = globalNSSetter
lineno := uint64(0)
for line := range lines {
typ := classify(line)
lineno += 1
switch typ {
case tNamespace:
namespaceKeys, err := parseOutNamespaceHeaders(line)
if err != nil {
return nil, err
}
lastNSSetter = makeNSSetter(namespaceKeys...)
case tClause:
clause, err := parseOutClause(line)
if err != nil {
return nil, err
}
if clause != "" {
lastNSSetter(clause)
}
}
}
return ns, nil
}
func parseOutClause(line string) (string, error) {
line = strings.TrimSpace(line)
return line, nil
}
func parseOutNamespaceHeaders(line string) ([]string, error) {
line = strings.TrimSpace(line)
lbc, rbc := strings.Count(line, lBrace), strings.Count(line, rBrace)
if lbc != 1 {
return nil, fmt.Errorf("expecting exactly 1 %q got %s", lBrace, line)
}
if rbc != 1 {
return nil, fmt.Errorf("expecting exactly 1 %q", rBrace)
}
lbi, rbi := strings.Index(line, lBrace), strings.Index(line, rBrace)
if lbi >= rbi {
return nil, fmt.Errorf("%q must be before %q", lBrace, rBrace)
}
namespaceName := line[lbi+1 : rbi]
splits := strings.Split(namespaceName, commonCommandDelim)
return prepareNamespaceKeys(splits), nil
}
func prepareNamespaceKeys(keys []string) []string {
var cleaned []string
for _, key := range keys {
key = strings.TrimSpace(key)
if key != "" {
cleaned = append(cleaned, key)
}
}
if len(keys) >= 1 && len(cleaned) < 1 {
// They wanted the global namespace but due to our strict policy
// of ignoring empty keys, we'll just reset to the globalNamespace
cleaned = []string{GlobalNamespaceKey}
}
return cleaned
}