feat: multiple curly braces for watcher (#2068)

Allows doing something like this:

```caddyfile
watch "/app/{config,src}/*.{php,js}"
```

In the long term it would be nice to have pattern matching in the
watcher repo itself
This commit is contained in:
Alexander Stecher
2025-12-17 00:22:28 +01:00
committed by GitHub
parent a8f75d0eef
commit 175e644d10
2 changed files with 35 additions and 22 deletions

View File

@@ -127,7 +127,7 @@ func (p *pattern) isValidPattern(fileName string) bool {
// if the pattern has size 1 we can match it directly against the filename // if the pattern has size 1 we can match it directly against the filename
if len(p.parsedValues) == 1 { if len(p.parsedValues) == 1 {
return matchBracketPattern(p.parsedValues[0], fileNameWithoutDir) return matchCurlyBracePattern(p.parsedValues[0], fileNameWithoutDir)
} }
return p.matchPatterns(fileNameWithoutDir) return p.matchPatterns(fileNameWithoutDir)
@@ -159,7 +159,7 @@ func (p *pattern) matchPatterns(fileName string) bool {
cursor = j cursor = j
subPattern := strings.Join(partsToMatch[j:j+patternSize], string(filepath.Separator)) subPattern := strings.Join(partsToMatch[j:j+patternSize], string(filepath.Separator))
if matchBracketPattern(pattern, subPattern) { if matchCurlyBracePattern(pattern, subPattern) {
cursor = j + patternSize - 1 cursor = j + patternSize - 1
break break
@@ -174,24 +174,10 @@ func (p *pattern) matchPatterns(fileName string) bool {
return true return true
} }
// we also check for the following bracket syntax: /path/*.{php,twig,yaml} // we also check for the following syntax: /path/*.{php,twig,yaml}
func matchBracketPattern(pattern string, fileName string) bool { func matchCurlyBracePattern(pattern string, fileName string) bool {
openingBracket := strings.Index(pattern, "{") for _, subPattern := range expandCurlyBraces(pattern) {
closingBracket := strings.Index(pattern, "}") if matchPattern(subPattern, fileName) {
// if there are no brackets we can match regularly
if openingBracket == -1 || closingBracket == -1 {
return matchPattern(pattern, fileName)
}
beforeTheBrackets := pattern[:openingBracket]
betweenTheBrackets := pattern[openingBracket+1 : closingBracket]
afterTheBrackets := pattern[closingBracket+1:]
// all bracket entries are checked individually, only one needs to match
// *.{php,twig,yaml} -> *.php, *.twig, *.yaml
for pattern := range strings.SplitSeq(betweenTheBrackets, ",") {
if matchPattern(beforeTheBrackets+pattern+afterTheBrackets, fileName) {
return true return true
} }
} }
@@ -199,6 +185,26 @@ func matchBracketPattern(pattern string, fileName string) bool {
return false return false
} }
// {dir1,dir2}/path -> []string{"dir1/path", "dir2/path"}
func expandCurlyBraces(s string) []string {
before, rest, found := strings.Cut(s, "{")
if !found {
return []string{s}
}
inside, after, found := strings.Cut(rest, "}")
if !found {
return []string{s} // no closing brace
}
var out []string
for _, subPattern := range strings.Split(inside, ",") {
out = append(out, expandCurlyBraces(before+subPattern+after)...)
}
return out
}
func matchPattern(pattern string, fileName string) bool { func matchPattern(pattern string, fileName string) bool {
if pattern == "" { if pattern == "" {
return true return true

View File

@@ -259,7 +259,7 @@ func TestInvalidDirectoryPatterns(t *testing.T) {
} }
} }
func TestValidExtendedPatterns(t *testing.T) { func TestValidCurlyBracePatterns(t *testing.T) {
data := []struct { data := []struct {
pattern string pattern string
dir string dir string
@@ -272,6 +272,10 @@ func TestValidExtendedPatterns(t *testing.T) {
{"/path/{dir1,dir2}/file.php", "/path/dir2/file.php"}, {"/path/{dir1,dir2}/file.php", "/path/dir2/file.php"},
{"/app/{app,config,resources}/**/*.php", "/app/app/subpath/file.php"}, {"/app/{app,config,resources}/**/*.php", "/app/app/subpath/file.php"},
{"/app/{app,config,resources}/**/*.php", "/app/config/subpath/file.php"}, {"/app/{app,config,resources}/**/*.php", "/app/config/subpath/file.php"},
{"/path/{dir1,dir2}/{a,b}{a,b}.php", "/path/dir1/ab.php"},
{"/path/{dir1,dir2}/{a,b}{a,b}.php", "/path/dir2/aa.php"},
{"/path/{dir1,dir2}/{a,b}{a,b}.php", "/path/dir2/bb.php"},
{"/path/{dir1/test.php,dir2/test.php}", "/path/dir1/test.php"},
} }
for _, d := range data { for _, d := range data {
@@ -283,7 +287,7 @@ func TestValidExtendedPatterns(t *testing.T) {
} }
} }
func TestInvalidExtendedPatterns(t *testing.T) { func TestInvalidCurlyBracePatterns(t *testing.T) {
data := []struct { data := []struct {
pattern string pattern string
dir string dir string
@@ -293,6 +297,9 @@ func TestInvalidExtendedPatterns(t *testing.T) {
{"/path/{file.php,file.twig}", "/path/file.txt"}, {"/path/{file.php,file.twig}", "/path/file.txt"},
{"/path/{dir1,dir2}/file.php", "/path/dir3/file.php"}, {"/path/{dir1,dir2}/file.php", "/path/dir3/file.php"},
{"/path/{dir1,dir2}/**/*.php", "/path/dir1/subpath/file.txt"}, {"/path/{dir1,dir2}/**/*.php", "/path/dir1/subpath/file.txt"},
{"/path/{dir1,dir2}/{a,b}{a,b}.php", "/path/dir1/ac.php"},
{"/path/{}/{a,b}{a,b}.php", "/path/dir1/ac.php"},
{"/path/}dir{/{a,b}{a,b}.php", "/path/dir1/aa.php"},
} }
for _, d := range data { for _, d := range data {