Skip to content

Commit

Permalink
Add remove_ attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Sep 4, 2021
1 parent 7b1988e commit 9e77ea5
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 1 deletion.
9 changes: 9 additions & 0 deletions docs/REFERENCE.md
Expand Up @@ -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. |

Expand All @@ -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` |

Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions internal/chezmoi/attr.go
Expand Up @@ -15,6 +15,7 @@ const (
SourceFileTypeCreate SourceFileTargetType = iota
SourceFileTypeFile
SourceFileTypeModify
SourceFileTypeRemove
SourceFileTypeScript
SourceFileTypeSymlink
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions internal/chezmoi/attr_test.go
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion internal/chezmoi/chezmoi.go
Expand Up @@ -37,6 +37,7 @@ const (
modifyPrefix = "modify_"
oncePrefix = "once_"
privatePrefix = "private_"
removePrefix = "remove_"
runPrefix = "run_"
symlinkPrefix = "symlink_"
literalSuffix = ".literal"
Expand All @@ -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`)
)

Expand Down
10 changes: 10 additions & 0 deletions internal/chezmoi/sourcestate.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions internal/cmd/testdata/scripts/remove.txt
Expand Up @@ -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 --

0 comments on commit 9e77ea5

Please sign in to comment.