Skip to content

Commit 1ff5254

Browse files
committed
git/gitattr: add a macro processor
When processing .gitattributes files, it's possible to have macros defined. Add a macro processor class that takes lists of attribute lines and expands them using macros, optionally reading in any macros from the file. This design allows us to process macros for some files and not others, like Git does.
1 parent 9d3e52d commit 1ff5254

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

git/gitattr/macro.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package gitattr
2+
3+
type MacroProcessor struct {
4+
macros map[string][]*Attr
5+
}
6+
7+
// NewMacroProcessor returns a new MacroProcessor object for parsing macros.
8+
func NewMacroProcessor() *MacroProcessor {
9+
macros := make(map[string][]*Attr)
10+
11+
// This is built into Git.
12+
macros["binary"] = []*Attr{
13+
&Attr{K: "diff", V: "false"},
14+
&Attr{K: "merge", V: "false"},
15+
&Attr{K: "text", V: "false"},
16+
}
17+
18+
return &MacroProcessor{
19+
macros: macros,
20+
}
21+
}
22+
23+
// ProcessLines reads the specified lines, returning a new set of lines which
24+
// all have a valid pattern. If readMacros is true, it additionally loads any
25+
// macro lines as it reads them.
26+
func (mp *MacroProcessor) ProcessLines(lines []*Line, readMacros bool) []*Line {
27+
result := make([]*Line, 0, len(lines))
28+
for _, line := range lines {
29+
if line.Pattern != nil {
30+
resultLine := Line{
31+
Pattern: line.Pattern,
32+
Attrs: make([]*Attr, 0, len(line.Attrs)),
33+
}
34+
for _, attr := range line.Attrs {
35+
macros := mp.macros[attr.K]
36+
if attr.V == "true" && macros != nil {
37+
resultLine.Attrs = append(
38+
resultLine.Attrs,
39+
macros...,
40+
)
41+
}
42+
43+
// Git copies through aliases as well as
44+
// expanding them.
45+
resultLine.Attrs = append(
46+
resultLine.Attrs,
47+
attr,
48+
)
49+
}
50+
result = append(result, &resultLine)
51+
} else if readMacros {
52+
mp.macros[line.Macro] = line.Attrs
53+
}
54+
}
55+
return result
56+
}

git/gitattr/macro_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package gitattr
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestProcessLinesWithMacros(t *testing.T) {
11+
lines, _, err := ParseLines(strings.NewReader(strings.Join([]string{
12+
"[attr]lfs filter=lfs diff=lfs merge=lfs -text",
13+
"*.dat lfs",
14+
"*.txt text"}, "\n")))
15+
16+
assert.Len(t, lines, 3)
17+
assert.NoError(t, err)
18+
19+
mp := NewMacroProcessor()
20+
lines = mp.ProcessLines(lines, true)
21+
22+
assert.Len(t, lines, 2)
23+
24+
assert.Equal(t, lines[0].Macro, "")
25+
assert.Equal(t, lines[0].Pattern.String(), "*.dat")
26+
assert.Len(t, lines[0].Attrs, 5)
27+
assert.Equal(t, lines[0].Attrs[0], &Attr{K: "filter", V: "lfs"})
28+
assert.Equal(t, lines[0].Attrs[1], &Attr{K: "diff", V: "lfs"})
29+
assert.Equal(t, lines[0].Attrs[2], &Attr{K: "merge", V: "lfs"})
30+
assert.Equal(t, lines[0].Attrs[3], &Attr{K: "text", V: "false"})
31+
assert.Equal(t, lines[0].Attrs[4], &Attr{K: "lfs", V: "true"})
32+
33+
assert.Equal(t, lines[1].Macro, "")
34+
assert.Equal(t, lines[1].Pattern.String(), "*.txt")
35+
assert.Len(t, lines[1].Attrs, 1)
36+
assert.Equal(t, lines[1].Attrs[0], &Attr{K: "text", V: "true"})
37+
}
38+
39+
func TestProcessLinesWithMacrosDisabled(t *testing.T) {
40+
lines, _, err := ParseLines(strings.NewReader(strings.Join([]string{
41+
"[attr]lfs filter=lfs diff=lfs merge=lfs -text",
42+
"*.dat lfs",
43+
"*.txt text"}, "\n")))
44+
45+
assert.Len(t, lines, 3)
46+
assert.NoError(t, err)
47+
48+
mp := NewMacroProcessor()
49+
lines = mp.ProcessLines(lines, false)
50+
51+
assert.Len(t, lines, 2)
52+
53+
assert.Equal(t, lines[0].Macro, "")
54+
assert.Equal(t, lines[0].Pattern.String(), "*.dat")
55+
assert.Len(t, lines[0].Attrs, 1)
56+
assert.Equal(t, lines[0].Attrs[0], &Attr{K: "lfs", V: "true"})
57+
58+
assert.Equal(t, lines[1].Macro, "")
59+
assert.Equal(t, lines[1].Pattern.String(), "*.txt")
60+
assert.Len(t, lines[1].Attrs, 1)
61+
assert.Equal(t, lines[1].Attrs[0], &Attr{K: "text", V: "true"})
62+
}
63+
64+
func TestProcessLinesWithBinaryMacros(t *testing.T) {
65+
lines, _, err := ParseLines(strings.NewReader(strings.Join([]string{
66+
"*.dat binary",
67+
"*.txt text"}, "\n")))
68+
69+
assert.Len(t, lines, 2)
70+
assert.NoError(t, err)
71+
72+
mp := NewMacroProcessor()
73+
lines = mp.ProcessLines(lines, true)
74+
75+
assert.Len(t, lines, 2)
76+
77+
assert.Equal(t, lines[0].Macro, "")
78+
assert.Equal(t, lines[0].Pattern.String(), "*.dat")
79+
assert.Len(t, lines[0].Attrs, 4)
80+
assert.Equal(t, lines[0].Attrs[0], &Attr{K: "diff", V: "false"})
81+
assert.Equal(t, lines[0].Attrs[1], &Attr{K: "merge", V: "false"})
82+
assert.Equal(t, lines[0].Attrs[2], &Attr{K: "text", V: "false"})
83+
assert.Equal(t, lines[0].Attrs[3], &Attr{K: "binary", V: "true"})
84+
85+
assert.Equal(t, lines[1].Macro, "")
86+
assert.Equal(t, lines[1].Pattern.String(), "*.txt")
87+
assert.Len(t, lines[1].Attrs, 1)
88+
assert.Equal(t, lines[1].Attrs[0], &Attr{K: "text", V: "true"})
89+
}
90+
91+
func TestProcessLinesIsStateful(t *testing.T) {
92+
lines, _, err := ParseLines(strings.NewReader(strings.Join([]string{
93+
"[attr]lfs filter=lfs diff=lfs merge=lfs -text",
94+
"*.txt text"}, "\n")))
95+
96+
assert.Len(t, lines, 2)
97+
assert.NoError(t, err)
98+
99+
mp := NewMacroProcessor()
100+
lines = mp.ProcessLines(lines, true)
101+
102+
assert.Len(t, lines, 1)
103+
104+
assert.Equal(t, lines[0].Macro, "")
105+
assert.Equal(t, lines[0].Pattern.String(), "*.txt")
106+
assert.Len(t, lines[0].Attrs, 1)
107+
assert.Equal(t, lines[0].Attrs[0], &Attr{K: "text", V: "true"})
108+
109+
lines2, _, err := ParseLines(strings.NewReader("*.dat lfs\n"))
110+
111+
assert.Len(t, lines2, 1)
112+
assert.NoError(t, err)
113+
114+
lines2 = mp.ProcessLines(lines2, false)
115+
116+
assert.Len(t, lines2, 1)
117+
118+
assert.Equal(t, lines2[0].Macro, "")
119+
assert.Equal(t, lines2[0].Pattern.String(), "*.dat")
120+
assert.Len(t, lines2[0].Attrs, 5)
121+
assert.Equal(t, lines2[0].Attrs[0], &Attr{K: "filter", V: "lfs"})
122+
assert.Equal(t, lines2[0].Attrs[1], &Attr{K: "diff", V: "lfs"})
123+
assert.Equal(t, lines2[0].Attrs[2], &Attr{K: "merge", V: "lfs"})
124+
assert.Equal(t, lines2[0].Attrs[3], &Attr{K: "text", V: "false"})
125+
assert.Equal(t, lines2[0].Attrs[4], &Attr{K: "lfs", V: "true"})
126+
}

0 commit comments

Comments
 (0)