Skip to content

Commit d26f9eb

Browse files
committed
feat: Add encoding template directive
1 parent 260f6ef commit d26f9eb

File tree

3 files changed

+66
-1
lines changed

3 files changed

+66
-1
lines changed

assets/chezmoi.io/docs/reference/templates/directives.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,33 @@ inherited by templates called from the file.
3737
# [[ "true" ]]
3838
```
3939

40+
## Encoding
41+
42+
Templates are always written in UTF-8 with no byte order mark.
43+
44+
By default, the result of executing a template is also UTF-8 with no
45+
byte order mark but this can be transformed into another encoding with the
46+
template directive:
47+
48+
chezmoi:template:encoding=$ENCODING
49+
50+
where `$ENCODING` is one of:
51+
52+
| Encoding | Description |
53+
| --------------- | -------------------------------------------- |
54+
| `utf-8` | UTF-8 with no byte order mark |
55+
| `utf-8-bom` | UTF-8 with a byte order mark |
56+
| `utf-16-be` | Big-endian UTF-16 with no byte order mark |
57+
| `utf-16-be-bom` | Big-endian UTF-16 with a byte order mark |
58+
| `utf-16-le` | Little-endian UTF-16 with no byte order mark |
59+
| `utf-16-le-bom` | Little-endian UTF-16 with a byte order mark |
60+
61+
!!! example
62+
63+
```
64+
{{/* chezmoi:template:encoding=utf-16-le */}}
65+
```
66+
4067
## Format indent
4168

4269
By default, chezmoi's `toJson`, `toToml`, and `toYaml` template functions use

internal/chezmoi/template.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package chezmoi
33
import (
44
"bytes"
55
"encoding/json"
6+
"fmt"
67
"maps"
78
"strconv"
89
"strings"
@@ -12,6 +13,8 @@ import (
1213
"github.com/mattn/go-runewidth"
1314
"github.com/mitchellh/copystructure"
1415
"github.com/pelletier/go-toml/v2"
16+
"golang.org/x/text/encoding"
17+
"golang.org/x/text/encoding/unicode"
1518
)
1619

1720
// A Template extends text/template.Template with support for directives.
@@ -23,6 +26,7 @@ type Template struct {
2326

2427
// TemplateOptions are template options that can be set with directives.
2528
type TemplateOptions struct {
29+
Encoding encoding.Encoding
2630
Funcs template.FuncMap
2731
FormatIndent string
2832
LeftDelimiter string
@@ -107,7 +111,12 @@ func (t *Template) Execute(data any) ([]byte, error) {
107111
if err := t.template.ExecuteTemplate(&builder, t.name, data); err != nil {
108112
return nil, err
109113
}
110-
return []byte(replaceLineEndings(builder.String(), t.options.LineEnding)), nil
114+
115+
result := []byte(replaceLineEndings(builder.String(), t.options.LineEnding))
116+
if t.options.Encoding != nil {
117+
return t.options.Encoding.NewEncoder().Bytes(result)
118+
}
119+
return result, nil
111120
}
112121

113122
// parseAndRemoveDirectives updates o by parsing all template directives in data
@@ -126,6 +135,23 @@ func (o *TemplateOptions) parseAndRemoveDirectives(data []byte) ([]byte, error)
126135
key := string(keyValuePairMatch[1])
127136
value := maybeUnquote(string(keyValuePairMatch[2]))
128137
switch key {
138+
case "encoding":
139+
switch value {
140+
case "utf-8":
141+
o.Encoding = unicode.UTF8
142+
case "utf-8-bom":
143+
o.Encoding = unicode.UTF8BOM
144+
case "utf-16-be":
145+
o.Encoding = unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM)
146+
case "utf-16-be-bom":
147+
o.Encoding = unicode.UTF16(unicode.BigEndian, unicode.UseBOM)
148+
case "utf-16-le":
149+
o.Encoding = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
150+
case "utf-16-le-bom":
151+
o.Encoding = unicode.UTF16(unicode.LittleEndian, unicode.UseBOM)
152+
default:
153+
return nil, fmt.Errorf("%s: unknown encoding", value)
154+
}
129155
case "format-indent":
130156
o.FormatIndent = value
131157
case "format-indent-width":

internal/cmd/testdata/scripts/templatedirectives.txtar

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
hexdecode golden/encoding.hex
2+
13
# test --left-delimiter and --right-delimiter flags to chezmoi execute-template
24
exec chezmoi execute-template --left-delimiter=[[ --right-delimiter=]] '[[ "ok" ]]'
35
stdout ^ok$
46

7+
# test that the encoding can be set in files
8+
exec chezmoi cat $HOME${/}encoding
9+
cmp stdout golden/encoding
10+
511
# test that missing key behavior can be set in files
612
exec chezmoi cat $HOME${/}missing-key
713
cmp stdout golden/missing-key
@@ -10,13 +16,19 @@ cmp stdout golden/missing-key
1016
exec chezmoi cat $HOME${/}nested-template
1117
cmp stdout golden/nested-template
1218

19+
-- golden/encoding.hex --
20+
fffe # UTF-16 BOM
21+
480065006c006c006f002c00200077006f0072006c00640021000a00 # "Hello, world!\n"
1322
-- golden/missing-key --
1423
<no value>
1524
-- golden/nested-template --
1625
(nested)
1726
-- home/user/.local/share/chezmoi/.chezmoitemplates/nested --
1827
# chezmoi:template:left-delimiter=(( right-delimiter=))
1928
((- . -))
29+
-- home/user/.local/share/chezmoi/encoding.tmpl --
30+
# chezmoi:template:encoding=utf-16-le-bom
31+
Hello, world!
2032
-- home/user/.local/share/chezmoi/missing-key.tmpl --
2133
# chezmoi:template:missing-key=default
2234
{{ .MissingKey }}

0 commit comments

Comments
 (0)