Skip to content

Commit

Permalink
feat: Add support for the JSONC format
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Mar 6, 2023
1 parent eaf4c66 commit 0a6e190
Show file tree
Hide file tree
Showing 9 changed files with 62 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if the environment variable `$NO_COLOR` is not set and stdout is a terminal.

Read the configuration from *filename*.

## `--config-format` `json`|`toml`|`yaml`
## `--config-format` `json`|`jsonc`|`toml`|`yaml`

Assume the configuration file is in the given format. This is only needed if
the config filename does not have an extension, for example when it is
Expand Down
4 changes: 2 additions & 2 deletions assets/chezmoi.io/docs/reference/commands/init.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ First, if the source directory does not already contain a repository, then if
repository is initialized in the source directory.

Second, if a file called `.chezmoi.$FORMAT.tmpl` exists, where `$FORMAT` is one
of the supported file formats (e.g. `json`, `toml`, or `yaml`) then a new
configuration file is created using that file as a template.
of the supported file formats (e.g. `json`, `jsonc`, `toml`, or `yaml`) then a
new configuration file is created using that file as a template.

Then, if the `--apply` flag is passed, `chezmoi apply` is run.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
chezmoi searches for its configuration file according to the [XDG Base
Directory
Specification](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html)
and supports [JSON](https://www.json.org/json-en.html),
and supports [JSON](https://www.json.org/json-en.html), JSONC,
[TOML](https://github.com/toml-lang/toml), and [YAML](https://yaml.org/). The
basename of the config file is `chezmoi`, and the first config file found is
used.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ machine to machine. For example, for your home machine:
If you intend to store private data (e.g. access tokens) in
`~/.config/chezmoi/chezmoi.toml`, make sure it has permissions `0600`.

If you prefer, you can use JSON or YAML for your configuration file. Variable
names must start with a letter and be followed by zero or more letters or
digits.
If you prefer, you can use JSON, JSONC, or YAML for your configuration file.
Variable names must start with a letter and be followed by zero or more letters
or digits.

Then, add `~/.gitconfig` to chezmoi using the `--autotemplate` flag to turn it
into a template and automatically detect variables from the `data` section of
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.2
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
github.com/twpayne/go-pinentry v0.2.0
github.com/twpayne/go-vfs/v4 v4.2.0
github.com/twpayne/go-xdg/v6 v6.1.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/twpayne/go-pinentry v0.2.0 h1:hS5NEJiilop9xP9pBX/1NYduzDlGGMdg1KamTBTrOWw=
github.com/twpayne/go-pinentry v0.2.0/go.mod h1:r6buhMwARxnnL0VRBqfd1tE6Fadk1kfP00GRMutEspY=
github.com/twpayne/go-vfs/v4 v4.2.0 h1:cIjUwaKSCq0y6dT+ev6uLSmKjGTbHCR4xaocROqHFsE=
Expand Down
51 changes: 40 additions & 11 deletions pkg/chezmoi/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import (
"strings"

"github.com/pelletier/go-toml/v2"
"github.com/tailscale/hujson"
"gopkg.in/yaml.v3"
)

// Formats.
var (
FormatJSON Format = formatJSON{}
FormatTOML Format = formatTOML{}
FormatYAML Format = formatYAML{}
FormatJSON Format = formatJSON{}
FormatJSONC Format = formatJSONC{}
FormatTOML Format = formatTOML{}
FormatYAML Format = formatYAML{}
)

// A Format is a serialization format.
Expand All @@ -26,6 +28,9 @@ type Format interface {
// A formatJSON implements the JSON serialization format.
type formatJSON struct{}

// A formatJSONC implements the JSONC serialization format.
type formatJSONC struct{}

// A formatTOML implements the TOML serialization format.
type formatTOML struct{}

Expand All @@ -35,22 +40,46 @@ type formatYAML struct{}
var (
// FormatsByName is a map of all FormatsByName by name.
FormatsByName = map[string]Format{
"json": FormatJSON,
"toml": FormatTOML,
"yaml": FormatYAML,
"jsonc": FormatJSONC,
"json": FormatJSON,
"toml": FormatTOML,
"yaml": FormatYAML,
}

// FormatsByExtension is a map of all Formats by extension.
FormatsByExtension = map[string]Format{
"json": FormatJSON,
"toml": FormatTOML,
"yaml": FormatYAML,
"yml": FormatYAML,
"jsonc": FormatJSONC,
"json": FormatJSON,
"toml": FormatTOML,
"yaml": FormatYAML,
"yml": FormatYAML,
}

FormatExtensions = sortedKeys(FormatsByExtension)
)

// Marshal implements Format.Marshal.
func (formatJSONC) Marshal(value any) ([]byte, error) {
data, err := json.Marshal(value)
if err != nil {
return nil, err
}
return hujson.Format(data)
}

// Name implements Format.Name.
func (formatJSONC) Name() string {
return "jsonc"
}

// Unmarshal implements Format.Unmarshal.
func (formatJSONC) Unmarshal(data []byte, value any) error {
data, err := hujson.Standardize(data)
if err != nil {
return err
}
return json.Unmarshal(data, value)
}

// Marshal implements Format.Marshal.
func (formatJSON) Marshal(value any) ([]byte, error) {
data, err := json.MarshalIndent(value, "", " ")
Expand Down
2 changes: 2 additions & 0 deletions pkg/chezmoi/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

func TestFormats(t *testing.T) {
assert.Contains(t, FormatsByName, "json")
assert.Contains(t, FormatsByName, "jsonc")
assert.Contains(t, FormatsByName, "toml")
assert.Contains(t, FormatsByName, "yaml")
assert.NotContains(t, FormatsByName, "yml")
Expand All @@ -24,6 +25,7 @@ func TestFormatRoundTrip(t *testing.T) {
}

for _, format := range []Format{
formatJSONC{},
formatJSON{},
formatTOML{},
formatYAML{},
Expand Down
11 changes: 10 additions & 1 deletion pkg/cmd/testdata/scripts/config.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ stdout /flag/source
exec chezmoi data --format=yaml
stdout 'sourceDir: .*/config/source'

# test that the config file can be set
# test that the config file can be set and can be in YAML format
exec chezmoi data --config=$HOME/.chezmoi.yaml --format=yaml
stdout 'sourceDir: .*/config2/source'

# test that the config file can be in JSONC format
exec chezmoi data --config=$HOME/.chezmoi.jsonc --format=yaml
stdout 'sourceDir: .*/config3/source'

# test that the cache directory can be set
exec chezmoi data --cache=/flag/cache --format=yaml
stdout 'cacheDir: .*/flag/cache'
Expand All @@ -31,6 +35,11 @@ stdin home2/user/.chezmoi.yaml
exec chezmoi data --config=/dev/stdin --config-format=yaml --format=yaml
stdout 'sourceDir: .*/config2/source'

-- home2/user/.chezmoi.jsonc --
{
"color": "auto", // Color
"sourceDir": "/config3/source", // Source directory
}
-- home2/user/.chezmoi.yaml --
color: auto
sourceDir: /config2/source
Expand Down

0 comments on commit 0a6e190

Please sign in to comment.