From d5593ad4a2e74a7d6194820076928b2eed5ea02d Mon Sep 17 00:00:00 2001 From: Jay <1204942+rogueserenity@users.noreply.github.com> Date: Fri, 5 Jul 2024 19:17:19 -0600 Subject: [PATCH] fix: exclude init-only files when updating (#36) * test: remove wip tag * test: dump stdout, err for failures * fix: set update flag in config during updates * fix: exclude init-only files on update * fix: exclude update from config write --- cmd/update.go | 2 ++ config/config.go | 3 ++ features/init/local.feature | 1 - features/steps/local.py | 53 +++++++++++++++++++++++++++++++++++ features/steps/stenciler.py | 4 +++ features/update/local.feature | 6 ++++ files/common.go | 31 ++++++++++++++++++++ files/raw.go | 29 ++++++------------- files/template.go | 43 +++++++++------------------- 9 files changed, 121 insertions(+), 51 deletions(-) diff --git a/cmd/update.go b/cmd/update.go index 6aeebad..bd76b5e 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -52,6 +52,8 @@ func doUpdate() { cobra.CheckErr(err) } + mergedTemplate.Update = true + updateWrite(mergedTemplate) } diff --git a/config/config.go b/config/config.go index fe51a30..77b608b 100644 --- a/config/config.go +++ b/config/config.go @@ -55,6 +55,9 @@ type Param struct { // Template holds all of the values for a template configuration. The paths defined by init-only and raw-copy are // relative to directory. The directory and hook paths are all relative to the repository root. type Template struct { + // Update is true if the current command is update. This is not saved in the config file and is only used during + // execution. + Update bool `yaml:"-"` // Repository is the URL of the repository to clone. Required. Repository string `yaml:"repository"` // Directory is the directory at the root of the repository that holds the template data. Required. diff --git a/features/init/local.feature b/features/init/local.feature index 4150a52..2cf234b 100644 --- a/features/init/local.feature +++ b/features/init/local.feature @@ -55,7 +55,6 @@ Feature: Local Template Processing When I run stenciler init with the repository URL in an empty directory Then I see the current directory initialized with the template data - @wip Scenario: Ensuring that a template hook is receiving params as environment variables Given I have a local template with a pre-init hook that uses parameter variables When I run stenciler init with the repository URL in an empty directory diff --git a/features/steps/local.py b/features/steps/local.py index 46209d8..73a8990 100644 --- a/features/steps/local.py +++ b/features/steps/local.py @@ -919,3 +919,56 @@ def step_impl( file = os.path.join(context.expected_dir.name, "Serenity") with open(file, "w", encoding="utf-8") as f: f.write("Malcolm Reynolds\n") + + +@given("I have a local updated template with init-only files") +def step_impl( + context: Context, +): + context.repository_url = "https://github.com/local/repo" + context.template_root_dir = "foo" + root = os.path.join(context.input_dir.name, context.template_root_dir) + leaf_dir1 = os.path.join(root, "bar", "baz") + os.makedirs(leaf_dir1, exist_ok=True) + with open(os.path.join(leaf_dir1, "file.txt"), "w", encoding="utf-8") as f: + f.write("Rogue") + with open(os.path.join(leaf_dir1, "file2.txt"), "w", encoding="utf-8") as f: + f.write("Rogue2") + with open(os.path.join(leaf_dir1, "test.txt"), "w", encoding="utf-8") as f: + f.write("Serenity") + + yaml_data = { + "templates": [ + { + "directory": "foo", + "init-only": [ + "**/file.txt", + "**/file2.txt", + ], + "raw-copy": [ + "**/file2.txt", + ], + }, + ], + } + with open(context.input_config_file, "w", encoding="utf-8") as f: + yaml.dump(yaml_data, f) + + leaf_dir1 = os.path.join(context.expected_dir.name, "bar", "baz") + os.makedirs(leaf_dir1, exist_ok=True) + with open(os.path.join(leaf_dir1, "test.txt"), "w", encoding="utf-8") as f: + f.write("Serenity") + + yaml_data = { + "templates": [ + { + "directory": "foo", + "init-only": [ + "**/file.txt", + "**/file2.txt", + ], + }, + ], + } + with open(context.output_config_file, "w", encoding="utf-8") as f: + yaml.dump(yaml_data, f) diff --git a/features/steps/stenciler.py b/features/steps/stenciler.py index f5a09d0..202edac 100644 --- a/features/steps/stenciler.py +++ b/features/steps/stenciler.py @@ -63,6 +63,8 @@ def step_impl( if init.poll() is not None: break + print("STDOUT: ", init.stdout.read()) + print("STDERR: ", init.stderr.read()) assert init.returncode == 0 @@ -107,4 +109,6 @@ def step_impl( if update.poll() is not None: break + print("STDOUT: ", update.stdout.read()) + print("STDERR: ", update.stderr.read()) assert update.returncode == 0 diff --git a/features/update/local.feature b/features/update/local.feature index 325d2de..52e3d31 100644 --- a/features/update/local.feature +++ b/features/update/local.feature @@ -42,3 +42,9 @@ Feature: Local Template Processing Given I have a local updated template with a new param When I run stenciler update in the current directory Then I see the current directory updated with the template data + + @wip + Scenario: Init only files are not copied during an update + Given I have a local updated template with init-only files + When I run stenciler update in the current directory + Then I see the current directory updated with the template data diff --git a/files/common.go b/files/common.go index a93685d..6345557 100644 --- a/files/common.go +++ b/files/common.go @@ -3,8 +3,12 @@ package files import ( "fmt" "io/fs" + "log/slog" "os" "path/filepath" + "slices" + + "github.com/bmatcuk/doublestar/v4" ) // ensureDirExists ensures that the directory containing the file at relFilePath exists in destRootPath with the same @@ -81,3 +85,30 @@ func isRegularFile(srcRootPath, relFilePath string) bool { } return srcInfo.Mode().IsRegular() } + +// createFileList generates a list of files in the specified root that match the list of glob patterns. +func createFileList(root string, globPatterns []string) ([]string, error) { + var copyList []string + srcRoot := os.DirFS(root) + for _, rawGlob := range globPatterns { + rawFiles, err := doublestar.Glob(srcRoot, rawGlob) + if err != nil { + return nil, fmt.Errorf("failed to glob %s: %w", rawGlob, err) + } + copyList = append(copyList, rawFiles...) + } + + slog.Debug("copy list", slog.Any("files", copyList)) + + return copyList, nil +} + +func removeFromFileList(fileList, removeList []string) []string { + var resultList []string + for _, f := range fileList { + if !slices.Contains(removeList, f) { + resultList = append(resultList, f) + } + } + return resultList +} diff --git a/files/raw.go b/files/raw.go index 92c7c84..85ab79b 100644 --- a/files/raw.go +++ b/files/raw.go @@ -7,8 +7,6 @@ import ( "os" "path/filepath" - "github.com/bmatcuk/doublestar/v4" - "github.com/rogueserenity/stenciler/config" ) @@ -21,36 +19,27 @@ func CopyRaw(repoDir string, template *config.Template) error { return fmt.Errorf("failed to get current working directory: %w", err) } - copyList, err := generateRawCopyList(srcRootPath, template.RawCopyPaths) + copyList, err := createFileList(srcRootPath, template.RawCopyPaths) if err != nil { return fmt.Errorf("failed to generate copy list: %w", err) } - for _, f := range copyList { - _, err = copyRawFile(srcRootPath, destRootPath, f) + if template.Update { + initOnlyList, err := createFileList(srcRootPath, template.InitOnlyPaths) if err != nil { - return fmt.Errorf("failed to copy %s: %w", f, err) + return fmt.Errorf("failed to generate init-only list: %w", err) } + copyList = removeFromFileList(copyList, initOnlyList) } - return nil -} - -// generateRawCopyList generates a list of files to copy from the template directory without template processing. -func generateRawCopyList(srcRootPath string, rawCopyPaths []string) ([]string, error) { - var copyList []string - srcRoot := os.DirFS(srcRootPath) - for _, rawGlob := range rawCopyPaths { - rawFiles, err := doublestar.Glob(srcRoot, rawGlob) + for _, f := range copyList { + _, err = copyRawFile(srcRootPath, destRootPath, f) if err != nil { - return nil, fmt.Errorf("failed to glob %s: %w", rawGlob, err) + return fmt.Errorf("failed to copy %s: %w", f, err) } - copyList = append(copyList, rawFiles...) } - slog.Debug("copy list", slog.Any("files", copyList)) - - return copyList, nil + return nil } // copyRawFile copies the file at relFilePath in srcRootPath to destRootPath. It skips any non-regular files and will diff --git a/files/template.go b/files/template.go index 1596a7c..b83c814 100644 --- a/files/template.go +++ b/files/template.go @@ -26,10 +26,19 @@ func CopyTemplated(repoDir string, tmplate *config.Template) error { params[p.Name] = p.Value } - fileList, err := generateFileList(srcRootPath, tmplate) + fileList, err := createSourceFileList(srcRootPath) if err != nil { return fmt.Errorf("failed to generate file list: %w", err) } + fileList = removeFromFileList(fileList, tmplate.RawCopyPaths) + + if tmplate.Update { + initOnlyList, err := createFileList(srcRootPath, tmplate.InitOnlyPaths) + if err != nil { + return fmt.Errorf("failed to generate init-only list: %w", err) + } + fileList = removeFromFileList(fileList, initOnlyList) + } for _, f := range fileList { err = copyTemplatedFile(srcRootPath, destRootPath, f, params) @@ -41,26 +50,13 @@ func CopyTemplated(repoDir string, tmplate *config.Template) error { return nil } -func generateFileList(srcRootPath string, tmplate *config.Template) ([]string, error) { - var fileList []string - srcRoot := os.DirFS(srcRootPath) +func createSourceFileList(root string) ([]string, error) { + srcRoot := os.DirFS(root) allFiles, err := doublestar.Glob(srcRoot, "**") if err != nil { return nil, fmt.Errorf("failed to glob all files: %w", err) } - for _, file := range allFiles { - isRawCopyFile, err := isRawCopyFile(file, tmplate.RawCopyPaths) - if err != nil { - return nil, fmt.Errorf("failed to check if %s is a raw copy file: %w", file, err) - } - if isRawCopyFile { - continue - } - - fileList = append(fileList, file) - } - - return fileList, nil + return allFiles, nil } func copyTemplatedFile(srcRootPath, destRootPath, relFilePath string, params map[string]string) error { @@ -103,16 +99,3 @@ func copyTemplatedFile(srcRootPath, destRootPath, relFilePath string, params map return nil } - -func isRawCopyFile(filePath string, rawCopyPaths []string) (bool, error) { - for _, rawGlob := range rawCopyPaths { - match, err := doublestar.Match(rawGlob, filePath) - if err != nil { - return false, fmt.Errorf("failed to match %s: %w", rawGlob, err) - } - if match { - return true, nil - } - } - return false, nil -}