Skip to content

Commit

Permalink
Add .literal suffix to make all filenames representable
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Jul 2, 2021
1 parent 646f4f3 commit d506456
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 22 deletions.
15 changes: 3 additions & 12 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions docs/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
11 changes: 10 additions & 1 deletion internal/chezmoi/attr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
Expand Down
46 changes: 45 additions & 1 deletion internal/chezmoi/attr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -184,6 +187,7 @@ func TestFileAttrLiteral(t *testing.T) {
sourceName string
encryptedSuffix string
fileAttr FileAttr
nonCanonical bool
}{
{
sourceName: "dot_file",
Expand Down Expand Up @@ -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))
}
})
}
}
2 changes: 2 additions & 0 deletions internal/chezmoi/chezmoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const (
privatePrefix = "private_"
runPrefix = "run_"
symlinkPrefix = "symlink_"
literalSuffix = ".literal"
TemplateSuffix = ".tmpl"
)

Expand All @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions internal/cmd/testdata/scripts/literal.txt
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 --
Expand All @@ -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

0 comments on commit d506456

Please sign in to comment.