diff --git a/integration_tests/features/orb_pack.feature b/integration_tests/features/orb_pack.feature index 0780e78dd..007c61fda 100644 --- a/integration_tests/features/orb_pack.feature +++ b/integration_tests/features/orb_pack.feature @@ -22,22 +22,27 @@ Feature: Orb pack And the exit status should be 0 @mocked_home_directory - Scenario: Orb pack with multiple includes fails + Scenario: Orb pack with multiple includes Given a file named "src/@orb.yml" with: """ commands: test: steps: - run: - command: <> <> + command: <> <> """ - Given a file named "src/script.sh" with "echo Hello, world!" + Given a file named "src/script.sh" with "echo Hello," + Given a file named "src/script2.sh" with "world!" When I run `circleci orb pack src` Then the output should contain: """ - Error: An unexpected error occurred: multiple include statements: '<> <>' + commands: + test: + steps: + - run: + command: echo Hello, world! """ - And the exit status should be 255 + And the exit status should be 0 @mocked_home_directory Scenario: Orb pack with include statement in bigger string @@ -47,15 +52,19 @@ Feature: Orb pack test: steps: - run: - command: include <> + command: echo "<>" """ - Given a file named "src/script.sh" with "echo Hello, world!" + Given a file named "src/script.sh" with "Hello, world!" When I run `circleci orb pack src` Then the output should contain: """ - Error: An unexpected error occurred: entire string must be include statement: 'include <>' + commands: + test: + steps: + - run: + command: echo "Hello, world!" """ - And the exit status should be 255 + And the exit status should be 0 @mocked_home_directory Scenario: Missing @orb.yml for orb packing diff --git a/process/process.go b/process/process.go index cb7745ae1..6b1753ca4 100644 --- a/process/process.go +++ b/process/process.go @@ -8,36 +8,27 @@ import ( "strings" ) -// MaybeIncludeFile replaces intsances of <> with the contents +// MaybeIncludeFile replaces instances of <> with the contents // of "file", escaping instances of "<<" within the file before returning, when // the <> parameter is the string passed. func MaybeIncludeFile(s string, orbDirectory string) (string, error) { // View: https://regexr.com/599mq includeRegex := regexp.MustCompile(`<<[\s]*include\(([-\w\/\.]+)\)?[\s]*>>`) - // only find up to 2 matches, because we throw an error if we find >1 - includeMatches := includeRegex.FindAllStringSubmatch(s, 2) - if len(includeMatches) > 1 { - return "", fmt.Errorf("multiple include statements: '%s'", s) - } + includeMatches := includeRegex.FindAllStringSubmatch(s, -1) - if len(includeMatches) == 1 { - match := includeMatches[0] + for _, match := range includeMatches { fullMatch, subMatch := match[0], match[1] - // throw an error if the entire string wasn't matched - if fullMatch != s { - return "", fmt.Errorf("entire string must be include statement: '%s'", s) - } - filepath := filepath.Join(orbDirectory, subMatch) file, err := ioutil.ReadFile(filepath) + if err != nil { return "", fmt.Errorf("could not open %s for inclusion", filepath) } - escaped := strings.ReplaceAll(string(file), "<<", "\\<<") - return escaped, nil + escaped := strings.ReplaceAll(string(file), "<<", "\\<<") + s = strings.ReplaceAll(s, fullMatch, escaped) } return s, nil diff --git a/process/process_test.go b/process/process_test.go new file mode 100644 index 000000000..23d8edc97 --- /dev/null +++ b/process/process_test.go @@ -0,0 +1,86 @@ +package process + +import ( + "io/ioutil" + "os" + "testing" +) + +func Test_MaybeIncludeFile(t *testing.T) { + testCases := []struct { + description string + template string + expected string + files map[string]string + errExpected bool + }{ + { + description: "File gets replaced", + template: "<>", + expected: "world", + files: map[string]string{ + "example.txt": "world", + }, + errExpected: false, + }, + { + description: "Partial line include", + template: "hello <>", + expected: "hello world", + files: map[string]string{ + "example-2.txt": "world", + }, + errExpected: false, + }, + { + description: "Multiple includes", + template: "<> <>", + expected: "hello world", + files: map[string]string{ + "example-1.txt": "hello", + "example-2.txt": "world", + }, + errExpected: false, + }, + { + description: "File does not exist", + template: "<>", + files: map[string]string{}, + errExpected: true, + }, + { + description: "Included files are escaped", + template: "<> world", + expected: "\\<< hello world", + files: map[string]string{ + "example-1.txt": "<< hello", + }, + errExpected: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + dir, err := ioutil.TempDir("", "circleci-cli-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + for name, content := range tc.files { + if err := ioutil.WriteFile(dir+"/"+name, []byte(content), 0644); err != nil { + t.Fatal(err) + } + } + + orbDirectory := dir + res, err := MaybeIncludeFile(tc.template, orbDirectory) + if err != nil && !tc.errExpected { + t.Errorf("Unexpected error: %v", err) + } + + if !tc.errExpected && res != tc.expected { + t.Errorf("expected '%s', got '%s'", tc.expected, res) + } + }) + } +}