From d5064563691ca21099a9002f3bf0e64e7c6bfa72 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Fri, 2 Jul 2021 21:42:41 +0200 Subject: [PATCH] Add .literal suffix to make all filenames representable --- docs/FAQ.md | 15 ++------ docs/REFERENCE.md | 13 ++++--- internal/chezmoi/attr.go | 11 +++++- internal/chezmoi/attr_test.go | 46 ++++++++++++++++++++++- internal/chezmoi/chezmoi.go | 2 + internal/cmd/testdata/scripts/literal.txt | 10 ++++- 6 files changed, 75 insertions(+), 22 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index c1d796e95ab..3d8c2301c3f 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -197,21 +197,12 @@ better than just having the correct dotfile contents. There are a number of criticisms of how chezmoi's source state is represented on disk: -1. The source file naming system cannot handle all possible filenames. -2. Not all possible file permissions can be represented. -3. The long source file names are weird and verbose. -4. Everything is in a single directory, which can end up containing many entries. +1. Not all possible file permissions can be represented. +2. The long source file names are weird and verbose. +3. Everything is in a single directory, which can end up containing many entries. chezmoi's source state representation is a deliberate, practical compromise. -Certain target filenames, for example `~/dot_example`, are incompatible with -chezmoi's -[attributes](https://github.com/twpayne/chezmoi/blob/master/docs/REFERENCE.md#source-state-attributes) -used in the source state. In practice, dotfile filenames are unlikely to -conflict with chezmoi's attributes. If this does cause a genuine problem for -you, please [open an issue on -GitHub](https://github.com/twpayne/chezmoi/issues/new/choose). - The `dot_` attribute makes it transparent which dotfiles are managed by chezmoi and which files are ignored by chezmoi. chezmoi ignores all files and directories that start with `.` so no special whitelists are needed for version diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index 37cca29fb49..a07cb9f18c7 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -394,9 +394,10 @@ to as "attributes": | `run_` | Treat the contents as a script to run. | | `symlink_` | Create a symlink instead of a regular file. | -| Suffix | Effect | -| ------- | ---------------------------------------------------- | -| `.tmpl` | Treat the contents of the source file as a template. | +| Suffix | Effect | +| ---------- | ---------------------------------------------------- | +| `.literal` | Stop parsing suffix attributes. | +| `.tmpl` | Treat the contents of the source file as a template. | Different target types allow different prefixes and suffixes. The order of prefixes is important. @@ -410,9 +411,9 @@ prefixes is important. | Script | File | `run_`, `once_`, `before_` or `after_` | `.tmpl` | | Symbolic link | File | `symlink_`, `dot_`, | `.tmpl` | -The `literal_` prefix can appear anywhere and stop prefix attribute parsing. -This permits filenames that would otherwise conflict with chezmoi's attributes -to be represented. +The `literal_` prefix and `.literal` suffix can appear anywhere and stop +attribute parsing. This permits filenames that would otherwise conflict with +chezmoi's attributes to be represented. In addition, if the source file is encrypted, the suffix `.age` (when age encryption is used) or `.asc` (when gpg encryption is used) is stripped. These diff --git a/internal/chezmoi/attr.go b/internal/chezmoi/attr.go index 98b16c046cf..4f35a562959 100644 --- a/internal/chezmoi/attr.go +++ b/internal/chezmoi/attr.go @@ -181,9 +181,15 @@ func parseFileAttr(sourceName, encryptedSuffix string) FileAttr { if encrypted { name = strings.TrimSuffix(name, encryptedSuffix) } - if strings.HasSuffix(name, TemplateSuffix) { + switch { + case strings.HasSuffix(name, literalSuffix): + name = mustTrimSuffix(name, literalSuffix) + case strings.HasSuffix(name, TemplateSuffix): name = mustTrimSuffix(name, TemplateSuffix) template = true + if strings.HasSuffix(name, literalSuffix) { + name = mustTrimSuffix(name, literalSuffix) + } } return FileAttr{ TargetName: name, @@ -256,6 +262,9 @@ func (fa FileAttr) SourceName(encryptedSuffix string) string { default: sourceName += fa.TargetName } + if fileSuffixRegexp.MatchString(fa.TargetName) { + sourceName += literalSuffix + } if fa.Template { sourceName += TemplateSuffix } diff --git a/internal/chezmoi/attr_test.go b/internal/chezmoi/attr_test.go index 7cb5e2c384a..c9eba98af1c 100644 --- a/internal/chezmoi/attr_test.go +++ b/internal/chezmoi/attr_test.go @@ -80,10 +80,13 @@ func TestFileAttr(t *testing.T) { "dot_name", "exact_name", "literal_name", + "literal_name", "modify_name", + "name.literal", "name", "run_name", "symlink_name", + "template.tmpl", } require.NoError(t, combinator.Generate(&fas, struct { Type SourceFileTargetType @@ -184,6 +187,7 @@ func TestFileAttrLiteral(t *testing.T) { sourceName string encryptedSuffix string fileAttr FileAttr + nonCanonical bool }{ { sourceName: "dot_file", @@ -221,10 +225,50 @@ func TestFileAttrLiteral(t *testing.T) { Type: SourceFileTypeScript, }, }, + { + sourceName: "file.literal", + fileAttr: FileAttr{ + TargetName: "file", + Type: SourceFileTypeFile, + }, + nonCanonical: true, + }, + { + sourceName: "file.literal.literal", + fileAttr: FileAttr{ + TargetName: "file.literal", + Type: SourceFileTypeFile, + }, + }, + { + sourceName: "file.tmpl", + fileAttr: FileAttr{ + TargetName: "file", + Type: SourceFileTypeFile, + Template: true, + }, + }, + { + sourceName: "file.tmpl.literal", + fileAttr: FileAttr{ + TargetName: "file.tmpl", + Type: SourceFileTypeFile, + }, + }, + { + sourceName: "file.tmpl.literal.tmpl", + fileAttr: FileAttr{ + TargetName: "file.tmpl", + Type: SourceFileTypeFile, + Template: true, + }, + }, } { t.Run(tc.sourceName, func(t *testing.T) { assert.Equal(t, tc.fileAttr, parseFileAttr(tc.sourceName, tc.encryptedSuffix)) - assert.Equal(t, tc.sourceName, tc.fileAttr.SourceName(tc.encryptedSuffix)) + if !tc.nonCanonical { + assert.Equal(t, tc.sourceName, tc.fileAttr.SourceName(tc.encryptedSuffix)) + } }) } } diff --git a/internal/chezmoi/chezmoi.go b/internal/chezmoi/chezmoi.go index 970fd6f8a7f..aaa7ccefa5b 100644 --- a/internal/chezmoi/chezmoi.go +++ b/internal/chezmoi/chezmoi.go @@ -39,6 +39,7 @@ const ( privatePrefix = "private_" runPrefix = "run_" symlinkPrefix = "symlink_" + literalSuffix = ".literal" TemplateSuffix = ".tmpl" ) @@ -56,6 +57,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)_`) + fileSuffixRegexp = regexp.MustCompile(`\.(literal|tmpl)\z`) ) // knownPrefixedFiles is a set of known filenames with the .chezmoi prefix. diff --git a/internal/cmd/testdata/scripts/literal.txt b/internal/cmd/testdata/scripts/literal.txt index c414912bd99..9f3e8e76b24 100644 --- a/internal/cmd/testdata/scripts/literal.txt +++ b/internal/cmd/testdata/scripts/literal.txt @@ -1,13 +1,15 @@ # test that chezmoi add can add files that look like files in the source state -chezmoi add $HOME${/}dot_file $HOME${/}run_script $HOME${/}symlink_symlink -rm $HOME/dot_file $HOME/run_script $HOME/symlink_symlink +chezmoi add $HOME${/}dot_file $HOME${/}run_script $HOME${/}symlink_symlink $HOME${/}template.tmpl +rm $HOME/dot_file $HOME/run_script $HOME/symlink_symlink $HOME/template.tmpl chezmoi apply --force ! exists $HOME/.file ! stdout . ! exists $HOME/symlink +! exists $HOME/template cmp $HOME/dot_file golden/dot_file cmp $HOME/run_script golden/run_script cmp $HOME/symlink_symlink golden/symlink_symlink +cmp $HOME/template.tmpl golden/template.tmpl -- home/user/dot_file -- # contents of dot_file @@ -17,6 +19,8 @@ cmp $HOME/symlink_symlink golden/symlink_symlink echo contents of run_script -- home/user/symlink_symlink -- # contents of symlink_symlink +-- home/user/template.tmpl -- +# contents of template.tmpl -- golden/dot_file -- # contents of dot_file -- golden/run_script -- @@ -25,3 +29,5 @@ echo contents of run_script echo contents of run_script -- golden/symlink_symlink -- # contents of symlink_symlink +-- golden/template.tmpl -- +# contents of template.tmpl