annotate icf/icf.go @ 1:3e7247db5c6e

show index.html
author Atarwn Gard <a@qwa.su>
date Mon, 09 Mar 2026 01:04:16 +0500
parents 48bdab3eec8a
children d19133be91ba
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
1 // Package icf implements the Inherited Configuration Format parser.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
2 //
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
3 // ICF is a rule-based configuration format with variables, abstract blocks
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
4 // (mixins), pattern matching with named capture groups, and brace expansion.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
5 //
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
6 // Syntax:
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
7 //
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
8 // ; comment
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
9 // KEY=value variable
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
10 // @name abstract block (mixin)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
11 // |> directive arg {a,b} directive with optional brace expansion
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
12 // block.id @mixin concrete block inheriting a mixin
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
13 // <cap>.example.com block with named capture group
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
14 // <_>.example.com anonymous wildcard
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
15 package icf
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
16
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
17 import (
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
18 "bufio"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
19 "fmt"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
20 "io"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
21 "strings"
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
22 )
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
23
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
24 // Directive is a single key + arguments line inside a block.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
25 type Directive struct {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
26 Key string
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
27 Args []string
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
28 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
29
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
30 // Config holds the parsed and fully resolved state of an ICF file.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
31 type Config struct {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
32 vars map[string]string
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
33 abstract map[string][]Directive
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
34 Blocks []ParsedBlock
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
35 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
36
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
37 type ParsedBlock struct {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
38 ID string
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
39 Mixin string
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
40 Directives []Directive
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
41 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
42
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
43 // Parse reads an ICF document and returns a ready Config.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
44 func Parse(r io.Reader) (*Config, error) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
45 c := &Config{
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
46 vars: make(map[string]string),
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
47 abstract: make(map[string][]Directive),
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
48 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
49
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
50 var raw []ParsedBlock
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
51 var cur *ParsedBlock
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
52
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
53 flush := func() {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
54 if cur != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
55 raw = append(raw, *cur)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
56 cur = nil
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
57 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
58 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
59
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
60 scanner := bufio.NewScanner(r)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
61 for scanner.Scan() {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
62 line := stripComment(strings.TrimSpace(scanner.Text()))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
63 if line == "" {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
64 continue
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
65 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
66
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
67 // Variable assignment: KEY=value
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
68 if i := strings.Index(line, "="); i > 0 && !strings.HasPrefix(line, "|>") {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
69 key := line[:i]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
70 if isVarName(key) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
71 c.vars[key] = strings.TrimSpace(line[i+1:])
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
72 continue
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
73 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
74 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
75
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
76 // Directive: |> key args...
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
77 if strings.HasPrefix(line, "|>") {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
78 if cur == nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
79 return nil, fmt.Errorf("icf: directive outside block: %q", line)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
80 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
81 parts := strings.Fields(strings.TrimSpace(line[2:]))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
82 if len(parts) == 0 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
83 continue
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
84 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
85 cur.Directives = append(cur.Directives, Directive{
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
86 Key: parts[0],
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
87 Args: braceExpand(parts[1:]),
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
88 })
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
89 continue
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
90 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
91
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
92 // Block header: id [@mixin]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
93 flush()
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
94 parts := strings.Fields(line)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
95 pb := ParsedBlock{ID: parts[0]}
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
96 if len(parts) >= 2 && strings.HasPrefix(parts[1], "@") {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
97 pb.Mixin = parts[1][1:]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
98 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
99 cur = &pb
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
100 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
101 flush()
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
102
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
103 if err := scanner.Err(); err != nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
104 return nil, err
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
105 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
106
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
107 // Separate abstract from concrete blocks
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
108 for _, b := range raw {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
109 if strings.HasPrefix(b.ID, "@") {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
110 c.abstract[b.ID[1:]] = b.Directives
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
111 } else {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
112 c.Blocks = append(c.Blocks, b)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
113 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
114 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
115
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
116 return c, nil
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
117 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
118
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
119 // Abstract returns the directives of a named abstract block with variables
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
120 // substituted. Returns nil if not found.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
121 func (c *Config) Abstract(name string) []Directive {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
122 dirs, ok := c.abstract[name]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
123 if !ok {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
124 return nil
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
125 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
126 return c.applyVars(dirs, nil)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
127 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
128
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
129 // Match finds the most specific block matching input (e.g. "host/path") and
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
130 // returns resolved directives plus named captures.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
131 // Domain part is matched exactly (with captures); path part uses prefix match.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
132 func (c *Config) Match(input string) ([]Directive, map[string]string) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
133 inHost, inPath, _ := strings.Cut(input, "/")
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
134
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
135 type hit struct {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
136 block ParsedBlock
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
137 captures map[string]string
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
138 score int
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
139 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
140 var best *hit
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
141
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
142 for _, b := range c.Blocks {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
143 patHost, patPath, hasPath := strings.Cut(b.ID, "/")
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
144
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
145 caps := make(map[string]string)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
146
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
147 domScore, ok := matchExact(patHost, inHost, caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
148 if !ok {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
149 continue
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
150 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
151
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
152 pathScore := 0
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
153 if hasPath {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
154 pathScore, ok = matchPrefix(patPath, inPath, caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
155 if !ok {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
156 continue
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
157 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
158 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
159
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
160 score := domScore*1000 + pathScore
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
161 if best == nil || score > best.score {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
162 best = &hit{block: b, captures: caps, score: score}
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
163 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
164 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
165
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
166 if best == nil {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
167 return nil, nil
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
168 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
169
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
170 return c.ResolveBlock(best.block, best.captures), best.captures
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
171 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
172
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
173 // ResolveBlock merges mixin then block directives, substituting vars+captures.
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
174 func (c *Config) ResolveBlock(b ParsedBlock, caps map[string]string) []Directive {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
175 var merged []Directive
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
176 if b.Mixin != "" {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
177 merged = append(merged, c.abstract[b.Mixin]...)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
178 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
179 merged = append(merged, b.Directives...)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
180 return c.applyVars(merged, caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
181 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
182
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
183 func (c *Config) applyVars(dirs []Directive, caps map[string]string) []Directive {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
184 out := make([]Directive, len(dirs))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
185 for i, d := range dirs {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
186 out[i].Key = c.subst(d.Key, caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
187 out[i].Args = make([]string, len(d.Args))
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
188 for j, a := range d.Args {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
189 out[i].Args[j] = c.subst(a, caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
190 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
191 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
192 return out
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
193 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
194
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
195 func (c *Config) subst(s string, caps map[string]string) string {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
196 if !strings.Contains(s, "$") {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
197 return s
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
198 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
199 var b strings.Builder
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
200 i := 0
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
201 for i < len(s) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
202 if s[i] != '$' {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
203 b.WriteByte(s[i])
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
204 i++
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
205 continue
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
206 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
207 j := i + 1
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
208 for j < len(s) && isVarChar(s[j]) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
209 j++
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
210 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
211 name := s[i+1 : j]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
212 if v, ok := caps[name]; ok {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
213 b.WriteString(v)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
214 } else if v, ok := c.vars[name]; ok {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
215 b.WriteString(v)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
216 } else {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
217 b.WriteString(s[i:j])
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
218 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
219 i = j
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
220 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
221 return b.String()
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
222 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
223
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
224 func matchExact(pat, s string, caps map[string]string) (int, bool) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
225 score, rem, ok := matchCaptures(pat, s, caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
226 if !ok || rem != "" {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
227 return 0, false
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
228 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
229 return score, true
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
230 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
231
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
232 func matchPrefix(pat, s string, caps map[string]string) (int, bool) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
233 score, _, ok := matchCaptures(pat, s, caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
234 return score, ok
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
235 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
236
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
237 func matchCaptures(pat, inp string, caps map[string]string) (int, string, bool) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
238 for {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
239 if pat == "" {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
240 return 0, inp, true
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
241 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
242
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
243 if strings.HasPrefix(pat, "<") {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
244 end := strings.Index(pat, ">")
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
245 if end == -1 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
246 return 0, "", false
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
247 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
248 capName := pat[1:end]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
249 rest := pat[end+1:]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
250
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
251 for split := 1; split <= len(inp); split++ {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
252 candidate := inp[:split]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
253 remaining := inp[split:]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
254 score, finalRem, ok := matchCaptures(rest, remaining, caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
255 if ok {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
256 if capName != "_" {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
257 caps[capName] = candidate
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
258 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
259 return score, finalRem, true
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
260 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
261 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
262 return 0, "", false
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
263 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
264
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
265 if inp == "" || pat[0] != inp[0] {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
266 return 0, "", false
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
267 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
268 score, rem, ok := matchCaptures(pat[1:], inp[1:], caps)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
269 return score + 1, rem, ok
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
270 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
271 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
272
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
273 func braceExpand(args []string) []string {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
274 var out []string
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
275 for _, a := range args {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
276 out = append(out, expandOne(a)...)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
277 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
278 return out
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
279 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
280
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
281 func expandOne(s string) []string {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
282 start := strings.Index(s, "{")
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
283 end := strings.Index(s, "}")
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
284 if start == -1 || end == -1 || end < start {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
285 return []string{s}
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
286 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
287 prefix := s[:start]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
288 suffix := s[end+1:]
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
289 var out []string
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
290 for _, v := range strings.Split(s[start+1:end], ",") {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
291 out = append(out, prefix+strings.TrimSpace(v)+suffix)
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
292 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
293 return out
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
294 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
295
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
296 func stripComment(line string) string {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
297 if strings.HasPrefix(line, ";") {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
298 return ""
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
299 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
300 if i := strings.Index(line, " ;"); i != -1 {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
301 return strings.TrimSpace(line[:i])
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
302 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
303 return line
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
304 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
305
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
306 func isVarName(s string) bool {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
307 if s == "" {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
308 return false
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
309 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
310 for i := 0; i < len(s); i++ {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
311 if !isVarChar(s[i]) {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
312 return false
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
313 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
314 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
315 return true
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
316 }
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
317
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
318 func isVarChar(c byte) bool {
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
319 return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
320 (c >= '0' && c <= '9') || c == '_'
48bdab3eec8a Initial
Atarwn Gard <a@qwa.su>
parents:
diff changeset
321 }