From bbe436939a49d30111f20370b264f76620dcc9c4 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 2 Sep 2024 12:57:00 +0300 Subject: [PATCH 01/14] chore: replace viper with koanf --- go.mod | 24 ++-- go.sum | 56 ++++------ internal/config/command.go | 28 ++--- internal/config/hook.go | 8 +- internal/config/load.go | 223 ++++++++++++++++++++++--------------- internal/config/script.go | 19 ++-- internal/lefthook/run.go | 4 +- 7 files changed, 193 insertions(+), 169 deletions(-) diff --git a/go.mod b/go.mod index bc9e6ebe..58987ac9 100644 --- a/go.mod +++ b/go.mod @@ -10,15 +10,18 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 github.com/creack/pty v1.1.24 github.com/gobwas/glob v0.2.3 + 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.13.1 github.com/schollz/progressbar/v3 v3.17.1 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 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 ) @@ -26,36 +29,31 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/x/ansi v0.4.2 // 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/tools v0.22.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.27.0 // indirect golang.org/x/term v0.26.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 2b7ce187..016b2c8d 100644 --- a/go.sum +++ b/go.sum @@ -13,34 +13,37 @@ github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoC github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= 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.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 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/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= @@ -52,51 +55,36 @@ 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 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.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U= github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4= -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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= @@ -112,7 +100,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 8a2ca4da..d8b89658 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 4e970b22..a302bc02 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(state func() git.State) bool { return skipChecker.check(state, 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 e8f0fb98..4dbd6d43 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() } -// Error returns message of viper.ConfigFileNotFoundError. -func (err NotFoundError) Error() string { - return err.message +func tomlParser() koanf.Parser { + return toml.Parser() +} + +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,19 +229,19 @@ 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 _, pathOrGlob := range v.GetStringSlice("extends") { +func extendRecursive(fs fs, k *koanf.Koanf, root string, extends map[string]struct{}) error { + for _, pathOrGlob := range k.Strings("extends") { if !filepath.IsAbs(pathOrGlob) { pathOrGlob = filepath.Join(root, pathOrGlob) } - paths, err := afero.Glob(fs, pathOrGlob) + paths, err := afero.Glob(fs.fs, pathOrGlob) if err != nil { return fmt.Errorf("bad glob syntax for '%s': %w", pathOrGlob, err) } @@ -235,49 +252,70 @@ func extendRecursive(fs afero.Fs, v *viper.Viper, root string, extends map[strin } extends[path] = struct{}{} - extendV := newViper(fs, root) - extendV.SetConfigFile(path) - if err := extendV.ReadInConfig(); err != nil { - return err + ext := filepath.Ext(path) + if len(ext) == 0 || parsers[ext] == nil { + return fmt.Errorf("unable to parse an extension: %s", path) } - if err := extendRecursive(fs, extendV, root, extends); err != nil { + if !filepath.IsAbs(path) { + path = filepath.Join(root, 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 := 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 } } } - 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 { @@ -289,7 +327,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 } @@ -306,11 +344,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 } @@ -334,9 +372,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 a8c74635..ec162ba3 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 b27f04e9..3b1307c2 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 } From 451c7080a6711ecde0c3423e0ae91d3c463ad9ee Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 18 Nov 2024 10:43:07 +0300 Subject: [PATCH 02/14] fix: refactor load.go --- internal/config/load.go | 288 +++++++++++++++++----------------------- 1 file changed, 121 insertions(+), 167 deletions(-) diff --git a/internal/config/load.go b/internal/config/load.go index 4dbd6d43..e390a9e2 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -1,20 +1,16 @@ +// TODO rewrite using Koanf 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" @@ -29,53 +25,51 @@ 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, + mainConfigNames = []string{"lefthook", ".lefthook"} + extensions = map[string]struct{}{ + ".yaml": {}, + ".json": {}, + ".toml": {}, + ".yml": {}, } ) -type ErrNotFound struct { - msg string +// NotFoundError wraps viper.ConfigFileNotFoundError for lefthook. +type NotFoundError struct { + message 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() -} - -type fs struct { - fs afero.Fs -} - -func (f fs) Open(name string) (iofs.File, error) { - return f.fs.Open(name) +// Error returns message of viper.ConfigFileNotFoundError. +func (err NotFoundError) Error() string { + return err.message } // Loads configs from the given directory with extensions. -func Load(f afero.Fs, repo *git.Repository) (*Config, error) { - fs := fs{fs: f} - global, err := readOne(fs, repo.RootPath) +func Load(fs afero.Fs, repo *git.Repository) (*Config, error) { + main, err := readOne(fs, repo.RootPath, mainConfigNames) if err != nil { return nil, err } - extends, err := mergeAll(fs, repo) + extends := main.GetStringSlice("extends") + var remote *Remote + var remotes []*Remote + err = main.UnmarshalKey("remotes", &remotes) + if err != nil { + return nil, err + } + // Deprecated + err = main.UnmarshalKey("remote", &remote) + if err != nil { + return nil, err + } + + // Backward compatibility + if remote != nil { + remotes = append(remotes, remote) + } + + secondary, err := readSecondary(fs, repo, extends, remotes) if err != nil { return nil, err } @@ -85,7 +79,7 @@ func Load(f afero.Fs, repo *git.Repository) (*Config, error) { config.SourceDir = DefaultSourceDir config.SourceDirLocal = DefaultSourceDirLocal - err = unmarshalConfigs(global, extends, &config) + err = unmarshalConfigs(main, secondary, &config) if err != nil { return nil, err } @@ -94,95 +88,83 @@ func Load(f afero.Fs, repo *git.Repository) (*Config, error) { return &config, 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 { +func read(fs afero.Fs, name, path string) (*viper.Viper, error) { + v := newViper(fs, path) + v.SetConfigName(name) + + if err := v.ReadInConfig(); err != nil { return nil, err } - return k, nil + return v, nil } -func readOne(fs fs, path string) (*koanf.Koanf, error) { - for _, name := range names { - for _, ext := range extensions { - k, err := read(fs, path, name+ext, parsers[ext]) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - continue - } +func newViper(fs afero.Fs, path string) *viper.Viper { + v := viper.New() + v.SetFs(fs) + v.AddConfigPath(path) - return nil, err + // 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) { + for _, name := range names { + v, err := read(fs, name, path) + if err != nil { + var notFoundErr viper.ConfigFileNotFoundError + if ok := errors.As(err, ¬FoundErr); ok { + continue } - return k, nil + return nil, err } + + return v, nil } - return nil, ErrNotFound{fmt.Sprintf("No config files were found in \"%s\"", path)} + return nil, NotFoundError{fmt.Sprintf("No config files with names %q have been found in \"%s\"", names, path)} } -// mergeAll merges configs using the following order. -// - lefthook/.lefthook +// readSecondary reads extends, remotes and local config. // - files from `extends` // - files from `remotes` // - lefthook-local/.lefthook-local. -func mergeAll(fs fs, repo *git.Repository) (*koanf.Koanf, error) { - extends, err := readOne(fs, repo.RootPath) - if err != nil { +func readSecondary(fs afero.Fs, repo *git.Repository, extends []string, remotes []*Remote) (*viper.Viper, error) { + secondary := newViper(fs, repo.RootPath) + if err := extend(fs, repo.RootPath, secondary, extends); err != nil { return nil, err } - if err := extend(fs, extends, repo.RootPath); err != nil { - return nil, err - } - - // Save global extends to compare them after merging local config - globalExtends := extends.Strings("extends") - - if err := mergeRemotes(fs, repo, extends); err != nil { + if err := mergeRemotes(fs, repo, secondary, remotes); err != nil { return nil, err } //nolint:nestif - if err := mergeLocal(fs, extends, repo.RootPath); err == nil { + if err := mergeLocal(secondary); err == nil { // Local extends need to be re-applied only if they have different settings - localExtends := extends.Strings("extends") - if !slices.Equal(globalExtends, localExtends) { - if err = extend(fs, extends, repo.RootPath); err != nil { + localExtends := secondary.GetStringSlice("extends") + if !slices.Equal(extends, localExtends) { + if err = extend(fs, repo.RootPath, secondary, localExtends); err != nil { return nil, err } } } else { - if !errors.Is(err, os.ErrNotExist) { + var notFoundErr viper.ConfigFileNotFoundError + if ok := errors.As(err, ¬FoundErr); !ok { return nil, err } } - return extends, nil + return secondary, nil } // mergeRemotes merges remote configs to the current one. -func mergeRemotes(fs fs, repo *git.Repository, k *koanf.Koanf) error { - var remote *Remote // Deprecated - var remotes []*Remote - - err := k.Unmarshal("remotes", &remotes) - if err != nil { - return err - } - - // Deprecated - err = k.Unmarshal("remote", &remote) - if err != nil { - return err - } - - // Backward compatibility - if remote != nil { - remotes = append(remotes, remote) - } - +func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper, remotes []*Remote) error { for _, remote := range remotes { if !remote.Configured() { continue @@ -204,22 +186,23 @@ func mergeRemotes(fs fs, repo *git.Repository, k *koanf.Koanf) 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(fs, k, configPath); err != nil { + if err = merge(v, "remotes", configPath); err != nil { return err } - if err = extend(fs, k, filepath.Dir(configPath)); err != nil { + extends := v.GetStringSlice("extends") + if err = extend(fs, filepath.Dir(configPath), v, extends); err != nil { return err } } // Reset extends to omit issues when extending with remote extends. - err = k.Set("extends", nil) + err := v.MergeConfigMap(map[string]interface{}{"extends": nil}) if err != nil { return err } @@ -229,93 +212,73 @@ func mergeRemotes(fs fs, repo *git.Repository, k *koanf.Koanf) error { } // extend merges all files listed in 'extends' option into the config. -func extend(fs fs, k *koanf.Koanf, root string) error { - return extendRecursive(fs, k, root, make(map[string]struct{})) +func extend(fs afero.Fs, root string, v *viper.Viper, extends []string) error { + return extendRecursive(fs, root, v, extends, make(map[string]struct{})) } // extendRecursive merges extends. // If extends contain other extends they get merged too. -func extendRecursive(fs fs, k *koanf.Koanf, root string, extends map[string]struct{}) error { - for _, pathOrGlob := range k.Strings("extends") { +func extendRecursive(fs afero.Fs, root string, v *viper.Viper, extends []string, visited map[string]struct{}) error { + for _, pathOrGlob := range extends { if !filepath.IsAbs(pathOrGlob) { pathOrGlob = filepath.Join(root, pathOrGlob) } - paths, err := afero.Glob(fs.fs, pathOrGlob) + paths, err := afero.Glob(fs, pathOrGlob) if err != nil { return fmt.Errorf("bad glob syntax for '%s': %w", pathOrGlob, err) } for _, path := range paths { - if _, contains := extends[path]; contains { + if _, contains := visited[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) - } + visited[path] = struct{}{} - 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) + extendV := newViper(fs, root) + extendV.SetConfigFile(path) + if err := extendV.ReadInConfig(); err != nil { + return err } - if err := extendRecursive(fs, ko, root, extends); err != nil { + if err := extendRecursive(fs, root, extendV, extendV.GetStringSlice("extends"), visited); err != nil { return err } - if err := k.Merge(ko); err != nil { + if err := v.MergeConfigMap(extendV.AllSettings()); err != nil { return err } } } + return nil } -// 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 +// merge merges the configuration using viper builtin MergeInConfig. +func merge(v *viper.Viper, name, path string) error { + v.SetConfigName(name) + v.SetConfigFile(path) + return v.MergeInConfig() } -// mergeLocal merges local configurations if they exist. -func mergeLocal(fs fs, k *koanf.Koanf, root string) error { +func mergeLocal(v *viper.Viper) error { for _, name := range localConfigNames { - for _, ext := range extensions { - err := merge(fs, k, filepath.Join(root, name+ext)) - if err == nil { - break + if err := merge(v, name, ""); err != nil { + var notFoundErr viper.ConfigFileNotFoundError + if ok := errors.As(err, ¬FoundErr); ok { + continue } - if !errors.Is(err, os.ErrNotExist) { - return err - } + return err } + + break } return nil } -func unmarshalConfigs(base, extra *koanf.Koanf, c *Config) error { +func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { c.Hooks = make(map[string]*Hook) for hookName := range AvailableHooks { @@ -327,7 +290,7 @@ func unmarshalConfigs(base, extra *koanf.Koanf, 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.Keys(), extra.Keys()...) { + for _, maybeHook := range append(base.AllKeys(), extra.AllKeys()...) { if !hookKeyRegexp.MatchString(maybeHook) { continue } @@ -344,11 +307,11 @@ func unmarshalConfigs(base, extra *koanf.Koanf, c *Config) error { } // Merge config and unmarshal it - if err := base.Merge(extra); err != nil { + if err := base.MergeConfigMap(extra.AllSettings()); err != nil { return err } - if err := base.Unmarshal(".", c); err != nil { + if err := base.Unmarshal(c); err != nil { return err } @@ -372,18 +335,9 @@ func unmarshalConfigs(base, extra *koanf.Koanf, c *Config) error { return nil } -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) +func addHook(hookName string, base, extra *viper.Viper, c *Config) error { + baseHook := base.Sub(hookName) + extraHook := extra.Sub(hookName) resultHook, err := unmarshalHooks(baseHook, extraHook) if err != nil { From 38b26feff3601342fd91d76c65ce23240905e433 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 19 Nov 2024 11:43:15 +0300 Subject: [PATCH 03/14] fix: rewrite basic load of configs in koanf --- go.mod | 15 ++- go.sum | 38 +++++- internal/config/load.go | 248 ++++++++++++++++++++++------------------ 3 files changed, 184 insertions(+), 117 deletions(-) diff --git a/go.mod b/go.mod index 58987ac9..ae46067d 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/schollz/progressbar/v3 v3.17.1 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 ) @@ -29,15 +30,25 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/x/ansi v0.4.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect - github.com/kr/pretty v0.3.1 // indirect + github.com/magiconair/properties v1.8.7 // 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 + github.com/spf13/cast v1.6.0 // indirect + github.com/subosito/gotenv v1.6.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.22.0 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect ) require ( diff --git a/go.sum b/go.sum index 016b2c8d..04f8cd5f 100644 --- a/go.sum +++ b/go.sum @@ -13,17 +13,26 @@ github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoC github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= 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.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 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/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= @@ -44,6 +53,8 @@ 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= @@ -65,26 +76,45 @@ 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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 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.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U= github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4= +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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= @@ -100,5 +130,7 @@ 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/load.go b/internal/config/load.go index e390a9e2..9f596bb7 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -6,9 +6,12 @@ import ( "fmt" "path/filepath" "regexp" - "slices" - "strings" + "github.com/knadh/koanf/parsers/json" + "github.com/knadh/koanf/parsers/toml/v2" + "github.com/knadh/koanf/parsers/yaml" + kfs "github.com/knadh/koanf/providers/fs" + "github.com/knadh/koanf/v2" "github.com/spf13/afero" "github.com/spf13/viper" @@ -26,11 +29,17 @@ var ( hookKeyRegexp = regexp.MustCompile(`^(?P[^.]+)\.(scripts|commands)`) localConfigNames = []string{"lefthook-local", ".lefthook-local"} mainConfigNames = []string{"lefthook", ".lefthook"} - extensions = map[string]struct{}{ - ".yaml": {}, - ".json": {}, - ".toml": {}, - ".yml": {}, + extensions = []string{ + ".yml", + ".yaml", + ".json", + ".toml", + } + parsers = map[string]koanf.Parser{ + ".yml": yaml.Parser(), + ".yaml": yaml.Parser(), + ".json": json.Parser(), + ".toml": toml.Parser(), } ) @@ -44,43 +53,79 @@ func (err NotFoundError) Error() string { return err.message } +func loadOne(k *koanf.Koanf, filesystem afero.Fs, root string, names []string) error { + for _, extension := range extensions { + for _, name := range names { + config := filepath.Join(root, name+extension) + if ok, _ := afero.Exists(filesystem, config); !ok { + continue + } + + if err := k.Load(kfs.Provider(afero.NewIOFS(filesystem), config), parsers[extension]); err != nil { + return err + } + + return nil + } + } + + return NotFoundError{fmt.Sprintf("No config files with names %q have been found in \"%s\"", names, root)} +} + // Loads configs from the given directory with extensions. -func Load(fs afero.Fs, repo *git.Repository) (*Config, error) { - main, err := readOne(fs, repo.RootPath, mainConfigNames) - if err != nil { +func Load(filesystem afero.Fs, repo *git.Repository) (*Config, error) { + main := koanf.New(".") + + // Load main (e.g. lefthook.yml) + if err := loadOne(main, filesystem, repo.RootPath, mainConfigNames); err != nil { return nil, err } - extends := main.GetStringSlice("extends") - var remote *Remote + // Save `extends` and `remotes` + extends := main.Strings("extends") var remotes []*Remote - err = main.UnmarshalKey("remotes", &remotes) - if err != nil { + if err := main.Unmarshal("remotes", &remotes); err != nil { return nil, err } + // Deprecated - err = main.UnmarshalKey("remote", &remote) - if err != nil { + var remote *Remote + if err := main.Unmarshal("remote", &remote); err != nil { return nil, err } - // Backward compatibility + // Backward compatibility for `remote`. Will be deleted in future major release if remote != nil { remotes = append(remotes, remote) } - secondary, err := readSecondary(fs, repo, extends, remotes) - if err != nil { + secondary := koanf.New(".") + + // Load main `extends` + if err := extend(secondary, filesystem, repo.RootPath, extends); err != nil { return nil, err } + // Load main `remotes` + if err := loadRemotes(secondary, filesystem, repo, remotes); err != nil { + return nil, err + } + + // Load optional local config (e.g. lefthook-local.yml) + if err := loadOne(secondary, filesystem, repo.RootPath, localConfigNames); err != nil { + var notFoundErr NotFoundError + if ok := errors.As(err, ¬FoundErr); !ok { + return nil, err + } + } + var config Config config.SourceDir = DefaultSourceDir config.SourceDirLocal = DefaultSourceDirLocal - err = unmarshalConfigs(main, secondary, &config) - if err != nil { + // TODO: continue here, merge rules must be applied + if err := unmarshalConfigs(main, secondary, &config); err != nil { return nil, err } @@ -88,83 +133,56 @@ func Load(fs afero.Fs, repo *git.Repository) (*Config, error) { return &config, nil } -func read(fs afero.Fs, name, path string) (*viper.Viper, error) { - v := newViper(fs, path) - v.SetConfigName(name) +// func read(fs afero.Fs, name, path string) (*viper.Viper, error) { +// v := newViper(fs, path) +// v.SetConfigName(name) - if err := v.ReadInConfig(); err != nil { - return nil, err - } +// if err := v.ReadInConfig(); err != nil { +// return nil, err +// } - return v, nil -} +// return v, nil +// } -func newViper(fs afero.Fs, path string) *viper.Viper { - v := viper.New() - v.SetFs(fs) - v.AddConfigPath(path) +// 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() +// // Allow overwriting settings with ENV variables +// v.SetEnvPrefix("LEFTHOOK") +// v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) +// v.AutomaticEnv() - return v -} +// return v +// } -func readOne(fs afero.Fs, path string, names []string) (*viper.Viper, error) { - for _, name := range names { - v, err := read(fs, name, path) - if err != nil { - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); ok { - continue - } - - return nil, err - } - - return v, nil - } - - return nil, NotFoundError{fmt.Sprintf("No config files with names %q have been found in \"%s\"", names, path)} -} - -// readSecondary reads extends, remotes and local config. +// loadSecondary reads extends, remotes and local config. // - files from `extends` // - files from `remotes` // - lefthook-local/.lefthook-local. -func readSecondary(fs afero.Fs, repo *git.Repository, extends []string, remotes []*Remote) (*viper.Viper, error) { - secondary := newViper(fs, repo.RootPath) - if err := extend(fs, repo.RootPath, secondary, extends); err != nil { - return nil, err - } - - if err := mergeRemotes(fs, repo, secondary, remotes); err != nil { - return nil, err - } - - //nolint:nestif - if err := mergeLocal(secondary); err == nil { - // Local extends need to be re-applied only if they have different settings - localExtends := secondary.GetStringSlice("extends") - if !slices.Equal(extends, localExtends) { - if err = extend(fs, repo.RootPath, secondary, localExtends); err != nil { - return nil, err - } - } - } else { - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); !ok { - return nil, err - } - } - - return secondary, nil -} - -// mergeRemotes merges remote configs to the current one. -func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper, remotes []*Remote) error { +//func loadSecondary(k *koanf.Koanf, fs afero.Fs, repo *git.Repository, extends []string, remotes []*Remote) (*viper.Viper, error) { +// //nolint:nestif +// if err := mergeLocal(secondary); err == nil { +// // Local extends need to be re-applied only if they have different settings +// localExtends := secondary.GetStringSlice("extends") +// if !slices.Equal(extends, localExtends) { +// if err = extend(fs, repo.RootPath, secondary, localExtends); err != nil { +// return nil, err +// } +// } +// } else { +// var notFoundErr viper.ConfigFileNotFoundError +// if ok := errors.As(err, ¬FoundErr); !ok { +// return nil, err +// } +// } + +// return secondary, nil +//} + +// loadRemotes merges remote configs to the current one. +func loadRemotes(k *koanf.Koanf, filesystem afero.Fs, repo *git.Repository, remotes []*Remote) error { for _, remote := range remotes { if !remote.Configured() { continue @@ -186,24 +204,27 @@ func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper, remotes []* log.Debugf("Merging remote config: %s: %s", remote.GitURL, configPath) - _, err := fs.Stat(configPath) - if err != nil { + if ok, err := afero.Exists(filesystem, configPath); !ok || err != nil { continue } - if err = merge(v, "remotes", configPath); err != nil { + parser, ok := parsers[filepath.Ext(configPath)] + if !ok { + panic("TODO: unknown extension to parse") + } + + if err := k.Load(kfs.Provider(afero.NewIOFS(filesystem), configPath), parser); err != nil { return err } - extends := v.GetStringSlice("extends") - if err = extend(fs, filepath.Dir(configPath), v, extends); err != nil { + extends := k.Slices("extends") + if err := extend(k, filesystem, filepath.Dir(configPath), extends); err != nil { return err } } // Reset extends to omit issues when extending with remote extends. - err := v.MergeConfigMap(map[string]interface{}{"extends": nil}) - if err != nil { + if err := k.Set("extends", nil); err != nil { return err } } @@ -212,19 +233,19 @@ func mergeRemotes(fs afero.Fs, repo *git.Repository, v *viper.Viper, remotes []* } // extend merges all files listed in 'extends' option into the config. -func extend(fs afero.Fs, root string, v *viper.Viper, extends []string) error { - return extendRecursive(fs, root, v, extends, make(map[string]struct{})) +func extend(k *koanf.Koanf, filesystem afero.Fs, root string, extends []string) error { + return extendRecursive(k, filesystem, root, extends, make(map[string]struct{})) } // extendRecursive merges extends. // If extends contain other extends they get merged too. -func extendRecursive(fs afero.Fs, root string, v *viper.Viper, extends []string, visited map[string]struct{}) error { +func extendRecursive(k *koanf.Koanf, filesystem afero.Fs, root string, extends []string, visited map[string]struct{}) error { for _, pathOrGlob := range extends { if !filepath.IsAbs(pathOrGlob) { pathOrGlob = filepath.Join(root, pathOrGlob) } - paths, err := afero.Glob(fs, pathOrGlob) + paths, err := afero.Glob(filesystem, pathOrGlob) if err != nil { return fmt.Errorf("bad glob syntax for '%s': %w", pathOrGlob, err) } @@ -235,17 +256,20 @@ func extendRecursive(fs afero.Fs, root string, v *viper.Viper, extends []string, } visited[path] = struct{}{} - extendV := newViper(fs, root) - extendV.SetConfigFile(path) - if err := extendV.ReadInConfig(); err != nil { + extent := koanf.New(".") + parser, ok := parsers[filepath.Ext(path)] + if !ok { + panic("TODO: unknown extension for extent " + path) + } + if err := extent.Load(kfs.Provider(afero.NewIOFS(filesystem), path), parser); err != nil { return err } - if err := extendRecursive(fs, root, extendV, extendV.GetStringSlice("extends"), visited); err != nil { + if err := extendRecursive(extent, filesystem, root, extent.Strings("extends"), visited); err != nil { return err } - if err := v.MergeConfigMap(extendV.AllSettings()); err != nil { + if err := k.Merge(extent); err != nil { return err } } @@ -254,14 +278,14 @@ func extendRecursive(fs afero.Fs, root string, v *viper.Viper, extends []string, return nil } -// merge merges the configuration using viper builtin MergeInConfig. -func merge(v *viper.Viper, name, path string) error { - v.SetConfigName(name) - v.SetConfigFile(path) - return v.MergeInConfig() -} +// // merge merges the configuration using viper builtin MergeInConfig. +// func merge(v *viper.Viper, name, path string) error { +// v.SetConfigName(name) +// v.SetConfigFile(path) +// return v.MergeInConfig() +// } -func mergeLocal(v *viper.Viper) error { +func loadLocal(k *koanf.Koanf) error { for _, name := range localConfigNames { if err := merge(v, name, ""); err != nil { var notFoundErr viper.ConfigFileNotFoundError From 577e78f531bf9f40c41e82122fadab531d169350 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Wed, 20 Nov 2024 11:25:57 +0300 Subject: [PATCH 04/14] fix: load with koanf (no complicated load yet) --- internal/config/command.go | 6 +- internal/config/config.go | 12 +-- internal/config/hook.go | 45 +------- internal/config/load.go | 203 +++++++++++++++++++------------------ internal/config/remote.go | 4 +- internal/lefthook/run.go | 2 +- 6 files changed, 116 insertions(+), 156 deletions(-) diff --git a/internal/config/command.go b/internal/config/command.go index d8b89658..5fe5085a 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -21,17 +21,17 @@ type Command struct { Tags []string `json:"tags,omitempty" mapstructure:"tags" toml:"tags,omitempty" yaml:",omitempty"` Env map[string]string `json:"env,omitempty" mapstructure:"env" toml:"env,omitempty" yaml:",omitempty"` - FileTypes []string `json:"file_types,omitempty" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty"` + FileTypes []string `json:"file_types,omitempty" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty" koanf:"file_types"` Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` Exclude interface{} `json:"exclude,omitempty" mapstructure:"exclude" toml:"exclude,omitempty" yaml:",omitempty"` Priority int `json:"priority,omitempty" mapstructure:"priority" toml:"priority,omitempty" yaml:",omitempty"` - FailText string `json:"fail_text,omitempty" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty"` + FailText string `json:"fail_text,omitempty" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty" koanf:"fail_text"` Interactive bool `json:"interactive,omitempty" mapstructure:"interactive" toml:"interactive,omitempty" yaml:",omitempty"` UseStdin bool `json:"use_stdin,omitempty" mapstructure:"use_stdin" toml:"use_stdin,omitempty" yaml:",omitempty"` - StageFixed bool `json:"stage_fixed,omitempty" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` + StageFixed bool `json:"stage_fixed,omitempty" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty" koanf:"stage_fixed"` } type commandRunReplace struct { diff --git a/internal/config/config.go b/internal/config/config.go index aab7e5e9..286cff6c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -27,15 +27,15 @@ const ( ) type Config struct { - MinVersion string `mapstructure:"min_version,omitempty"` - SourceDir string `mapstructure:"source_dir"` - SourceDirLocal string `mapstructure:"source_dir_local"` + MinVersion string `mapstructure:"min_version,omitempty" koanf:"min_version"` + SourceDir string `mapstructure:"source_dir" koanf:"source_dir"` + SourceDirLocal string `mapstructure:"source_dir_local" koanf:"source_dir_local"` Rc string `mapstructure:"rc,omitempty"` - SkipOutput interface{} `mapstructure:"skip_output,omitempty"` + SkipOutput interface{} `mapstructure:"skip_output,omitempty" koanf:"skip_output"` Output interface{} `mapstructure:"output,omitempty"` Extends []string `mapstructure:"extends,omitempty"` - NoTTY bool `mapstructure:"no_tty,omitempty"` - AssertLefthookInstalled bool `mapstructure:"assert_lefthook_installed,omitempty"` + NoTTY bool `mapstructure:"no_tty,omitempty" koanf:"no_tty"` + AssertLefthookInstalled bool `mapstructure:"assert_lefthook_installed,omitempty" koanf:"assert_lefthook_installed"` Colors interface{} `mapstructure:"colors,omitempty"` SkipLFS bool `mapstructure:"skip_lfs,omitempty"` diff --git a/internal/config/hook.go b/internal/config/hook.go index a302bc02..319b1123 100644 --- a/internal/config/hook.go +++ b/internal/config/hook.go @@ -2,10 +2,6 @@ package config import ( "errors" - "os" - "strings" - - "github.com/knadh/koanf/v2" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" @@ -30,7 +26,7 @@ type Hook struct { Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` Piped bool `json:"piped,omitempty" mapstructure:"piped" toml:"piped,omitempty" yaml:",omitempty"` Follow bool `json:"follow,omitempty" mapstructure:"follow" toml:"follow,omitempty" yaml:",omitempty"` - ExcludeTags []string `json:"exclude_tags,omitempty" mapstructure:"exclude_tags" toml:"exclude_tags,omitempty" yaml:"exclude_tags,omitempty"` + ExcludeTags []string `json:"exclude_tags,omitempty" mapstructure:"exclude_tags" toml:"exclude_tags,omitempty" yaml:"exclude_tags,omitempty" koanf:"exclude_tags"` Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` } @@ -47,42 +43,3 @@ func (h *Hook) DoSkip(state func() git.State) bool { skipChecker := NewSkipChecker(system.Cmd) return skipChecker.check(state, h.Skip, h.Only) } - -func unmarshalHooks(base, extra *koanf.Koanf) (*Hook, error) { - if base == nil && extra == nil { - return nil, nil - } - - commands, err := mergeCommands(base, extra) - if err != nil { - return nil, err - } - - scripts, err := mergeScripts(base, extra) - if err != nil { - return nil, err - } - - hook := Hook{ - Commands: commands, - Scripts: scripts, - } - - if base == nil { - base = extra - } else if extra != nil { - if err = base.Merge(extra); err != nil { - return nil, err - } - } - - if err := base.Unmarshal("", &hook); err != nil { - return nil, err - } - - if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { - hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) - } - - return &hook, nil -} diff --git a/internal/config/load.go b/internal/config/load.go index 9f596bb7..ac95c11f 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -1,11 +1,13 @@ -// TODO rewrite using Koanf package config import ( "errors" "fmt" + "io/fs" + "os" "path/filepath" "regexp" + "strings" "github.com/knadh/koanf/parsers/json" "github.com/knadh/koanf/parsers/toml/v2" @@ -13,7 +15,6 @@ import ( kfs "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" @@ -43,13 +44,13 @@ var ( } ) -// NotFoundError wraps viper.ConfigFileNotFoundError for lefthook. -type NotFoundError struct { +// ConfigNotFoundError. +type ConfigNotFoundError struct { message string } // Error returns message of viper.ConfigFileNotFoundError. -func (err NotFoundError) Error() string { +func (err ConfigNotFoundError) Error() string { return err.message } @@ -61,7 +62,7 @@ func loadOne(k *koanf.Koanf, filesystem afero.Fs, root string, names []string) e continue } - if err := k.Load(kfs.Provider(afero.NewIOFS(filesystem), config), parsers[extension]); err != nil { + if err := k.Load(kfs.Provider(newIOFS(filesystem), config), parsers[extension]); err != nil { return err } @@ -69,7 +70,7 @@ func loadOne(k *koanf.Koanf, filesystem afero.Fs, root string, names []string) e } } - return NotFoundError{fmt.Sprintf("No config files with names %q have been found in \"%s\"", names, root)} + return ConfigNotFoundError{fmt.Sprintf("No config files with names %q have been found in \"%s\"", names, root)} } // Loads configs from the given directory with extensions. @@ -113,8 +114,8 @@ func Load(filesystem afero.Fs, repo *git.Repository) (*Config, error) { // Load optional local config (e.g. lefthook-local.yml) if err := loadOne(secondary, filesystem, repo.RootPath, localConfigNames); err != nil { - var notFoundErr NotFoundError - if ok := errors.As(err, ¬FoundErr); !ok { + var configNotFoundErr ConfigNotFoundError + if ok := errors.As(err, &configNotFoundErr); !ok { return nil, err } } @@ -124,63 +125,15 @@ func Load(filesystem afero.Fs, repo *git.Repository) (*Config, error) { config.SourceDir = DefaultSourceDir config.SourceDirLocal = DefaultSourceDirLocal - // TODO: continue here, merge rules must be applied if err := unmarshalConfigs(main, secondary, &config); err != nil { return nil, err } log.SetColors(config.Colors) + return &config, nil } -// func read(fs afero.Fs, name, path string) (*viper.Viper, error) { -// v := newViper(fs, path) -// v.SetConfigName(name) - -// if err := v.ReadInConfig(); err != nil { -// return nil, err -// } - -// return v, 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 -// } - -// loadSecondary reads extends, remotes and local config. -// - files from `extends` -// - files from `remotes` -// - lefthook-local/.lefthook-local. -//func loadSecondary(k *koanf.Koanf, fs afero.Fs, repo *git.Repository, extends []string, remotes []*Remote) (*viper.Viper, error) { -// //nolint:nestif -// if err := mergeLocal(secondary); err == nil { -// // Local extends need to be re-applied only if they have different settings -// localExtends := secondary.GetStringSlice("extends") -// if !slices.Equal(extends, localExtends) { -// if err = extend(fs, repo.RootPath, secondary, localExtends); err != nil { -// return nil, err -// } -// } -// } else { -// var notFoundErr viper.ConfigFileNotFoundError -// if ok := errors.As(err, ¬FoundErr); !ok { -// return nil, err -// } -// } - -// return secondary, nil -//} - // loadRemotes merges remote configs to the current one. func loadRemotes(k *koanf.Koanf, filesystem afero.Fs, repo *git.Repository, remotes []*Remote) error { for _, remote := range remotes { @@ -213,11 +166,11 @@ func loadRemotes(k *koanf.Koanf, filesystem afero.Fs, repo *git.Repository, remo panic("TODO: unknown extension to parse") } - if err := k.Load(kfs.Provider(afero.NewIOFS(filesystem), configPath), parser); err != nil { + if err := k.Load(kfs.Provider(newIOFS(filesystem), configPath), parser); err != nil { return err } - extends := k.Slices("extends") + extends := k.Strings("extends") if err := extend(k, filesystem, filepath.Dir(configPath), extends); err != nil { return err } @@ -261,7 +214,7 @@ func extendRecursive(k *koanf.Koanf, filesystem afero.Fs, root string, extends [ if !ok { panic("TODO: unknown extension for extent " + path) } - if err := extent.Load(kfs.Provider(afero.NewIOFS(filesystem), path), parser); err != nil { + if err := extent.Load(kfs.Provider(newIOFS(filesystem), path), parser); err != nil { return err } @@ -278,35 +231,15 @@ func extendRecursive(k *koanf.Koanf, filesystem afero.Fs, root string, extends [ return nil } -// // merge merges the configuration using viper builtin MergeInConfig. -// func merge(v *viper.Viper, name, path string) error { -// v.SetConfigName(name) -// v.SetConfigFile(path) -// return v.MergeInConfig() -// } - -func loadLocal(k *koanf.Koanf) error { - for _, name := range localConfigNames { - if err := merge(v, name, ""); err != nil { - var notFoundErr viper.ConfigFileNotFoundError - if ok := errors.As(err, ¬FoundErr); ok { - continue - } - - return err - } - - break - } - - return nil -} - -func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { +func unmarshalConfigs(main, secondary *koanf.Koanf, c *Config) error { c.Hooks = make(map[string]*Hook) for hookName := range AvailableHooks { - if err := addHook(hookName, base, extra, c); err != nil { + if !main.Exists(hookName) && !secondary.Exists(hookName) { + continue + } + + if err := addHook(hookName, main, secondary, c); err != nil { return err } } @@ -314,7 +247,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(main.Keys(), secondary.Keys()...) { if !hookKeyRegexp.MatchString(maybeHook) { continue } @@ -325,17 +258,17 @@ func unmarshalConfigs(base, extra *viper.Viper, c *Config) error { continue } - if err := addHook(hookName, base, extra, c); err != nil { + if err := addHook(hookName, main, secondary, c); err != nil { return err } } // Merge config and unmarshal it - if err := base.MergeConfigMap(extra.AllSettings()); err != nil { + if err := main.Merge(secondary); err != nil { return err } - if err := base.Unmarshal(c); err != nil { + if err := main.Unmarshal("", c); err != nil { return err } @@ -359,20 +292,90 @@ 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(name string, main, secondary *koanf.Koanf, c *Config) error { + mainHook := main.Cut(name) + overrideHook := secondary.Cut(name) - resultHook, err := unmarshalHooks(baseHook, extraHook) - if err != nil { + if err := mainHook.Merge(overrideHook); err != nil { return err } - if resultHook == nil { - return nil + var hook Hook + if err := mainHook.Unmarshal("", &hook); err != nil { + return err } - c.Hooks[hookName] = resultHook - + c.Hooks[name] = &hook return nil + + // resultHook, err := unmarshalHook(mainHook, overrideHook) + // if err != nil { + // return err + // } + + // if resultHook == nil { + // return nil + // } + + // c.Hooks[hookName] = resultHook + + // return nil +} + +func unmarshalHook(main, override *koanf.Koanf) (*Hook, error) { + if main == nil && override == nil { + return nil, nil + } + + commands, err := mergeCommands(main, override) + if err != nil { + return nil, err + } + + scripts, err := mergeScripts(main, override) + if err != nil { + return nil, err + } + + hook := Hook{ + Commands: commands, + Scripts: scripts, + } + + if main == nil { + main = override + } else if override != nil { + if err = main.Merge(override); err != nil { + return nil, err + } + } + + if err := main.Unmarshal("", &hook); err != nil { + return nil, err + } + + if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { + hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) + } + + return &hook, nil +} + +// Rewritten afero.NewIOFS to support opening paths starting with '/'. + +type iofs struct { + fs afero.Fs +} + +func newIOFS(filesystem afero.Fs) iofs { + return iofs{filesystem} +} + +func (iofs iofs) Open(name string) (fs.File, error) { + file, err := iofs.fs.Open(name) + if err != nil { + return nil, fmt.Errorf("open failed: %s: %w", name, err) + } + + return file, nil } diff --git a/internal/config/remote.go b/internal/config/remote.go index 3cb29637..d7ea3591 100644 --- a/internal/config/remote.go +++ b/internal/config/remote.go @@ -1,13 +1,13 @@ package config type Remote struct { - GitURL string `json:"git_url,omitempty" mapstructure:"git_url" toml:"git_url" yaml:"git_url"` + GitURL string `json:"git_url,omitempty" mapstructure:"git_url" toml:"git_url" yaml:"git_url" koanf:"git_url"` Ref string `json:"ref,omitempty" mapstructure:"ref,omitempty" toml:"ref,omitempty" yaml:",omitempty"` // Deprecated Config string `json:"config,omitempty" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"` Configs []string `json:"configs,omitempty" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"` Refetch bool `json:"refetch,omitempty" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"` - RefetchFrequency string `json:"refetch_frequency,omitempty" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty"` + RefetchFrequency string `json:"refetch_frequency,omitempty" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty" koanf:"refetch_frequency"` } func (r *Remote) Configured() bool { diff --git a/internal/lefthook/run.go b/internal/lefthook/run.go index 3b1307c2..ff9fc129 100644 --- a/internal/lefthook/run.go +++ b/internal/lefthook/run.go @@ -55,7 +55,7 @@ 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 errNotFound config.ErrNotFound + var errNotFound config.ConfigNotFoundError if ok := errors.As(err, &errNotFound); ok { log.Warn(err.Error()) return nil From 90968a7ae6467b5d924a056db47d4571db7072a6 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Wed, 20 Nov 2024 11:33:08 +0300 Subject: [PATCH 05/14] fix: load with koanf (no complicated load yet) --- internal/config/command.go | 6 +++--- internal/config/config.go | 12 ++++++------ internal/config/hook.go | 14 +++++++------- internal/config/remote.go | 12 ++++++------ 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/internal/config/command.go b/internal/config/command.go index 5fe5085a..6693c435 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -21,17 +21,17 @@ type Command struct { Tags []string `json:"tags,omitempty" mapstructure:"tags" toml:"tags,omitempty" yaml:",omitempty"` Env map[string]string `json:"env,omitempty" mapstructure:"env" toml:"env,omitempty" yaml:",omitempty"` - FileTypes []string `json:"file_types,omitempty" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty" koanf:"file_types"` + FileTypes []string `json:"file_types,omitempty" koanf:"file_types" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty"` Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` Exclude interface{} `json:"exclude,omitempty" mapstructure:"exclude" toml:"exclude,omitempty" yaml:",omitempty"` Priority int `json:"priority,omitempty" mapstructure:"priority" toml:"priority,omitempty" yaml:",omitempty"` - FailText string `json:"fail_text,omitempty" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty" koanf:"fail_text"` + FailText string `json:"fail_text,omitempty" koanf:"fail_text" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty"` Interactive bool `json:"interactive,omitempty" mapstructure:"interactive" toml:"interactive,omitempty" yaml:",omitempty"` UseStdin bool `json:"use_stdin,omitempty" mapstructure:"use_stdin" toml:"use_stdin,omitempty" yaml:",omitempty"` - StageFixed bool `json:"stage_fixed,omitempty" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty" koanf:"stage_fixed"` + StageFixed bool `json:"stage_fixed,omitempty" koanf:"stage_fixed" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` } type commandRunReplace struct { diff --git a/internal/config/config.go b/internal/config/config.go index 286cff6c..7aa1dc38 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -27,15 +27,15 @@ const ( ) type Config struct { - MinVersion string `mapstructure:"min_version,omitempty" koanf:"min_version"` - SourceDir string `mapstructure:"source_dir" koanf:"source_dir"` - SourceDirLocal string `mapstructure:"source_dir_local" koanf:"source_dir_local"` + MinVersion string `koanf:"min_version" mapstructure:"min_version,omitempty"` + SourceDir string `koanf:"source_dir" mapstructure:"source_dir"` + SourceDirLocal string `koanf:"source_dir_local" mapstructure:"source_dir_local"` Rc string `mapstructure:"rc,omitempty"` - SkipOutput interface{} `mapstructure:"skip_output,omitempty" koanf:"skip_output"` + SkipOutput interface{} `koanf:"skip_output" mapstructure:"skip_output,omitempty"` Output interface{} `mapstructure:"output,omitempty"` Extends []string `mapstructure:"extends,omitempty"` - NoTTY bool `mapstructure:"no_tty,omitempty" koanf:"no_tty"` - AssertLefthookInstalled bool `mapstructure:"assert_lefthook_installed,omitempty" koanf:"assert_lefthook_installed"` + NoTTY bool `koanf:"no_tty" mapstructure:"no_tty,omitempty"` + AssertLefthookInstalled bool `koanf:"assert_lefthook_installed" mapstructure:"assert_lefthook_installed,omitempty"` Colors interface{} `mapstructure:"colors,omitempty"` SkipLFS bool `mapstructure:"skip_lfs,omitempty"` diff --git a/internal/config/hook.go b/internal/config/hook.go index 319b1123..18f98046 100644 --- a/internal/config/hook.go +++ b/internal/config/hook.go @@ -22,13 +22,13 @@ type Hook struct { // Unmarshalling it manually, so omit auto unmarshalling Scripts map[string]*Script `json:"scripts,omitempty" mapstructure:"-" toml:"scripts,omitempty" yaml:",omitempty"` - Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` - Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` - Piped bool `json:"piped,omitempty" mapstructure:"piped" toml:"piped,omitempty" yaml:",omitempty"` - Follow bool `json:"follow,omitempty" mapstructure:"follow" toml:"follow,omitempty" yaml:",omitempty"` - ExcludeTags []string `json:"exclude_tags,omitempty" mapstructure:"exclude_tags" toml:"exclude_tags,omitempty" yaml:"exclude_tags,omitempty" koanf:"exclude_tags"` - Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` - Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` + Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` + Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` + Piped bool `json:"piped,omitempty" mapstructure:"piped" toml:"piped,omitempty" yaml:",omitempty"` + Follow bool `json:"follow,omitempty" mapstructure:"follow" toml:"follow,omitempty" yaml:",omitempty"` + ExcludeTags []string `json:"exclude_tags,omitempty" koanf:"exclude_tags" mapstructure:"exclude_tags" toml:"exclude_tags,omitempty" yaml:"exclude_tags,omitempty"` + Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` + Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` } func (h *Hook) Validate() error { diff --git a/internal/config/remote.go b/internal/config/remote.go index d7ea3591..15f1ca93 100644 --- a/internal/config/remote.go +++ b/internal/config/remote.go @@ -1,13 +1,13 @@ package config type Remote struct { - GitURL string `json:"git_url,omitempty" mapstructure:"git_url" toml:"git_url" yaml:"git_url" koanf:"git_url"` - Ref string `json:"ref,omitempty" mapstructure:"ref,omitempty" toml:"ref,omitempty" yaml:",omitempty"` + GitURL string `json:"git_url,omitempty" koanf:"git_url" mapstructure:"git_url" toml:"git_url" yaml:"git_url"` + Ref string `json:"ref,omitempty" mapstructure:"ref,omitempty" toml:"ref,omitempty" yaml:",omitempty"` // Deprecated - Config string `json:"config,omitempty" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"` - Configs []string `json:"configs,omitempty" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"` - Refetch bool `json:"refetch,omitempty" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"` - RefetchFrequency string `json:"refetch_frequency,omitempty" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty" koanf:"refetch_frequency"` + Config string `json:"config,omitempty" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"` + Configs []string `json:"configs,omitempty" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"` + Refetch bool `json:"refetch,omitempty" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"` + RefetchFrequency string `json:"refetch_frequency,omitempty" koanf:"refetch_frequency" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty"` } func (r *Remote) Configured() bool { From 551b2c8f08ecce1d8ec7ca6e7ce46a6ff52a2ed1 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 2 Dec 2024 10:14:42 +0300 Subject: [PATCH 06/14] fix: add kaonf provider --- internal/config/load.go | 99 ++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/internal/config/load.go b/internal/config/load.go index ac95c11f..7582f67d 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -4,10 +4,8 @@ import ( "errors" "fmt" "io/fs" - "os" "path/filepath" "regexp" - "strings" "github.com/knadh/koanf/parsers/json" "github.com/knadh/koanf/parsers/toml/v2" @@ -296,9 +294,16 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { mainHook := main.Cut(name) overrideHook := secondary.Cut(name) - if err := mainHook.Merge(overrideHook); err != nil { + options := koanf.WithMergeFunc(func(src, dest map[string]interface{}) error { + return nil + }) + + if err := mainHook.Load(koanfProvider{overrideHook}, nil, options); err != nil { return err } + // if err := mainHook.Merge(overrideHook); err != nil { + // return err + // } var hook Hook if err := mainHook.Unmarshal("", &hook); err != nil { @@ -322,44 +327,44 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { // return nil } -func unmarshalHook(main, override *koanf.Koanf) (*Hook, error) { - if main == nil && override == nil { - return nil, nil - } - - commands, err := mergeCommands(main, override) - if err != nil { - return nil, err - } - - scripts, err := mergeScripts(main, override) - if err != nil { - return nil, err - } - - hook := Hook{ - Commands: commands, - Scripts: scripts, - } - - if main == nil { - main = override - } else if override != nil { - if err = main.Merge(override); err != nil { - return nil, err - } - } - - if err := main.Unmarshal("", &hook); err != nil { - return nil, err - } - - if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { - hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) - } - - return &hook, nil -} +// func unmarshalHook(main, override *koanf.Koanf) (*Hook, error) { +// if main == nil && override == nil { +// return nil, nil +// } + +// commands, err := mergeCommands(main, override) +// if err != nil { +// return nil, err +// } + +// scripts, err := mergeScripts(main, override) +// if err != nil { +// return nil, err +// } + +// hook := Hook{ +// Commands: commands, +// Scripts: scripts, +// } + +// if main == nil { +// main = override +// } else if override != nil { +// if err = main.Merge(override); err != nil { +// return nil, err +// } +// } + +// if err := main.Unmarshal("", &hook); err != nil { +// return nil, err +// } + +// if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { +// hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) +// } + +// return &hook, nil +// } // Rewritten afero.NewIOFS to support opening paths starting with '/'. @@ -379,3 +384,15 @@ func (iofs iofs) Open(name string) (fs.File, error) { return file, nil } + +type koanfProvider struct { + k *koanf.Koanf +} + +func (k koanfProvider) Read() (map[string]interface{}, error) { + return k.k.Raw(), nil +} + +func (k koanfProvider) ReadBytes() ([]byte, error) { + panic("not implemented") +} From 7750ad1508f6a7f18846045024fa170f0db23782 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 2 Dec 2024 11:19:42 +0300 Subject: [PATCH 07/14] fix: merge local extends too --- internal/config/load.go | 37 ++++++++++++++++++------------------ internal/config/load_test.go | 8 ++++---- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/internal/config/load.go b/internal/config/load.go index 7582f67d..cf4e6404 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -4,9 +4,13 @@ import ( "errors" "fmt" "io/fs" + "os" "path/filepath" "regexp" + "slices" + "strings" + "github.com/knadh/koanf/maps" "github.com/knadh/koanf/parsers/json" "github.com/knadh/koanf/parsers/toml/v2" "github.com/knadh/koanf/parsers/yaml" @@ -118,6 +122,14 @@ func Load(filesystem afero.Fs, repo *git.Repository) (*Config, error) { } } + // Load local `extends` + localExtends := secondary.Strings("extends") + if len(localExtends) > 0 && !slices.Equal(extends, localExtends) { + if err := extend(secondary, filesystem, repo.RootPath, localExtends); err != nil { + return nil, err + } + } + var config Config config.SourceDir = DefaultSourceDir @@ -295,36 +307,25 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { overrideHook := secondary.Cut(name) options := koanf.WithMergeFunc(func(src, dest map[string]interface{}) error { + // TODO: merge dest to src replacing {cmd} properly + maps.Merge(src, dest) return nil }) if err := mainHook.Load(koanfProvider{overrideHook}, nil, options); err != nil { return err } - // if err := mainHook.Merge(overrideHook); err != nil { - // return err - // } - var hook Hook if err := mainHook.Unmarshal("", &hook); err != nil { return err } + if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { + hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) + } + c.Hooks[name] = &hook return nil - - // resultHook, err := unmarshalHook(mainHook, overrideHook) - // if err != nil { - // return err - // } - - // if resultHook == nil { - // return nil - // } - - // c.Hooks[hookName] = resultHook - - // return nil } // func unmarshalHook(main, override *koanf.Koanf) (*Hook, error) { @@ -366,7 +367,7 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { // return &hook, nil // } -// Rewritten afero.NewIOFS to support opening paths starting with '/'. +// Rewritten from afero.NewIOFS to support opening paths starting with '/'. type iofs struct { fs afero.Fs diff --git a/internal/config/load_test.go b/internal/config/load_test.go index 30705d6e..99005845 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -229,13 +229,13 @@ pre-push: ".lefthook.yml": ` pre-push: scripts: - "global-extend": + "global-extend.sh": runner: bash `, ".lefthook-local.yml": ` pre-push: scripts: - "local-extend": + "local-extend.sh": runner: bash `, }, @@ -246,10 +246,10 @@ pre-push: Hooks: map[string]*Hook{ "pre-push": { Scripts: map[string]*Script{ - "global-extend": { + "global-extend.sh": { Runner: "bash", }, - "local-extend": { + "local-extend.sh": { Runner: "bash", }, }, From 1ad2903102af575560ce5c8a79b1ad1753fa675c Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 2 Dec 2024 11:50:34 +0300 Subject: [PATCH 08/14] fix: add special handling for {cmd} --- internal/config/command.go | 73 -------------------------------- internal/config/load.go | 80 +++++++++++++++++------------------ internal/config/script.go | 85 ++------------------------------------ 3 files changed, 43 insertions(+), 195 deletions(-) diff --git a/internal/config/command.go b/internal/config/command.go index 6693c435..4f51dd5c 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -2,9 +2,6 @@ package config import ( "errors" - "strings" - - "github.com/knadh/koanf/v2" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" @@ -34,10 +31,6 @@ type Command struct { StageFixed bool `json:"stage_fixed,omitempty" koanf:"stage_fixed" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` } -type commandRunReplace struct { - Run string `mapstructure:"run"` -} - func (c Command) Validate() error { if !isRunnerFilesCompatible(c.Run) { return errFilesIncompatible @@ -54,69 +47,3 @@ func (c Command) DoSkip(state func() git.State) bool { func (c Command) ExecutionPriority() int { return c.Priority } - -func mergeCommands(base, extra *koanf.Koanf) (map[string]*Command, error) { - if base == nil && extra == nil { - return nil, nil - } - - if base == nil { - return unmarshalCommands(extra.Cut("commands")) - } - - if extra == nil { - return unmarshalCommands(base.Cut("commands")) - } - - commandsOrigin := base.Cut("commands") - commandsOverride := extra.Cut("commands") - if commandsOrigin == nil { - return unmarshalCommands(commandsOverride) - } - if commandsOverride == nil { - return unmarshalCommands(commandsOrigin) - } - - runReplaces := make(map[string]*commandRunReplace) - for key := range commandsOrigin.Raw() { - var replace commandRunReplace - - substructure := commandsOrigin.Cut(key) - if substructure == nil { - continue - } - - if err := substructure.Unmarshal("", &replace); err != nil { - return nil, err - } - - runReplaces[key] = &replace - } - - err := commandsOrigin.Merge(commandsOverride) - if err != nil { - return nil, err - } - - commands, err := unmarshalCommands(commandsOrigin) - if err != nil { - return nil, err - } - - for key, replace := range runReplaces { - if replace.Run != "" { - commands[key].Run = strings.ReplaceAll(commands[key].Run, CMD, replace.Run) - } - } - - return commands, nil -} - -func unmarshalCommands(k *koanf.Koanf) (map[string]*Command, error) { - commands := make(map[string]*Command) - if err := k.Unmarshal("", &commands); err != nil { - return nil, err - } - - return commands, nil -} diff --git a/internal/config/load.go b/internal/config/load.go index cf4e6404..1196d216 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -306,9 +306,48 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { mainHook := main.Cut(name) overrideHook := secondary.Cut(name) + // Special merge func to support merging {cmd} templates options := koanf.WithMergeFunc(func(src, dest map[string]interface{}) error { - // TODO: merge dest to src replacing {cmd} properly + // perf: allocate with len of src commands here + destCommands := make(map[string]string) + + switch commands := dest["commands"].(type) { + case map[string]interface{}: + for cmdName, command := range commands { + switch cmd := command.(type) { + case map[string]interface{}: + switch run := cmd["run"].(type) { + case string: + destCommands[cmdName] = run + default: + } + default: + } + } + default: + } + maps.Merge(src, dest) + + if len(destCommands) > 0 { + switch commands := dest["commands"].(type) { + case map[string]interface{}: + for cmdName, command := range commands { + switch cmd := command.(type) { + case map[string]interface{}: + switch run := cmd["run"].(type) { + case string: + newRun := strings.ReplaceAll(run, CMD, destCommands[cmdName]) + command.(map[string]interface{})["run"] = newRun + default: + } + default: + } + } + default: + } + } + return nil }) @@ -328,45 +367,6 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { return nil } -// func unmarshalHook(main, override *koanf.Koanf) (*Hook, error) { -// if main == nil && override == nil { -// return nil, nil -// } - -// commands, err := mergeCommands(main, override) -// if err != nil { -// return nil, err -// } - -// scripts, err := mergeScripts(main, override) -// if err != nil { -// return nil, err -// } - -// hook := Hook{ -// Commands: commands, -// Scripts: scripts, -// } - -// if main == nil { -// main = override -// } else if override != nil { -// if err = main.Merge(override); err != nil { -// return nil, err -// } -// } - -// if err := main.Unmarshal("", &hook); err != nil { -// return nil, err -// } - -// if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { -// hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) -// } - -// return &hook, nil -// } - // Rewritten from afero.NewIOFS to support opening paths starting with '/'. type iofs struct { diff --git a/internal/config/script.go b/internal/config/script.go index ec162ba3..cf7d90ea 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -1,11 +1,6 @@ package config import ( - "strings" - - "github.com/knadh/koanf/v2" - "github.com/mitchellh/mapstructure" - "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" ) @@ -25,10 +20,6 @@ type Script struct { StageFixed bool `json:"stage_fixed,omitempty" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` } -type scriptRunnerReplace struct { - Runner string `mapstructure:"runner"` -} - func (s Script) DoSkip(state func() git.State) bool { skipChecker := NewSkipChecker(system.Cmd) return skipChecker.check(state, s.Skip, s.Only) @@ -38,76 +29,6 @@ func (s Script) ExecutionPriority() int { return s.Priority } -func mergeScripts(base, extra *koanf.Koanf) (map[string]*Script, error) { - if base == nil && extra == nil { - return nil, nil - } - - if base == nil { - return unmarshalScripts(extra.Cut("scripts").Raw()) - } - - if extra == nil { - return unmarshalScripts(base.Cut("scripts").Raw()) - } - - scriptsOrigin := base.Cut("scripts").Raw() - scriptsOverride := extra.Cut("scripts").Raw() - if scriptsOrigin == nil { - return unmarshalScripts(scriptsOverride) - } - if scriptsOverride == nil { - return unmarshalScripts(scriptsOrigin) - } - - runReplaces := make(map[string]*scriptRunnerReplace) - for key, originConfig := range scriptsOrigin { - var runReplace scriptRunnerReplace - - if err := unmarshal(originConfig, &runReplace); err != nil { - return nil, err - } - - runReplaces[key] = &runReplace - } - - if err := base.Set("scripts", scriptsOverride); err != nil { - return nil, err - } - - scripts, err := unmarshalScripts(base.Cut("scripts").Raw()) - if err != nil { - return nil, err - } - - for key, replace := range runReplaces { - if replace.Runner != "" { - scripts[key].Runner = strings.ReplaceAll(scripts[key].Runner, CMD, replace.Runner) - } - } - - return scripts, nil -} - -func unmarshalScripts(s map[string]interface{}) (map[string]*Script, error) { - if len(s) == 0 { - return nil, nil - } - - scripts := make(map[string]*Script) - for name, scriptConfig := range s { - var script Script - - if err := unmarshal(scriptConfig, &script); err != nil { - return nil, err - } - - scripts[name] = &script - } - - return scripts, nil -} - // `scripts` are unmarshalled manually because viper // uses "." as a key delimiter. So, this definition: // @@ -132,6 +53,6 @@ func unmarshalScripts(s map[string]interface{}) (map[string]*Script, error) { // // This is not an expected behavior and cannot be controlled yet // Working with GetStringMap is the only way to get the structure "as is". -func unmarshal(input, output interface{}) error { - return mapstructure.WeakDecode(input, &output) -} +// func unmarshal(input, output interface{}) error { +// return mapstructure.WeakDecode(input, &output) +// } From db6b62ef76669b69c92f31cce98c98d7ef7ddb85 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 2 Dec 2024 11:56:38 +0300 Subject: [PATCH 09/14] perf: allocate destCommands when we know the len of commands --- internal/config/load.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/config/load.go b/internal/config/load.go index 1196d216..585502c9 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -308,11 +308,11 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { // Special merge func to support merging {cmd} templates options := koanf.WithMergeFunc(func(src, dest map[string]interface{}) error { - // perf: allocate with len of src commands here - destCommands := make(map[string]string) + var destCommands map[string]string switch commands := dest["commands"].(type) { case map[string]interface{}: + destCommands = make(map[string]string, len(commands)) for cmdName, command := range commands { switch cmd := command.(type) { case map[string]interface{}: From 76649a3484d6201221aa0ab0cebe0e067b6ccda2 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 2 Dec 2024 12:03:05 +0300 Subject: [PATCH 10/14] fix: remove ToUpper for envs, since koanf respects the registry --- internal/lefthook/runner/exec/execute_unix.go | 3 +-- internal/lefthook/runner/exec/execute_windows.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/lefthook/runner/exec/execute_unix.go b/internal/lefthook/runner/exec/execute_unix.go index 45efaab0..32287977 100644 --- a/internal/lefthook/runner/exec/execute_unix.go +++ b/internal/lefthook/runner/exec/execute_unix.go @@ -10,7 +10,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "github.com/creack/pty" "github.com/mattn/go-isatty" @@ -44,7 +43,7 @@ func (e CommandExecutor) Execute(ctx context.Context, opts Options, in io.Reader for name, value := range opts.Env { envs = append( envs, - fmt.Sprintf("%s=%s", strings.ToUpper(name), os.ExpandEnv(value)), + fmt.Sprintf("%s=%s", name, os.ExpandEnv(value)), ) } switch log.Colors() { diff --git a/internal/lefthook/runner/exec/execute_windows.go b/internal/lefthook/runner/exec/execute_windows.go index 47d72757..ce69cbf8 100644 --- a/internal/lefthook/runner/exec/execute_windows.go +++ b/internal/lefthook/runner/exec/execute_windows.go @@ -40,7 +40,7 @@ func (e CommandExecutor) Execute(ctx context.Context, opts Options, in io.Reader for name, value := range opts.Env { envs = append( envs, - fmt.Sprintf("%s=%s", strings.ToUpper(name), os.ExpandEnv(value)), + fmt.Sprintf("%s=%s", name, os.ExpandEnv(value)), ) } switch log.Colors() { From e033f2cbfb96b911511acca36279609aa4befe7b Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 3 Dec 2024 10:44:51 +0300 Subject: [PATCH 11/14] chore: go mod tidy --- go.mod | 17 +++-------------- go.sum | 38 +++----------------------------------- 2 files changed, 6 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index ae46067d..21931c2e 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 github.com/creack/pty v1.1.24 github.com/gobwas/glob v0.2.3 + github.com/knadh/koanf/maps v0.1.1 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 @@ -21,7 +22,6 @@ require ( github.com/schollz/progressbar/v3 v3.17.1 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 ) @@ -30,25 +30,14 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/x/ansi v0.4.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/knadh/koanf/maps v0.1.1 // indirect - github.com/magiconair/properties v1.8.7 // 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 - github.com/spf13/cast v1.6.0 // indirect - github.com/subosito/gotenv v1.6.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.22.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) require ( diff --git a/go.sum b/go.sum index 04f8cd5f..016b2c8d 100644 --- a/go.sum +++ b/go.sum @@ -13,26 +13,17 @@ github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoC github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM= github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY= 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.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 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/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= @@ -53,8 +44,6 @@ 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= @@ -76,45 +65,26 @@ 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 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.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U= github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4= -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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= @@ -130,7 +100,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= From 4e854c3cf110d78f7e957b6adfe5c5cae2cf5a9b Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 3 Dec 2024 10:48:14 +0300 Subject: [PATCH 12/14] chore: remove any reference to viper --- internal/config/hook.go | 9 +-------- internal/config/load.go | 1 - internal/config/script.go | 28 ---------------------------- 3 files changed, 1 insertion(+), 37 deletions(-) diff --git a/internal/config/hook.go b/internal/config/hook.go index 18f98046..15278f62 100644 --- a/internal/config/hook.go +++ b/internal/config/hook.go @@ -12,15 +12,8 @@ const CMD = "{cmd}" var errPipedAndParallelSet = errors.New("conflicting options 'piped' and 'parallel' are set to 'true', remove one of this option from hook group") type Hook struct { - // Should be unmarshalled with `mapstructure:"commands"` - // But replacing '{cmd}' is still an issue - // Unmarshalling it manually, so omit auto unmarshalling Commands map[string]*Command `json:"commands,omitempty" mapstructure:"-" toml:"commands,omitempty" yaml:",omitempty"` - - // Should be unmarshalled with `mapstructure:"scripts"` - // But parsing keys with dots in it is still an issue: https://github.com/spf13/viper/issues/324 - // Unmarshalling it manually, so omit auto unmarshalling - Scripts map[string]*Script `json:"scripts,omitempty" mapstructure:"-" toml:"scripts,omitempty" yaml:",omitempty"` + Scripts map[string]*Script `json:"scripts,omitempty" mapstructure:"-" toml:"scripts,omitempty" yaml:",omitempty"` Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` diff --git a/internal/config/load.go b/internal/config/load.go index 585502c9..7946f0ba 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -51,7 +51,6 @@ type ConfigNotFoundError struct { message string } -// Error returns message of viper.ConfigFileNotFoundError. func (err ConfigNotFoundError) Error() string { return err.message } diff --git a/internal/config/script.go b/internal/config/script.go index cf7d90ea..df3e71f0 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -28,31 +28,3 @@ func (s Script) DoSkip(state func() git.State) bool { func (s Script) ExecutionPriority() int { return s.Priority } - -// `scripts` are unmarshalled manually because viper -// uses "." as a key delimiter. So, this definition: -// -// ```yaml -// scripts: -// -// "example.sh": -// runner: bash -// -// ``` -// -// Unmarshals into this: -// -// ```yaml -// scripts: -// -// example: -// sh: -// runner: bash -// -// ``` -// -// This is not an expected behavior and cannot be controlled yet -// Working with GetStringMap is the only way to get the structure "as is". -// func unmarshal(input, output interface{}) error { -// return mapstructure.WeakDecode(input, &output) -// } From e4a4d96b463b9e0ac6c48ecc8456c0fe38032fdf Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 3 Dec 2024 10:55:42 +0300 Subject: [PATCH 13/14] docs: fix typo --- docs/configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 84eae07a..69d16f79 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -581,13 +581,13 @@ pre-commit: exclude_tags: frontend commands: lint: - tag: frontend + tags: frontend ... test: - tag: frontend + tags: frontend ... check-syntax: - tag: documentation + tags: documentation ``` ```bash From 0181299f5cb47d282d3c08aef5677c78aa4b080c Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 3 Dec 2024 11:02:18 +0300 Subject: [PATCH 14/14] docs: avoid comma separated tags --- docs/configuration.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 69d16f79..5a5fa833 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -604,10 +604,14 @@ This option is good to specify in `lefthook-local.yml` when you want to skip som pre-push: commands: packages-audit: - tags: frontend security + tags: + - frontend + - security run: yarn audit gems-audit: - tags: backend security + tags: + - backend + - security run: bundle audit ``` @@ -747,7 +751,9 @@ Simply run `bundle exec rubocop` on all files with `.rb` extension excluding `ap pre-commit: commands: rubocop: - tags: backend style + tags: + - backend + - style glob: "*.rb" exclude: - config/application.rb @@ -1012,10 +1018,14 @@ You can specify tags for commands and scripts. This is useful for [excluding](#e pre-commit: commands: lint: - tags: frontend,js + tags: + - frontend + - js run: yarn lint test: - tags: backend,ruby + tags: + - backend + - ruby run: bundle exec rspec ``` @@ -1069,7 +1079,9 @@ Provide a git command to list files. pre-push: commands: stylelint: - tags: frontend style + tags: + - frontend + - style files: git diff --name-only master glob: "*.js" run: yarn stylelint {files}