Skip to content

Commit

Permalink
fix: exclude init-only files when updating (#36)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rogueserenity committed Jul 6, 2024
1 parent ca12f43 commit d5593ad
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 51 deletions.
2 changes: 2 additions & 0 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ func doUpdate() {
cobra.CheckErr(err)
}

mergedTemplate.Update = true

updateWrite(mergedTemplate)
}

Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 0 additions & 1 deletion features/init/local.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions features/steps/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 4 additions & 0 deletions features/steps/stenciler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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
6 changes: 6 additions & 0 deletions features/update/local.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
31 changes: 31 additions & 0 deletions files/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
29 changes: 9 additions & 20 deletions files/raw.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"os"
"path/filepath"

"github.com/bmatcuk/doublestar/v4"

"github.com/rogueserenity/stenciler/config"
)

Expand All @@ -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
Expand Down
43 changes: 13 additions & 30 deletions files/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}

0 comments on commit d5593ad

Please sign in to comment.