diff --git a/docs/mdbook/configuration/README.md b/docs/mdbook/configuration/README.md index 36fec6234..4c868cbf3 100644 --- a/docs/mdbook/configuration/README.md +++ b/docs/mdbook/configuration/README.md @@ -19,6 +19,10 @@ Lefthook supports the following file names for the main config: | JSON | `lefthook.json` | | JSON | `.lefthook.json` | | JSON | `.config/lefthook.json` | +| | | +| JSONC | `lefthook.jsonc` | +| JSONC | `.lefthook.jsonc` | +| JSONC | `.config/lefthook.jsonc` | If there are more than 1 file in the project, only one will be used, and you'll never know which one. So, please, use one format in a project. diff --git a/go.mod b/go.mod index ab1a892cc..e2e737430 100644 --- a/go.mod +++ b/go.mod @@ -52,6 +52,7 @@ require ( github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/tidwall/jsonc v0.3.2 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/tools v0.39.0 // indirect diff --git a/go.sum b/go.sum index 0dc07ca29..33caf1d22 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/jsonc v0.3.2 h1:ZTKrmejRlAJYdn0kcaFqRAKlxxFIC21pYq8vLa4p2Wc= +github.com/tidwall/jsonc v0.3.2/go.mod h1:dw+3CIxqHi+t8eFSpzzMlcVYxKp08UP5CD8/uSFCyJE= github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo= github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= diff --git a/internal/command/install.go b/internal/command/install.go index ba8da14f7..72b4acb99 100644 --- a/internal/command/install.go +++ b/internal/command/install.go @@ -99,9 +99,7 @@ func (l *Lefthook) findMainConfig(path string) (string, error) { } for _, name := range config.MainConfigNames { - for _, extension := range []string{ - ".yml", ".yaml", ".toml", ".json", - } { + for _, extension := range config.Extensions { configPath := filepath.Join(path, name+extension) if ok, _ := afero.Exists(l.fs, configPath); ok { return configPath, nil diff --git a/internal/config/jsonc_parser.go b/internal/config/jsonc_parser.go new file mode 100644 index 000000000..7d9f19ae4 --- /dev/null +++ b/internal/config/jsonc_parser.go @@ -0,0 +1,26 @@ +package config + +import ( + "encoding/json" + + "github.com/tidwall/jsonc" +) + +type JSONC struct{} + +func jsoncParser() *JSONC { + return &JSONC{} +} + +func (p *JSONC) Unmarshal(b []byte) (map[string]any, error) { + var out map[string]any + if err := json.Unmarshal(jsonc.ToJSON(b), &out); err != nil { + return nil, err + } + return out, nil +} + +// Marshal marshals the given config map to JSON bytes. +func (p *JSONC) Marshal(o map[string]any) ([]byte, error) { + return json.Marshal(o) +} diff --git a/internal/config/load.go b/internal/config/load.go index 5209d7d9c..fb0894ec6 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -32,17 +32,19 @@ var ( hookKeyRegexp = regexp.MustCompile(`^(?P[^.]+)\.(?:scripts|commands|jobs)`) LocalConfigNames = []string{"lefthook-local", ".lefthook-local", filepath.Join(".config", "lefthook-local")} MainConfigNames = []string{"lefthook", ".lefthook", filepath.Join(".config", "lefthook")} - extensions = []string{ + Extensions = []string{ ".yml", ".yaml", ".json", + ".jsonc", ".toml", } parsers = map[string]koanf.Parser{ - ".yml": yaml.Parser(), - ".yaml": yaml.Parser(), - ".json": json.Parser(), - ".toml": toml.Parser(), + ".yml": yaml.Parser(), + ".yaml": yaml.Parser(), + ".json": json.Parser(), + ".jsonc": jsoncParser(), + ".toml": toml.Parser(), } mergeJobsOption = koanf.WithMergeFunc(mergeJobs) @@ -70,7 +72,7 @@ func loadConfig(k *koanf.Koanf, filesystem afero.Fs, path string) error { // loadFirst loads the first existing config from given names and supported extensions. func loadFirst(k *koanf.Koanf, filesystem afero.Fs, root string, names []string) error { - for _, extension := range extensions { + for _, extension := range Extensions { for _, name := range names { config := filepath.Join(root, name+extension) if ok, _ := afero.Exists(filesystem, config); !ok { @@ -90,7 +92,7 @@ func loadFirstMain(k *koanf.Koanf, filesystem afero.Fs, root string) error { if ok := errors.As(err, &ConfigNotFoundError{}); ok { var hasLocalConfig bool OUT: - for _, extension := range extensions { + for _, extension := range Extensions { for _, name := range LocalConfigNames { if ok, _ := afero.Exists(filesystem, filepath.Join(root, name+extension)); ok { hasLocalConfig = true diff --git a/tests/integration/run_jsonc.txt b/tests/integration/run_jsonc.txt new file mode 100644 index 000000000..9322c5a17 --- /dev/null +++ b/tests/integration/run_jsonc.txt @@ -0,0 +1,22 @@ +exec git init +exec git config user.email "you@example.com" +exec git config user.name "Your Name" +exec git add -A +exec lefthook install +exec git commit -m 'test' +stderr '\s*Hi there from Lefthook\s*' + +-- lefthook.jsonc -- +{ + /* Prints only what's being executed */ + "output": [ + "execution" + ], + "pre-commit": { + "commands": { + "echo": { + "run": "echo Hi there from Lefthook" // echoes Hi there from Lefthook + } + } + } +}