From cdce508e2126e0b108c423ea9baf002d35a934aa Mon Sep 17 00:00:00 2001 From: Roland Illig Date: Sat, 14 Dec 2024 10:59:41 +0100 Subject: [PATCH] Implement expansion of curly braces These patterns are used in DependencyPattern, such as in conflict declarations, as well as in doc/pkg-vulnerabilities. --- v23/util.go | 36 ++++++++++++++++++++++++++++++++++++ v23/util_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/v23/util.go b/v23/util.go index 7686e9b8..cdce2616 100644 --- a/v23/util.go +++ b/v23/util.go @@ -1018,6 +1018,42 @@ func hasBalancedBraces(text string) bool { return n == 0 } +// expandCurlyBraces expands "a{b,c}d" to ["abd", "acd"]. +// The braces in s must be balanced, see hasBalancedBraces. +func expandCurlyBraces(s string) []string { + + find := func(i int, b byte) int { + n := 0 + for ; i < len(s); i++ { + switch { + case (s[i] == '}' || s[i] == b) && n == 0: + return i + case s[i] == '{': + n++ + case s[i] == '}': + n-- + } + } + return i + } + + lbrace := strings.IndexByte(s, '{') + rbrace := find(lbrace+1, '}') + if lbrace == -1 || rbrace == len(s) { + return []string{s} + } + + var expanded []string + pieceStart := lbrace + 1 + for pieceStart < rbrace+1 { + pieceEnd := find(pieceStart, ',') + word := s[0:lbrace] + s[pieceStart:pieceEnd] + s[rbrace+1:] + expanded = append(expanded, expandCurlyBraces(word)...) + pieceStart = pieceEnd + 1 + } + return expanded +} + var pathMatchers = make(map[string]*pathMatcher) type pathMatcher struct { diff --git a/v23/util_test.go b/v23/util_test.go index a8963678..5628cd07 100644 --- a/v23/util_test.go +++ b/v23/util_test.go @@ -908,6 +908,30 @@ func (s *Suite) Test_shquote(c *check.C) { test("~", "'~'") } +func Test_expandCurlyBraces(t *testing.T) { + tests := []struct { + s string + want []string + }{ + {"}", []string{"}"}}, + {"{}{}{}{}{}{}{}", []string{""}}, + {"{}}", []string{"}"}}, + {"{}{}{}{}{}{}{}}", []string{"}"}}, + {"{thir,f{our,if}}teen", []string{"thirteen", "fourteen", "fifteen"}}, + {"pkgname<=1.0", []string{"pkgname<=1.0"}}, + {"{pkgname,pkgname-client}<=1.0", []string{"pkgname<=1.0", "pkgname-client<=1.0"}}, + {"a{b,c,{d,e}}f", []string{"abf", "acf", "adf", "aef"}}, + } + for _, tt := range tests { + t.Run(tt.s, func(t *testing.T) { + got := expandCurlyBraces(tt.s) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("got %q, want %q", got, tt.want) + } + }) + } +} + func (s *Suite) Test_LazyStringBuilder_WriteByte__exact_match(c *check.C) { t := s.Init(c)