From 54d8a3a82c5a464b37e689d1e75e251835ad97ee Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 2 Sep 2024 12:57:00 +0300 Subject: [PATCH] chore: replace viper with koanf --- go.mod | 24 ++--- go.sum | 49 ++++----- internal/config/command.go | 28 +++-- internal/config/hook.go | 8 +- internal/config/load.go | 216 ++++++++++++++++++++++--------------- internal/config/script.go | 19 ++-- internal/lefthook/run.go | 4 +- 7 files changed, 188 insertions(+), 160 deletions(-) diff --git a/go.mod b/go.mod index 50ed0ed2..da6b8d4b 100644 --- a/go.mod +++ b/go.mod @@ -11,13 +11,17 @@ require ( github.com/creack/pty v1.1.23 github.com/gobwas/glob v0.2.3 github.com/google/go-cmp v0.6.0 + github.com/knadh/koanf/parsers/json v0.1.0 + github.com/knadh/koanf/parsers/toml/v2 v2.1.0 + github.com/knadh/koanf/parsers/yaml v0.1.0 + github.com/knadh/koanf/providers/fs v0.1.0 + github.com/knadh/koanf/v2 v2.1.1 github.com/mattn/go-tty v0.0.7 github.com/mitchellh/mapstructure v1.5.0 github.com/rogpeppe/go-internal v1.12.0 github.com/schollz/progressbar/v3 v3.14.6 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.1 - github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 ) @@ -26,37 +30,31 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/x/ansi v0.1.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + 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/sagikazarmark/locafero v0.4.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/tools v0.13.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/fatih/color v1.14.1 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-runewidth v0.0.16 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 github.com/rivo/uniseg v0.4.7 // indirect - github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/subosito/gotenv v1.6.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.14.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index f460af03..cf233ae3 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,7 @@ github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDU github.com/charmbracelet/x/ansi v0.1.4 h1:IEU3D6+dWwPSgZ6HBH+v6oUuZ/nVawMiWj5831KfiLM= github.com/charmbracelet/x/ansi v0.1.4/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.23 h1:4M6+isWdcStXEf15G/RbrMPOQj1dZ7HPZCGwE4kOeP0= github.com/creack/pty v1.1.23/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -19,27 +20,33 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w= +github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU= +github.com/knadh/koanf/parsers/json v0.1.0/go.mod h1:ll2/MlXcZ2BfXD6YJcjVFzhG9P0TdJ207aIBKQhV2hY= +github.com/knadh/koanf/parsers/toml/v2 v2.1.0 h1:EUdIKIeezfDj6e1ABDhIjhbURUpyrP1HToqW6tz8R0I= +github.com/knadh/koanf/parsers/toml/v2 v2.1.0/go.mod h1:0KtwfsWJt4igUTQnsn0ZjFWVrP80Jv7edTBRbQFd2ho= +github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= +github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= +github.com/knadh/koanf/providers/fs v0.1.0 h1:9Hln9GS3bWTItAnGVFYyfkoAIxAFq7pvlF64pTNiDdQ= +github.com/knadh/koanf/providers/fs v0.1.0/go.mod h1:Cva1yH8NBxkEeVZx8CUmF5TunbgO72E+GwqDbqpP2sE= +github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= +github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -51,51 +58,39 @@ github.com/mattn/go-tty v0.0.7 h1:KJ486B6qI8+wBO7kQxYgmmEFDaFEE96JMBQ7h400N8Q= github.com/mattn/go-tty v0.0.7/go.mod h1:f2i5ZOvXBU/tCABmLmOfzLz9azMo5wdAaElRNnJKr+k= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/schollz/progressbar/v3 v3.14.6 h1:GyjwcWBAf+GFDMLziwerKvpuS7ZF+mNTAXIB2aspiZs= github.com/schollz/progressbar/v3 v3.14.6/go.mod h1:Nrzpuw3Nl0srLY0VlTvC4V6RL50pcEymjy6qyJAaLa0= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= @@ -111,7 +106,5 @@ gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfM gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/command.go b/internal/config/command.go index ad2db62e..f31488eb 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -4,7 +4,7 @@ import ( "errors" "strings" - "github.com/spf13/viper" + "github.com/knadh/koanf/v2" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" @@ -55,21 +55,21 @@ func (c Command) ExecutionPriority() int { return c.Priority } -func mergeCommands(base, extra *viper.Viper) (map[string]*Command, error) { +func mergeCommands(base, extra *koanf.Koanf) (map[string]*Command, error) { if base == nil && extra == nil { return nil, nil } if base == nil { - return unmarshalCommands(extra.Sub("commands")) + return unmarshalCommands(extra.Cut("commands")) } if extra == nil { - return unmarshalCommands(base.Sub("commands")) + return unmarshalCommands(base.Cut("commands")) } - commandsOrigin := base.Sub("commands") - commandsOverride := extra.Sub("commands") + commandsOrigin := base.Cut("commands") + commandsOverride := extra.Cut("commands") if commandsOrigin == nil { return unmarshalCommands(commandsOverride) } @@ -78,22 +78,22 @@ func mergeCommands(base, extra *viper.Viper) (map[string]*Command, error) { } runReplaces := make(map[string]*commandRunReplace) - for key := range commandsOrigin.AllSettings() { + for key := range commandsOrigin.Raw() { var replace commandRunReplace - substructure := commandsOrigin.Sub(key) + substructure := commandsOrigin.Cut(key) if substructure == nil { continue } - if err := substructure.Unmarshal(&replace); err != nil { + if err := substructure.Unmarshal("", &replace); err != nil { return nil, err } runReplaces[key] = &replace } - err := commandsOrigin.MergeConfigMap(commandsOverride.AllSettings()) + err := commandsOrigin.Merge(commandsOverride) if err != nil { return nil, err } @@ -112,13 +112,9 @@ func mergeCommands(base, extra *viper.Viper) (map[string]*Command, error) { return commands, nil } -func unmarshalCommands(v *viper.Viper) (map[string]*Command, error) { - if v == nil { - return nil, nil - } - +func unmarshalCommands(k *koanf.Koanf) (map[string]*Command, error) { commands := make(map[string]*Command) - if err := v.Unmarshal(&commands); err != nil { + if err := k.Unmarshal("", &commands); err != nil { return nil, err } diff --git a/internal/config/hook.go b/internal/config/hook.go index f22290bc..6783a825 100644 --- a/internal/config/hook.go +++ b/internal/config/hook.go @@ -5,7 +5,7 @@ import ( "os" "strings" - "github.com/spf13/viper" + "github.com/knadh/koanf/v2" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" @@ -48,7 +48,7 @@ func (h *Hook) DoSkip(gitState git.State) bool { return skipChecker.check(gitState, h.Skip, h.Only) } -func unmarshalHooks(base, extra *viper.Viper) (*Hook, error) { +func unmarshalHooks(base, extra *koanf.Koanf) (*Hook, error) { if base == nil && extra == nil { return nil, nil } @@ -71,12 +71,12 @@ func unmarshalHooks(base, extra *viper.Viper) (*Hook, error) { if base == nil { base = extra } else if extra != nil { - if err = base.MergeConfigMap(extra.AllSettings()); err != nil { + if err = base.Merge(extra); err != nil { return nil, err } } - if err := base.Unmarshal(&hook); err != nil { + if err := base.Unmarshal("", &hook); err != nil { return nil, err } diff --git a/internal/config/load.go b/internal/config/load.go index aa03aa43..cf2431a7 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -3,13 +3,18 @@ package config import ( "errors" "fmt" + iofs "io/fs" + "os" "path/filepath" "regexp" "slices" - "strings" + "github.com/knadh/koanf/parsers/json" + "github.com/knadh/koanf/parsers/toml/v2" + "github.com/knadh/koanf/parsers/yaml" + koanffs "github.com/knadh/koanf/providers/fs" + "github.com/knadh/koanf/v2" "github.com/spf13/afero" - "github.com/spf13/viper" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/log" @@ -24,21 +29,48 @@ const ( var ( hookKeyRegexp = regexp.MustCompile(`^(?P[^.]+)\.(scripts|commands)`) localConfigNames = []string{"lefthook-local", ".lefthook-local"} + names = []string{"lefthook", ".lefthook"} + extensions = []string{".yml", ".yaml", ".toml", ".json"} + parsers = map[string]func() koanf.Parser{ + ".json": jsonParser, + ".yaml": yamlParser, + ".yml": yamlParser, + ".toml": tomlParser, + } ) -// NotFoundError wraps viper.ConfigFileNotFoundError for lefthook. -type NotFoundError struct { - message string +type ErrNotFound struct { + msg string +} + +func (e ErrNotFound) Error() string { + return e.msg +} + +func yamlParser() koanf.Parser { + return yaml.Parser() +} + +func jsonParser() koanf.Parser { + return json.Parser() +} + +func tomlParser() koanf.Parser { + return toml.Parser() } -// Error returns message of viper.ConfigFileNotFoundError. -func (err NotFoundError) Error() string { - return err.message +type fs struct { + fs afero.Fs +} + +func (f fs) Open(name string) (iofs.File, error) { + return f.fs.Open(name) } // Loads configs from the given directory with extensions. -func Load(fs afero.Fs, repo *git.Repository) (*Config, error) { - global, err := readOne(fs, repo.RootPath, []string{"lefthook", ".lefthook"}) +func Load(f afero.Fs, repo *git.Repository) (*Config, error) { + fs := fs{fs: f} + global, err := readOne(fs, repo.RootPath) if err != nil { return nil, err } @@ -62,46 +94,32 @@ func Load(fs afero.Fs, repo *git.Repository) (*Config, error) { return &config, nil } -func read(fs afero.Fs, path string, name string) (*viper.Viper, error) { - v := newViper(fs, path) - v.SetConfigName(name) - - if err := v.ReadInConfig(); err != nil { +func read(fs fs, path string, name string, parser func() koanf.Parser) (*koanf.Koanf, error) { + k := koanf.New(".") + if err := k.Load(koanffs.Provider(fs, filepath.Join(path, name)), parser()); err != nil { return nil, err } - return v, nil + return k, nil } -func newViper(fs afero.Fs, path string) *viper.Viper { - v := viper.New() - v.SetFs(fs) - v.AddConfigPath(path) - - // Allow overwriting settings with ENV variables - v.SetEnvPrefix("LEFTHOOK") - v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - v.AutomaticEnv() - - return v -} - -func readOne(fs afero.Fs, path string, names []string) (*viper.Viper, error) { +func readOne(fs fs, path string) (*koanf.Koanf, error) { for _, name := range names { - v, err := read(fs, path, name) - if err != nil { - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); ok { - continue + for _, ext := range extensions { + k, err := read(fs, path, name+ext, parsers[ext]) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + + return nil, err } - return nil, err + return k, nil } - - return v, nil } - return nil, NotFoundError{fmt.Sprintf("No config files with names %q have been found in \"%s\"", names, path)} + return nil, ErrNotFound{fmt.Sprintf("No config files were found in \"%s\"", path)} } // mergeAll merges configs using the following order. @@ -109,8 +127,8 @@ func readOne(fs afero.Fs, path string, names []string) (*viper.Viper, error) { // - files from `extends` // - files from `remotes` // - lefthook-local/.lefthook-local. -func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) { - extends, err := readOne(fs, repo.RootPath, []string{"lefthook", ".lefthook"}) +func mergeAll(fs fs, repo *git.Repository) (*koanf.Koanf, error) { + extends, err := readOne(fs, repo.RootPath) if err != nil { return nil, err } @@ -120,24 +138,23 @@ func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) { } // Save global extends to compare them after merging local config - globalExtends := extends.GetStringSlice("extends") + globalExtends := extends.Strings("extends") if err := mergeRemotes(fs, repo, extends); err != nil { return nil, err } //nolint:nestif - if err := mergeLocal(extends); err == nil { + if err := mergeLocal(fs, extends, repo.RootPath); err == nil { // Local extends need to be re-applied only if they have different settings - localExtends := extends.GetStringSlice("extends") + localExtends := extends.Strings("extends") if !slices.Equal(globalExtends, localExtends) { if err = extend(fs, extends, repo.RootPath); err != nil { return nil, err } } } else { - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); !ok { + if !errors.Is(err, os.ErrNotExist) { return nil, err } } @@ -146,17 +163,17 @@ func mergeAll(fs afero.Fs, repo *git.Repository) (*viper.Viper, error) { } // mergeRemotes merges remote configs to the current one. -func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error { +func mergeRemotes(fs fs, repo *git.Repository, k *koanf.Koanf) error { var remote *Remote // Deprecated var remotes []*Remote - err := v.UnmarshalKey("remotes", &remotes) + err := k.Unmarshal("remotes", &remotes) if err != nil { return err } // Deprecated - err = v.UnmarshalKey("remote", &remote) + err = k.Unmarshal("remote", &remote) if err != nil { return err } @@ -187,22 +204,22 @@ func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error { log.Debugf("Merging remote config: %s: %s", remote.GitURL, configPath) - _, err = fs.Stat(configPath) - if err != nil { - continue - } + // _, err = fs.Stat(configPath) + // if err != nil { + // continue + // } - if err = merge("remotes", configPath, v); err != nil { + if err = merge(fs, k, configPath); err != nil { return err } - if err = extend(fs, v, filepath.Dir(configPath)); err != nil { + if err = extend(fs, k, filepath.Dir(configPath)); err != nil { return err } } // Reset extends to omit issues when extending with remote extends. - err = v.MergeConfigMap(map[string]interface{}{"extends": nil}) + err = k.Set("extends", nil) if err != nil { return err } @@ -212,34 +229,38 @@ func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper) error { } // extend merges all files listed in 'extends' option into the config. -func extend(fs afero.Fs, v *viper.Viper, root string) error { - return extendRecursive(fs, v, root, make(map[string]struct{})) +func extend(fs fs, k *koanf.Koanf, root string) error { + return extendRecursive(fs, k, root, make(map[string]struct{})) } // extendRecursive merges extends. // If extends contain other extends they get merged too. -func extendRecursive(fs afero.Fs, v *viper.Viper, root string, extends map[string]struct{}) error { - for _, path := range v.GetStringSlice("extends") { +func extendRecursive(fs fs, k *koanf.Koanf, root string, extends map[string]struct{}) error { + for _, path := range k.Strings("extends") { if _, contains := extends[path]; contains { return fmt.Errorf("possible recursion in extends: path %s is specified multiple times", path) } extends[path] = struct{}{} + ext := filepath.Ext(path) + if len(ext) == 0 || parsers[ext] == nil { + return fmt.Errorf("unable to parse an extension: %s", path) + } + if !filepath.IsAbs(path) { path = filepath.Join(root, path) } - extendV := newViper(fs, root) - extendV.SetConfigFile(path) - if err := extendV.ReadInConfig(); err != nil { - return err + ko := koanf.New(".") + if err := ko.Load(koanffs.Provider(fs, path), parsers[ext]()); err != nil { + return fmt.Errorf("failed to load file %s: %w", path, err) } - if err := extendRecursive(fs, extendV, root, extends); err != nil { + if err := extendRecursive(fs, ko, root, extends); err != nil { return err } - if err := v.MergeConfigMap(extendV.AllSettings()); err != nil { + if err := k.Merge(ko); err != nil { return err } } @@ -247,30 +268,44 @@ func extendRecursive(fs afero.Fs, v *viper.Viper, root string, extends map[strin return nil } -// merge merges the configuration using viper builtin MergeInConfig. -func merge(name, path string, v *viper.Viper) error { - v.SetConfigName(name) - v.SetConfigFile(path) - return v.MergeInConfig() +// merge merges the configuration with a new one parsed from `path`. +func merge(fs fs, k *koanf.Koanf, path string) error { + ext := filepath.Ext(path) + if len(ext) == 0 || parsers[ext] == nil { + return fmt.Errorf("unable to parse an extension: %s", path) + } + + ko := koanf.New(".") + if err := ko.Load(koanffs.Provider(fs, path), parsers[ext]()); err != nil { + return fmt.Errorf("failed to load file %s: %w", path, err) + } + + if err := k.Merge(ko); err != nil { + return err + } + + return nil } -func mergeLocal(v *viper.Viper) error { +// mergeLocal merges local configurations if they exist. +func mergeLocal(fs fs, k *koanf.Koanf, root string) error { for _, name := range localConfigNames { - err := merge(name, "", v) - if err == nil { - break - } + for _, ext := range extensions { + err := merge(fs, k, filepath.Join(root, name+ext)) + if err == nil { + break + } - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); !ok { - return err + if !errors.Is(err, os.ErrNotExist) { + return err + } } } return nil } -func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { +func unmarshalConfigs(base, extra *koanf.Koanf, c *Config) error { c.Hooks = make(map[string]*Hook) for _, hookName := range AvailableHooks { @@ -282,7 +317,7 @@ func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { // For extra non-git hooks. // This behavior may be deprecated in next versions. // Notice that with append we're allowing extra hooks to be added in local config - for _, maybeHook := range append(base.AllKeys(), extra.AllKeys()...) { + for _, maybeHook := range append(base.Keys(), extra.Keys()...) { if !hookKeyRegexp.MatchString(maybeHook) { continue } @@ -299,11 +334,11 @@ func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { } // Merge config and unmarshal it - if err := base.MergeConfigMap(extra.AllSettings()); err != nil { + if err := base.Merge(extra); err != nil { return err } - if err := base.Unmarshal(c); err != nil { + if err := base.Unmarshal(".", c); err != nil { return err } @@ -327,9 +362,18 @@ func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { return nil } -func addHook(hookName string, base, extra *viper.Viper, c *Config) error { - baseHook := base.Sub(hookName) - extraHook := extra.Sub(hookName) +func addHook(hookName string, base, extra *koanf.Koanf, c *Config) error { + if !extra.Exists(hookName) { + return nil + } + + if !base.Exists(hookName) { + base.Set(hookName, extra.Cut(hookName).Raw()) + return nil + } + + baseHook := base.Cut(hookName) + extraHook := extra.Cut(hookName) resultHook, err := unmarshalHooks(baseHook, extraHook) if err != nil { diff --git a/internal/config/script.go b/internal/config/script.go index bd3e1f8d..c3d38a9d 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -3,8 +3,8 @@ package config import ( "strings" + "github.com/knadh/koanf/v2" "github.com/mitchellh/mapstructure" - "github.com/spf13/viper" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" @@ -38,21 +38,21 @@ func (s Script) ExecutionPriority() int { return s.Priority } -func mergeScripts(base, extra *viper.Viper) (map[string]*Script, error) { +func mergeScripts(base, extra *koanf.Koanf) (map[string]*Script, error) { if base == nil && extra == nil { return nil, nil } if base == nil { - return unmarshalScripts(extra.GetStringMap("scripts")) + return unmarshalScripts(extra.Cut("scripts").Raw()) } if extra == nil { - return unmarshalScripts(base.GetStringMap("scripts")) + return unmarshalScripts(base.Cut("scripts").Raw()) } - scriptsOrigin := base.GetStringMap("scripts") - scriptsOverride := extra.GetStringMap("scripts") + scriptsOrigin := base.Cut("scripts").Raw() + scriptsOverride := extra.Cut("scripts").Raw() if scriptsOrigin == nil { return unmarshalScripts(scriptsOverride) } @@ -71,14 +71,11 @@ func mergeScripts(base, extra *viper.Viper) (map[string]*Script, error) { runReplaces[key] = &runReplace } - err := base.MergeConfigMap(map[string]interface{}{ - "scripts": scriptsOverride, - }) - if err != nil { + if err := base.Set("scripts", scriptsOverride); err != nil { return nil, err } - scripts, err := unmarshalScripts(base.GetStringMap("scripts")) + scripts, err := unmarshalScripts(base.Cut("scripts").Raw()) if err != nil { return nil, err } diff --git a/internal/lefthook/run.go b/internal/lefthook/run.go index 0c38c5c7..2acd6640 100644 --- a/internal/lefthook/run.go +++ b/internal/lefthook/run.go @@ -55,8 +55,8 @@ func (l *Lefthook) Run(hookName string, args RunArgs, gitArgs []string) error { // Load config cfg, err := config.Load(l.Fs, l.repo) if err != nil { - var notFoundErr config.NotFoundError - if ok := errors.As(err, ¬FoundErr); ok { + var errNotFound config.ErrNotFound + if ok := errors.As(err, &errNotFound); ok { log.Warn(err.Error()) return nil }