From 9e77ea56e0df88e8b53ea2bca86276b331bd77c1 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Fri, 3 Sep 2021 22:30:20 +0200 Subject: [PATCH] Add remove_ attribute --- docs/REFERENCE.md | 9 +++++++++ internal/chezmoi/attr.go | 6 ++++++ internal/chezmoi/attr_test.go | 7 +++++++ internal/chezmoi/chezmoi.go | 3 ++- internal/chezmoi/sourcestate.go | 10 ++++++++++ internal/cmd/testdata/scripts/remove.txt | 15 +++++++++++++++ 6 files changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 496afae12cc..e3af2e3af84 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -424,6 +424,7 @@ to as "attributes": | `modify_` | Treat the contents as a script that modifies an existing file. | | `once_` | Run script once. | | `private_` | Remove all group and world permissions from the target file or directory. | +| `remove_` | Remove the entry if it exists. | | `run_` | Treat the contents as a script to run. | | `symlink_` | Create a symlink instead of a regular file. | @@ -441,6 +442,7 @@ prefixes is important. | Regular file | File | `encrypted_`, `private_`, `executable_`, `dot_` | `.tmpl` | | Create file | File | `create_`, `encrypted_`, `private_`, `executable_`, `dot_` | `.tmpl` | | Modify file | File | `modify_`, `encrypted_`, `private_`, `executable_`, `dot_` | `.tmpl` | +| Remove | File | `remove_`, `dot_` | *none* | | Script | File | `run_`, `once_`, `before_` or `after_` | `.tmpl` | | Symbolic link | File | `symlink_`, `dot_`, | `.tmpl` | @@ -495,6 +497,13 @@ new contents are read from the scripts standard output. --- +#### Remove entry + +Files with the `remove_` prefix will cause the corresponding entry (file, +directory, or symlink) to be removed in the target state. + +--- + ### Directories Directories are represented by regular directories in the source state. The diff --git a/internal/chezmoi/attr.go b/internal/chezmoi/attr.go index 4f35a562959..ef72942db84 100644 --- a/internal/chezmoi/attr.go +++ b/internal/chezmoi/attr.go @@ -15,6 +15,7 @@ const ( SourceFileTypeCreate SourceFileTargetType = iota SourceFileTypeFile SourceFileTypeModify + SourceFileTypeRemove SourceFileTypeScript SourceFileTypeSymlink ) @@ -125,6 +126,9 @@ func parseFileAttr(sourceName, encryptedSuffix string) FileAttr { name = mustTrimPrefix(name, executablePrefix) executable = true } + case strings.HasPrefix(name, removePrefix): + sourceFileType = SourceFileTypeRemove + name = mustTrimPrefix(name, removePrefix) case strings.HasPrefix(name, runPrefix): sourceFileType = SourceFileTypeScript name = mustTrimPrefix(name, runPrefix) @@ -240,6 +244,8 @@ func (fa FileAttr) SourceName(encryptedSuffix string) string { if fa.Executable { sourceName += executablePrefix } + case SourceFileTypeRemove: + sourceName = removePrefix case SourceFileTypeScript: sourceName = runPrefix if fa.Once { diff --git a/internal/chezmoi/attr_test.go b/internal/chezmoi/attr_test.go index c9eba98af1c..3b3b3ecf44f 100644 --- a/internal/chezmoi/attr_test.go +++ b/internal/chezmoi/attr_test.go @@ -133,6 +133,13 @@ func TestFileAttr(t *testing.T) { Private: []bool{false, true}, Template: []bool{false, true}, })) + require.NoError(t, combinator.Generate(&fas, struct { + Type SourceFileTargetType + TargetName []string + }{ + Type: SourceFileTypeRemove, + TargetName: targetNames, + })) require.NoError(t, combinator.Generate(&fas, struct { Type SourceFileTargetType TargetName []string diff --git a/internal/chezmoi/chezmoi.go b/internal/chezmoi/chezmoi.go index d05bca05345..8500fc184d2 100644 --- a/internal/chezmoi/chezmoi.go +++ b/internal/chezmoi/chezmoi.go @@ -37,6 +37,7 @@ const ( modifyPrefix = "modify_" oncePrefix = "once_" privatePrefix = "private_" + removePrefix = "remove_" runPrefix = "run_" symlinkPrefix = "symlink_" literalSuffix = ".literal" @@ -57,7 +58,7 @@ const ( var ( dirPrefixRegexp = regexp.MustCompile(`\A(dot|exact|literal|private)_`) - filePrefixRegexp = regexp.MustCompile(`\A(after|before|create|dot|empty|encrypted|executable|literal|modify|once|private|run|symlink)_`) + filePrefixRegexp = regexp.MustCompile(`\A(after|before|create|dot|empty|encrypted|executable|literal|modify|once|private|remove|run|symlink)_`) fileSuffixRegexp = regexp.MustCompile(`\.(literal|tmpl)\z`) ) diff --git a/internal/chezmoi/sourcestate.go b/internal/chezmoi/sourcestate.go index 3f4c89f4365..c45c9f68395 100644 --- a/internal/chezmoi/sourcestate.go +++ b/internal/chezmoi/sourcestate.go @@ -1230,6 +1230,14 @@ func (s *SourceState) newModifyTargetStateEntryFunc(sourceRelPath SourceRelPath, } } +// newRemoveTargetStateEntryFunc returns a targetStateEntryFunc that removes a +// target. +func (s *SourceState) newRemoveTargetStateEntryFunc(sourceRelPath SourceRelPath, fileAttr FileAttr) targetStateEntryFunc { + return func(destSystem System, destAbsPath AbsPath) (TargetStateEntry, error) { + return &TargetStateRemove{}, nil + } +} + // newScriptTargetStateEntryFunc returns a targetStateEntryFunc that returns a // script with sourceLazyContents. func (s *SourceState) newScriptTargetStateEntryFunc(sourceRelPath SourceRelPath, fileAttr FileAttr, targetRelPath RelPath, sourceLazyContents *lazyContents, interpreter *Interpreter) targetStateEntryFunc { @@ -1314,6 +1322,8 @@ func (s *SourceState) newSourceStateFile(sourceRelPath SourceRelPath, fileAttr F targetRelPath = targetRelPath[:len(targetRelPath)-len(ext)-1] } targetStateEntryFunc = s.newModifyTargetStateEntryFunc(sourceRelPath, fileAttr, sourceLazyContents, interpreter) + case SourceFileTypeRemove: + targetStateEntryFunc = s.newRemoveTargetStateEntryFunc(sourceRelPath, fileAttr) case SourceFileTypeScript: // If the script has an extension, determine if it indicates an // interpreter to use. diff --git a/internal/cmd/testdata/scripts/remove.txt b/internal/cmd/testdata/scripts/remove.txt index 4bd77253ff2..8ffc97bbe91 100644 --- a/internal/cmd/testdata/scripts/remove.txt +++ b/internal/cmd/testdata/scripts/remove.txt @@ -19,3 +19,18 @@ exists $HOME/.executable ! chezmoi remove --force $HOME${/}.newfile $HOME${/}.executable stderr 'not in source state' exists $HOME/.executable + +chhome home2/user + +# test that chezmoi apply removes a file and a directory +exists $HOME/.file +exists $HOME/.dir +chezmoi apply +! exists $HOME/.file +! exists $HOME/.dir + +-- home2/user/.dir/.keep -- +-- home2/user/.file -- +# contents of .file +-- home2/user/.local/share/chezmoi/remove_dot_file -- +-- home2/user/.local/share/chezmoi/remove_dot_dir --