From 7b315cd042c20045f83afc3689166d1fe48a8c7c Mon Sep 17 00:00:00 2001 From: Dmitry Shmulevich Date: Mon, 12 Aug 2024 07:22:53 -0700 Subject: [PATCH] support file paths with braces (#94) Signed-off-by: Dmitry Shmulevich --- pkg/config/config.go | 67 +++++++++++++++++++++++++++++++++-- pkg/config/config_test.go | 73 +++++++++++++++++++++++++++++++++++---- 2 files changed, 132 insertions(+), 8 deletions(-) diff --git a/pkg/config/config.go b/pkg/config/config.go index 2adb1bc..72581e0 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -72,10 +72,18 @@ func NewFromFile(path string) (*Workflow, error) { } func NewFromPaths(paths string) ([]*Workflow, error) { - cfgPaths := strings.Split(paths, ",") + if len(paths) == 0 { + return nil, fmt.Errorf("empty filepaths") + } + + files, err := parsePaths(paths) + if err != nil { + return nil, err + } + configs := []*Workflow{} var cfg *Workflow - for _, path := range cfgPaths { + for _, path := range files { fileInfo, err := os.Stat(path) if err != nil { return nil, err @@ -120,3 +128,58 @@ func (c *Workflow) validate() error { } return nil } + +func parsePaths(paths string) ([]string, error) { + start, n := 0, len(paths) + braces := false + ret := []string{} + + for i := 1; i < n; i++ { + switch paths[i] { + case '{': + if braces { + return nil, fmt.Errorf("unbalanced braces in %q", paths) + } + braces = true + case '}': + if !braces { + return nil, fmt.Errorf("unbalanced braces in %q", paths) + } + braces = false + case ',': + if !braces { + ret = append(ret, expandBraces(strings.TrimSpace(paths[start:i]))...) + start = i + 1 + } + } + } + if braces { + return nil, fmt.Errorf("unbalanced braces in %q", paths) + } + if start < n { + ret = append(ret, expandBraces(strings.TrimSpace(paths[start:n]))...) + } + + return ret, nil +} + +func expandBraces(pattern string) []string { + start := strings.Index(pattern, "{") + if start == -1 { + return []string{pattern} + } + end := strings.Index(pattern[start:], "}") + if end == -1 { + return []string{pattern} + } + end += start + prefix := pattern[:start] + suffix := pattern[end+1:] + parts := strings.Split(pattern[start+1:end], ",") + + var res []string + for _, part := range parts { + res = append(res, expandBraces(prefix+part+suffix)...) + } + return res +} diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index f913a3f..dbaea10 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -95,7 +95,7 @@ func TestWorkflowPaths(t *testing.T) { }{ { name: "Case 1: Empty string", - err: "stat : no such file or directory", + err: "empty filepaths", }, { name: "Case 2: Wrong path", @@ -104,22 +104,83 @@ func TestWorkflowPaths(t *testing.T) { }, { name: "Case 3: Valid input", - paths: "../../resources/workflows/volcano,../../resources/workflows/test-custom-resource.yml", - count: 2, + paths: "../../resources/workflows/volcano,../../resources/workflows/k8s/{test-job.yml,test-jobset.yml}", + count: 3, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { c, err := NewFromPaths(tc.paths) if len(tc.err) != 0 { - require.Error(t, err) - require.Equal(t, err.Error(), tc.err) + require.EqualError(t, err, tc.err) require.Nil(t, c) } else { - require.Nil(t, err) + require.NoError(t, err) require.NotNil(t, c) require.Equal(t, tc.count, len(c)) } }) } } + +func TestParsePaths(t *testing.T) { + testCases := []struct { + name string + input string + paths []string + err string + }{ + { + name: "Case 1a: valid, multi string", + input: "a/b/c, dd/ee/{f,g,e},/x/y/zz", + paths: []string{"a/b/c", "dd/ee/f", "dd/ee/g", "dd/ee/e", "/x/y/zz"}, + }, + { + name: "Case 1b: valid, single string", + input: "a/b/c", + paths: []string{"a/b/c"}, + }, + { + name: "Case 1c: valid, multiple braces", + input: "dd/{ee}/{f,g,e}/xx/{yy,zz}", + paths: []string{"dd/ee/f/xx/yy", "dd/ee/f/xx/zz", "dd/ee/g/xx/yy", "dd/ee/g/xx/zz", "dd/ee/e/xx/yy", "dd/ee/e/xx/zz"}, + }, + { + name: "Case 2a: unbalanced braces", + input: "a/b/c{{", + err: `unbalanced braces in "a/b/c{{"`, + }, + { + name: "Case 2b: unbalanced braces", + input: "a/b/c}", + err: `unbalanced braces in "a/b/c}"`, + }, + { + name: "Case 2c: unbalanced braces", + input: "a/b/c{", + err: `unbalanced braces in "a/b/c{"`, + }, + { + name: "Case 3: single character", + input: "a", + paths: []string{"a"}, + }, + { + name: "Case 4: empty string", + input: "", + paths: []string{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + paths, err := parsePaths(tc.input) + if len(tc.err) != 0 { + require.EqualError(t, err, tc.err) + } else { + require.NoError(t, err) + require.Equal(t, tc.paths, paths) + } + }) + } +}