Import upstream version 1.10.1+git20220312.1.dc76f3c
Debian Janitor
2 years ago
17 | 17 | - label: I have searched the [issue tracker](https://www.github.com/spf13/viper/issues) for an issue that matches the one I want to file, without success. |
18 | 18 | required: true |
19 | 19 | - label: I am not looking for support or already pursued the available [support channels](https://github.com/spf13/viper/issues/new/choose) without success. |
20 | required: true | |
21 | - label: I have checked the [troubleshooting guide](https://github.com/spf13/viper/blob/master/TROUBLESHOOTING.md) for my problem, without success. | |
20 | 22 | required: true |
21 | 23 | - type: input |
22 | 24 | attributes: |
16 | 16 | matrix: |
17 | 17 | os: [ubuntu-latest, macos-latest, windows-latest] |
18 | 18 | go: ['1.14', '1.15', '1.16', '1.17'] |
19 | tags: ['', 'viper_yaml3', 'viper_toml2'] | |
19 | 20 | env: |
20 | 21 | GOFLAGS: -mod=readonly |
21 | 22 | |
26 | 27 | go-version: ${{ matrix.go }} |
27 | 28 | |
28 | 29 | - name: Checkout code |
29 | uses: actions/checkout@v2 | |
30 | uses: actions/checkout@v3 | |
30 | 31 | |
31 | 32 | - name: Test |
32 | run: go test -race -v ./... | |
33 | run: go test -race -tags '${{ matrix.tags }}' -v ./... | |
34 | if: runner.os != 'Windows' | |
35 | ||
36 | - name: Test (without race detector) | |
37 | run: go test -tags '${{ matrix.tags }}' -v ./... | |
38 | if: runner.os == 'Windows' | |
33 | 39 | |
34 | 40 | lint: |
35 | 41 | name: Lint |
44 | 50 | go-version: 1.17 |
45 | 51 | |
46 | 52 | - name: Checkout code |
47 | uses: actions/checkout@v2 | |
53 | uses: actions/checkout@v3 | |
48 | 54 | |
49 | 55 | - name: Lint |
50 | 56 | uses: golangci/golangci-lint-action@v2 |
38 | 38 | |
39 | 39 | steps: |
40 | 40 | - name: Checkout repository |
41 | uses: actions/checkout@v2 | |
41 | uses: actions/checkout@v3 | |
42 | 42 | |
43 | 43 | # Initializes the CodeQL tools for scanning. |
44 | 44 | - name: Initialize CodeQL |
5 | 5 | comment: |
6 | 6 | runs-on: ubuntu-latest |
7 | 7 | steps: |
8 | - uses: actions/github-script@v5 | |
8 | - uses: actions/github-script@v6 | |
9 | 9 | with: |
10 | 10 | github-token: ${{secrets.GITHUB_TOKEN}} |
11 | 11 | script: | |
5 | 5 | comment: |
6 | 6 | runs-on: ubuntu-latest |
7 | 7 | steps: |
8 | - uses: actions/github-script@v5 | |
8 | - uses: actions/github-script@v6 | |
9 | 9 | with: |
10 | 10 | github-token: ${{secrets.GITHUB_TOKEN}} |
11 | 11 | script: | |
19 | 19 | go-version: '1.17' |
20 | 20 | |
21 | 21 | - name: Checkout code |
22 | uses: actions/checkout@v2 | |
22 | uses: actions/checkout@v3 | |
23 | 23 | |
24 | 24 | - name: Ensure Viper compiles for WASM |
25 | 25 | run: GOOS=js GOARCH=wasm go build . |
20 | 20 | Please refer to the [wiki](https://github.com/golang/go/wiki/Modules) on how to do that. |
21 | 21 | |
22 | 22 | **tl;dr* `export GO111MODULE=on` |
23 | ||
24 | ## Unquoted 'y' and 'n' characters get replaced with _true_ and _false_ when reading a YAML file | |
25 | ||
26 | This is a YAML 1.1 feature according to [go-yaml/yaml#740](https://github.com/go-yaml/yaml/issues/740). | |
27 | ||
28 | Potential solutions are: | |
29 | ||
30 | 1. Quoting values resolved as boolean | |
31 | 1. Upgrading to YAML v3 (for the time being this is possible by passing the `viper_yaml3` tag to your build) |
0 | //go:build viper_logger | |
1 | // +build viper_logger | |
2 | ||
3 | package viper | |
4 | ||
5 | // WithLogger sets a custom logger. | |
6 | func WithLogger(l Logger) Option { | |
7 | return optionFunc(func(v *Viper) { | |
8 | v.logger = l | |
9 | }) | |
10 | } |
4 | 4 | require ( |
5 | 5 | github.com/fsnotify/fsnotify v1.5.1 |
6 | 6 | github.com/hashicorp/hcl v1.0.0 |
7 | github.com/magiconair/properties v1.8.5 | |
7 | github.com/magiconair/properties v1.8.6 | |
8 | 8 | github.com/mitchellh/mapstructure v1.4.3 |
9 | 9 | github.com/pelletier/go-toml v1.9.4 |
10 | github.com/pelletier/go-toml/v2 v2.0.0-beta.6 | |
10 | 11 | github.com/sagikazarmark/crypt v0.4.0 |
11 | github.com/spf13/afero v1.6.0 | |
12 | github.com/spf13/afero v1.8.2 | |
12 | 13 | github.com/spf13/cast v1.4.1 |
13 | 14 | github.com/spf13/jwalterweatherman v1.1.0 |
14 | 15 | github.com/spf13/pflag v1.0.5 |
15 | github.com/stretchr/testify v1.7.0 | |
16 | github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 | |
16 | 17 | github.com/subosito/gotenv v1.2.0 |
17 | gopkg.in/ini.v1 v1.66.2 | |
18 | gopkg.in/ini.v1 v1.66.4 | |
18 | 19 | gopkg.in/yaml.v2 v2.4.0 |
20 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b | |
19 | 21 | ) |
20 | 22 | |
21 | 23 | require ( |
53 | 55 | go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect |
54 | 56 | go.etcd.io/etcd/client/v2 v2.305.1 // indirect |
55 | 57 | go.opencensus.io v0.23.0 // indirect |
56 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect | |
58 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect | |
57 | 59 | golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect |
58 | 60 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect |
59 | 61 | golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect |
64 | 66 | google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa // indirect |
65 | 67 | google.golang.org/grpc v1.43.0 // indirect |
66 | 68 | google.golang.org/protobuf v1.27.1 // indirect |
67 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect | |
68 | 69 | ) |
2 | 2 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= |
3 | 3 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= |
4 | 4 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= |
5 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= | |
5 | 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= |
6 | 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= |
7 | 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= |
14 | 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= |
15 | 16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= |
16 | 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= |
18 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= | |
17 | 19 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= |
18 | 20 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= |
19 | 21 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= |
45 | 47 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= |
46 | 48 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= |
47 | 49 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= |
50 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= | |
48 | 51 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= |
49 | 52 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
50 | 53 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= |
192 | 195 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= |
193 | 196 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= |
194 | 197 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= |
198 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= | |
195 | 199 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= |
196 | 200 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= |
197 | 201 | github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= |
205 | 209 | github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= |
206 | 210 | github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= |
207 | 211 | github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= |
212 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= | |
208 | 213 | github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= |
209 | 214 | github.com/hashicorp/consul/api v1.12.0 h1:k3y1FYv6nuKyNTqj6w9gXOx5r5CfLj/k/euUeBXj1OY= |
210 | 215 | github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= |
271 | 276 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= |
272 | 277 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
273 | 278 | github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= |
274 | github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= | |
275 | github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= | |
279 | github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= | |
280 | github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= | |
276 | 281 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= |
277 | 282 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= |
278 | 283 | github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= |
312 | 317 | github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= |
313 | 318 | github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= |
314 | 319 | github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= |
320 | github.com/pelletier/go-toml/v2 v2.0.0-beta.6 h1:JFNqj2afbbhCqTiyN16D7Tudc/aaDzE2FBDk+VlBQnE= | |
321 | github.com/pelletier/go-toml/v2 v2.0.0-beta.6/go.mod h1:ke6xncR3W76Ba8xnVxkrZG0js6Rd2BsQEAYrfgJ6eQA= | |
315 | 322 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
316 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | |
317 | 323 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
324 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | |
325 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | |
318 | 326 | github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= |
327 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= | |
319 | 328 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
320 | 329 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
321 | 330 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= |
343 | 352 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= |
344 | 353 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= |
345 | 354 | github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= |
346 | github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= | |
347 | 355 | github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= |
356 | github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= | |
357 | github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= | |
348 | 358 | github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= |
349 | 359 | github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= |
350 | 360 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= |
359 | 369 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
360 | 370 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= |
361 | 371 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
362 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | |
363 | 372 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
373 | github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942 h1:t0lM6y/M5IiUZyvbBTcngso8SZEZICH7is9B6g/obVU= | |
374 | github.com/stretchr/testify v1.7.1-0.20210427113832-6241f9ab9942/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | |
364 | 375 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= |
365 | 376 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= |
366 | 377 | github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= |
395 | 406 | golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= |
396 | 407 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
397 | 408 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= |
398 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= | |
409 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | |
399 | 410 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
411 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4= | |
412 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | |
400 | 413 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
401 | 414 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |
402 | 415 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= |
466 | 479 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= |
467 | 480 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= |
468 | 481 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
482 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | |
469 | 483 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
470 | 484 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
471 | 485 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= |
546 | 560 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
547 | 561 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
548 | 562 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
563 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
549 | 564 | golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
550 | 565 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
551 | 566 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
553 | 568 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
554 | 569 | golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
555 | 570 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
571 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | |
556 | 572 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
557 | 573 | golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
558 | 574 | golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
630 | 646 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= |
631 | 647 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= |
632 | 648 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= |
649 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= | |
633 | 650 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= |
634 | 651 | golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= |
635 | 652 | golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= |
717 | 734 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= |
718 | 735 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= |
719 | 736 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= |
737 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | |
720 | 738 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= |
739 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | |
721 | 740 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= |
722 | 741 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= |
723 | 742 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= |
793 | 812 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= |
794 | 813 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
795 | 814 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= |
796 | gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= | |
797 | gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |
815 | gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= | |
816 | gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | |
798 | 817 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
799 | 818 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
800 | 819 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
3 | 3 | "sync" |
4 | 4 | ) |
5 | 5 | |
6 | // Decoder decodes the contents of b into a v representation. | |
6 | // Decoder decodes the contents of b into v. | |
7 | 7 | // It's primarily used for decoding contents of a file into a map[string]interface{}. |
8 | 8 | type Decoder interface { |
9 | Decode(b []byte, v interface{}) error | |
9 | Decode(b []byte, v map[string]interface{}) error | |
10 | 10 | } |
11 | 11 | |
12 | 12 | const ( |
47 | 47 | } |
48 | 48 | |
49 | 49 | // Decode calls the underlying Decoder based on the format. |
50 | func (e *DecoderRegistry) Decode(format string, b []byte, v interface{}) error { | |
50 | func (e *DecoderRegistry) Decode(format string, b []byte, v map[string]interface{}) error { | |
51 | 51 | e.mu.RLock() |
52 | 52 | decoder, ok := e.decoders[format] |
53 | 53 | e.mu.RUnlock() |
0 | 0 | package encoding |
1 | 1 | |
2 | 2 | import ( |
3 | "reflect" | |
3 | 4 | "testing" |
4 | 5 | ) |
5 | 6 | |
6 | 7 | type decoder struct { |
7 | v interface{} | |
8 | v map[string]interface{} | |
8 | 9 | } |
9 | 10 | |
10 | func (d decoder) Decode(_ []byte, v interface{}) error { | |
11 | rv := v.(*string) | |
12 | *rv = d.v.(string) | |
11 | func (d decoder) Decode(_ []byte, v map[string]interface{}) error { | |
12 | for key, value := range d.v { | |
13 | v[key] = value | |
14 | } | |
13 | 15 | |
14 | 16 | return nil |
15 | 17 | } |
43 | 45 | t.Run("OK", func(t *testing.T) { |
44 | 46 | registry := NewDecoderRegistry() |
45 | 47 | decoder := decoder{ |
46 | v: "decoded value", | |
48 | v: map[string]interface{}{ | |
49 | "key": "value", | |
50 | }, | |
47 | 51 | } |
48 | 52 | |
49 | 53 | err := registry.RegisterDecoder("myformat", decoder) |
51 | 55 | t.Fatal(err) |
52 | 56 | } |
53 | 57 | |
54 | var v string | |
58 | v := map[string]interface{}{} | |
55 | 59 | |
56 | err = registry.Decode("myformat", []byte("some value"), &v) | |
60 | err = registry.Decode("myformat", []byte("key: value"), v) | |
57 | 61 | if err != nil { |
58 | 62 | t.Fatal(err) |
59 | 63 | } |
60 | 64 | |
61 | if v != "decoded value" { | |
62 | t.Fatalf("expected 'decoded value', got: %#v", v) | |
65 | if !reflect.DeepEqual(decoder.v, v) { | |
66 | t.Fatalf("decoded value does not match the expected one\nactual: %+v\nexpected: %+v", v, decoder.v) | |
63 | 67 | } |
64 | 68 | }) |
65 | 69 | |
66 | 70 | t.Run("DecoderNotFound", func(t *testing.T) { |
67 | 71 | registry := NewDecoderRegistry() |
68 | 72 | |
69 | var v string | |
73 | v := map[string]interface{}{} | |
70 | 74 | |
71 | err := registry.Decode("myformat", []byte("some value"), &v) | |
75 | err := registry.Decode("myformat", nil, v) | |
72 | 76 | if err != ErrDecoderNotFound { |
73 | 77 | t.Fatalf("expected ErrDecoderNotFound, got: %v", err) |
74 | 78 | } |
0 | package dotenv | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "fmt" | |
5 | "sort" | |
6 | "strings" | |
7 | ||
8 | "github.com/subosito/gotenv" | |
9 | ) | |
10 | ||
11 | const keyDelimiter = "_" | |
12 | ||
13 | // Codec implements the encoding.Encoder and encoding.Decoder interfaces for encoding data containing environment variables | |
14 | // (commonly called as dotenv format). | |
15 | type Codec struct{} | |
16 | ||
17 | func (Codec) Encode(v map[string]interface{}) ([]byte, error) { | |
18 | flattened := map[string]interface{}{} | |
19 | ||
20 | flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter) | |
21 | ||
22 | keys := make([]string, 0, len(flattened)) | |
23 | ||
24 | for key := range flattened { | |
25 | keys = append(keys, key) | |
26 | } | |
27 | ||
28 | sort.Strings(keys) | |
29 | ||
30 | var buf bytes.Buffer | |
31 | ||
32 | for _, key := range keys { | |
33 | _, err := buf.WriteString(fmt.Sprintf("%v=%v\n", strings.ToUpper(key), flattened[key])) | |
34 | if err != nil { | |
35 | return nil, err | |
36 | } | |
37 | } | |
38 | ||
39 | return buf.Bytes(), nil | |
40 | } | |
41 | ||
42 | func (Codec) Decode(b []byte, v map[string]interface{}) error { | |
43 | var buf bytes.Buffer | |
44 | ||
45 | _, err := buf.Write(b) | |
46 | if err != nil { | |
47 | return err | |
48 | } | |
49 | ||
50 | env, err := gotenv.StrictParse(&buf) | |
51 | if err != nil { | |
52 | return err | |
53 | } | |
54 | ||
55 | for key, value := range env { | |
56 | v[key] = value | |
57 | } | |
58 | ||
59 | return nil | |
60 | } |
0 | package dotenv | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | // original form of the data | |
8 | const original = `# key-value pair | |
9 | KEY=value | |
10 | ` | |
11 | ||
12 | // encoded form of the data | |
13 | const encoded = `KEY=value | |
14 | ` | |
15 | ||
16 | // Viper's internal representation | |
17 | var data = map[string]interface{}{ | |
18 | "KEY": "value", | |
19 | } | |
20 | ||
21 | func TestCodec_Encode(t *testing.T) { | |
22 | codec := Codec{} | |
23 | ||
24 | b, err := codec.Encode(data) | |
25 | if err != nil { | |
26 | t.Fatal(err) | |
27 | } | |
28 | ||
29 | if encoded != string(b) { | |
30 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
31 | } | |
32 | } | |
33 | ||
34 | func TestCodec_Decode(t *testing.T) { | |
35 | t.Run("OK", func(t *testing.T) { | |
36 | codec := Codec{} | |
37 | ||
38 | v := map[string]interface{}{} | |
39 | ||
40 | err := codec.Decode([]byte(original), v) | |
41 | if err != nil { | |
42 | t.Fatal(err) | |
43 | } | |
44 | ||
45 | if !reflect.DeepEqual(data, v) { | |
46 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data) | |
47 | } | |
48 | }) | |
49 | ||
50 | t.Run("InvalidData", func(t *testing.T) { | |
51 | codec := Codec{} | |
52 | ||
53 | v := map[string]interface{}{} | |
54 | ||
55 | err := codec.Decode([]byte(`invalid data`), v) | |
56 | if err == nil { | |
57 | t.Fatal("expected decoding to fail") | |
58 | } | |
59 | ||
60 | t.Logf("decoding failed as expected: %s", err) | |
61 | }) | |
62 | } |
0 | package dotenv | |
1 | ||
2 | import ( | |
3 | "strings" | |
4 | ||
5 | "github.com/spf13/cast" | |
6 | ) | |
7 | ||
8 | // flattenAndMergeMap recursively flattens the given map into a new map | |
9 | // Code is based on the function with the same name in tha main package. | |
10 | // TODO: move it to a common place | |
11 | func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} { | |
12 | if shadow != nil && prefix != "" && shadow[prefix] != nil { | |
13 | // prefix is shadowed => nothing more to flatten | |
14 | return shadow | |
15 | } | |
16 | if shadow == nil { | |
17 | shadow = make(map[string]interface{}) | |
18 | } | |
19 | ||
20 | var m2 map[string]interface{} | |
21 | if prefix != "" { | |
22 | prefix += delimiter | |
23 | } | |
24 | for k, val := range m { | |
25 | fullKey := prefix + k | |
26 | switch val.(type) { | |
27 | case map[string]interface{}: | |
28 | m2 = val.(map[string]interface{}) | |
29 | case map[interface{}]interface{}: | |
30 | m2 = cast.ToStringMap(val) | |
31 | default: | |
32 | // immediate value | |
33 | shadow[strings.ToLower(fullKey)] = val | |
34 | continue | |
35 | } | |
36 | // recursively merge to shadow map | |
37 | shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) | |
38 | } | |
39 | return shadow | |
40 | } |
6 | 6 | // Encoder encodes the contents of v into a byte representation. |
7 | 7 | // It's primarily used for encoding a map[string]interface{} into a file format. |
8 | 8 | type Encoder interface { |
9 | Encode(v interface{}) ([]byte, error) | |
9 | Encode(v map[string]interface{}) ([]byte, error) | |
10 | 10 | } |
11 | 11 | |
12 | 12 | const ( |
46 | 46 | return nil |
47 | 47 | } |
48 | 48 | |
49 | func (e *EncoderRegistry) Encode(format string, v interface{}) ([]byte, error) { | |
49 | func (e *EncoderRegistry) Encode(format string, v map[string]interface{}) ([]byte, error) { | |
50 | 50 | e.mu.RLock() |
51 | 51 | encoder, ok := e.encoders[format] |
52 | 52 | e.mu.RUnlock() |
7 | 7 | b []byte |
8 | 8 | } |
9 | 9 | |
10 | func (e encoder) Encode(_ interface{}) ([]byte, error) { | |
10 | func (e encoder) Encode(_ map[string]interface{}) ([]byte, error) { | |
11 | 11 | return e.b, nil |
12 | 12 | } |
13 | 13 | |
40 | 40 | t.Run("OK", func(t *testing.T) { |
41 | 41 | registry := NewEncoderRegistry() |
42 | 42 | encoder := encoder{ |
43 | b: []byte("encoded value"), | |
43 | b: []byte("key: value"), | |
44 | 44 | } |
45 | 45 | |
46 | 46 | err := registry.RegisterEncoder("myformat", encoder) |
48 | 48 | t.Fatal(err) |
49 | 49 | } |
50 | 50 | |
51 | b, err := registry.Encode("myformat", "some value") | |
51 | b, err := registry.Encode("myformat", map[string]interface{}{"key": "value"}) | |
52 | 52 | if err != nil { |
53 | 53 | t.Fatal(err) |
54 | 54 | } |
55 | 55 | |
56 | if string(b) != "encoded value" { | |
57 | t.Fatalf("expected 'encoded value', got: %#v", string(b)) | |
56 | if string(b) != "key: value" { | |
57 | t.Fatalf("expected 'key: value', got: %#v", string(b)) | |
58 | 58 | } |
59 | 59 | }) |
60 | 60 | |
61 | 61 | t.Run("EncoderNotFound", func(t *testing.T) { |
62 | 62 | registry := NewEncoderRegistry() |
63 | 63 | |
64 | _, err := registry.Encode("myformat", "some value") | |
64 | _, err := registry.Encode("myformat", map[string]interface{}{"key": "value"}) | |
65 | 65 | if err != ErrEncoderNotFound { |
66 | 66 | t.Fatalf("expected ErrEncoderNotFound, got: %v", err) |
67 | 67 | } |
11 | 11 | // TODO: add printer config to the codec? |
12 | 12 | type Codec struct{} |
13 | 13 | |
14 | func (Codec) Encode(v interface{}) ([]byte, error) { | |
14 | func (Codec) Encode(v map[string]interface{}) ([]byte, error) { | |
15 | 15 | b, err := json.Marshal(v) |
16 | 16 | if err != nil { |
17 | 17 | return nil, err |
34 | 34 | return buf.Bytes(), nil |
35 | 35 | } |
36 | 36 | |
37 | func (Codec) Decode(b []byte, v interface{}) error { | |
38 | return hcl.Unmarshal(b, v) | |
37 | func (Codec) Decode(b []byte, v map[string]interface{}) error { | |
38 | return hcl.Unmarshal(b, &v) | |
39 | 39 | } |
0 | package hcl | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | // original form of the data | |
8 | const original = `# key-value pair | |
9 | "key" = "value" | |
10 | ||
11 | // list | |
12 | "list" = ["item1", "item2", "item3"] | |
13 | ||
14 | /* map */ | |
15 | "map" = { | |
16 | "key" = "value" | |
17 | } | |
18 | ||
19 | /* | |
20 | nested map | |
21 | */ | |
22 | "nested_map" "map" { | |
23 | "key" = "value" | |
24 | ||
25 | "list" = ["item1", "item2", "item3"] | |
26 | }` | |
27 | ||
28 | // encoded form of the data | |
29 | const encoded = `"key" = "value" | |
30 | ||
31 | "list" = ["item1", "item2", "item3"] | |
32 | ||
33 | "map" = { | |
34 | "key" = "value" | |
35 | } | |
36 | ||
37 | "nested_map" "map" { | |
38 | "key" = "value" | |
39 | ||
40 | "list" = ["item1", "item2", "item3"] | |
41 | }` | |
42 | ||
43 | // decoded form of the data | |
44 | // | |
45 | // in case of HCL it's slightly different from Viper's internal representation | |
46 | // (eg. map is decoded into a list of maps) | |
47 | var decoded = map[string]interface{}{ | |
48 | "key": "value", | |
49 | "list": []interface{}{ | |
50 | "item1", | |
51 | "item2", | |
52 | "item3", | |
53 | }, | |
54 | "map": []map[string]interface{}{ | |
55 | { | |
56 | "key": "value", | |
57 | }, | |
58 | }, | |
59 | "nested_map": []map[string]interface{}{ | |
60 | { | |
61 | "map": []map[string]interface{}{ | |
62 | { | |
63 | "key": "value", | |
64 | "list": []interface{}{ | |
65 | "item1", | |
66 | "item2", | |
67 | "item3", | |
68 | }, | |
69 | }, | |
70 | }, | |
71 | }, | |
72 | }, | |
73 | } | |
74 | ||
75 | // Viper's internal representation | |
76 | var data = map[string]interface{}{ | |
77 | "key": "value", | |
78 | "list": []interface{}{ | |
79 | "item1", | |
80 | "item2", | |
81 | "item3", | |
82 | }, | |
83 | "map": map[string]interface{}{ | |
84 | "key": "value", | |
85 | }, | |
86 | "nested_map": map[string]interface{}{ | |
87 | "map": map[string]interface{}{ | |
88 | "key": "value", | |
89 | "list": []interface{}{ | |
90 | "item1", | |
91 | "item2", | |
92 | "item3", | |
93 | }, | |
94 | }, | |
95 | }, | |
96 | } | |
97 | ||
98 | func TestCodec_Encode(t *testing.T) { | |
99 | codec := Codec{} | |
100 | ||
101 | b, err := codec.Encode(data) | |
102 | if err != nil { | |
103 | t.Fatal(err) | |
104 | } | |
105 | ||
106 | if encoded != string(b) { | |
107 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
108 | } | |
109 | } | |
110 | ||
111 | func TestCodec_Decode(t *testing.T) { | |
112 | t.Run("OK", func(t *testing.T) { | |
113 | codec := Codec{} | |
114 | ||
115 | v := map[string]interface{}{} | |
116 | ||
117 | err := codec.Decode([]byte(original), v) | |
118 | if err != nil { | |
119 | t.Fatal(err) | |
120 | } | |
121 | ||
122 | if !reflect.DeepEqual(decoded, v) { | |
123 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded) | |
124 | } | |
125 | }) | |
126 | ||
127 | t.Run("InvalidData", func(t *testing.T) { | |
128 | codec := Codec{} | |
129 | ||
130 | v := map[string]interface{}{} | |
131 | ||
132 | err := codec.Decode([]byte(`invalid data`), v) | |
133 | if err == nil { | |
134 | t.Fatal("expected decoding to fail") | |
135 | } | |
136 | ||
137 | t.Logf("decoding failed as expected: %s", err) | |
138 | }) | |
139 | } |
0 | package ini | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "sort" | |
5 | "strings" | |
6 | ||
7 | "github.com/spf13/cast" | |
8 | "gopkg.in/ini.v1" | |
9 | ) | |
10 | ||
11 | // LoadOptions contains all customized options used for load data source(s). | |
12 | // This type is added here for convenience: this way consumers can import a single package called "ini". | |
13 | type LoadOptions = ini.LoadOptions | |
14 | ||
15 | // Codec implements the encoding.Encoder and encoding.Decoder interfaces for INI encoding. | |
16 | type Codec struct { | |
17 | KeyDelimiter string | |
18 | LoadOptions LoadOptions | |
19 | } | |
20 | ||
21 | func (c Codec) Encode(v map[string]interface{}) ([]byte, error) { | |
22 | cfg := ini.Empty() | |
23 | ini.PrettyFormat = false | |
24 | ||
25 | flattened := map[string]interface{}{} | |
26 | ||
27 | flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter()) | |
28 | ||
29 | keys := make([]string, 0, len(flattened)) | |
30 | ||
31 | for key := range flattened { | |
32 | keys = append(keys, key) | |
33 | } | |
34 | ||
35 | sort.Strings(keys) | |
36 | ||
37 | for _, key := range keys { | |
38 | sectionName, keyName := "", key | |
39 | ||
40 | lastSep := strings.LastIndex(key, ".") | |
41 | if lastSep != -1 { | |
42 | sectionName = key[:(lastSep)] | |
43 | keyName = key[(lastSep + 1):] | |
44 | } | |
45 | ||
46 | // TODO: is this a good idea? | |
47 | if sectionName == "default" { | |
48 | sectionName = "" | |
49 | } | |
50 | ||
51 | cfg.Section(sectionName).Key(keyName).SetValue(cast.ToString(flattened[key])) | |
52 | } | |
53 | ||
54 | var buf bytes.Buffer | |
55 | ||
56 | _, err := cfg.WriteTo(&buf) | |
57 | if err != nil { | |
58 | return nil, err | |
59 | } | |
60 | ||
61 | return buf.Bytes(), nil | |
62 | } | |
63 | ||
64 | func (c Codec) Decode(b []byte, v map[string]interface{}) error { | |
65 | cfg := ini.Empty(c.LoadOptions) | |
66 | ||
67 | err := cfg.Append(b) | |
68 | if err != nil { | |
69 | return err | |
70 | } | |
71 | ||
72 | sections := cfg.Sections() | |
73 | ||
74 | for i := 0; i < len(sections); i++ { | |
75 | section := sections[i] | |
76 | keys := section.Keys() | |
77 | ||
78 | for j := 0; j < len(keys); j++ { | |
79 | key := keys[j] | |
80 | value := cfg.Section(section.Name()).Key(key.Name()).String() | |
81 | ||
82 | deepestMap := deepSearch(v, strings.Split(section.Name(), c.keyDelimiter())) | |
83 | ||
84 | // set innermost value | |
85 | deepestMap[key.Name()] = value | |
86 | } | |
87 | } | |
88 | ||
89 | return nil | |
90 | } | |
91 | ||
92 | func (c Codec) keyDelimiter() string { | |
93 | if c.KeyDelimiter == "" { | |
94 | return "." | |
95 | } | |
96 | ||
97 | return c.KeyDelimiter | |
98 | } |
0 | package ini | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | // original form of the data | |
8 | const original = `; key-value pair | |
9 | key=value ; key-value pair | |
10 | ||
11 | # map | |
12 | [map] # map | |
13 | key=%(key)s | |
14 | ||
15 | ` | |
16 | ||
17 | // encoded form of the data | |
18 | const encoded = `key=value | |
19 | ||
20 | [map] | |
21 | key=value | |
22 | ||
23 | ` | |
24 | ||
25 | // decoded form of the data | |
26 | // | |
27 | // in case of INI it's slightly different from Viper's internal representation | |
28 | // (eg. top level keys land in a section called default) | |
29 | var decoded = map[string]interface{}{ | |
30 | "DEFAULT": map[string]interface{}{ | |
31 | "key": "value", | |
32 | }, | |
33 | "map": map[string]interface{}{ | |
34 | "key": "value", | |
35 | }, | |
36 | } | |
37 | ||
38 | // Viper's internal representation | |
39 | var data = map[string]interface{}{ | |
40 | "key": "value", | |
41 | "map": map[string]interface{}{ | |
42 | "key": "value", | |
43 | }, | |
44 | } | |
45 | ||
46 | func TestCodec_Encode(t *testing.T) { | |
47 | t.Run("OK", func(t *testing.T) { | |
48 | codec := Codec{} | |
49 | ||
50 | b, err := codec.Encode(data) | |
51 | if err != nil { | |
52 | t.Fatal(err) | |
53 | } | |
54 | ||
55 | if encoded != string(b) { | |
56 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
57 | } | |
58 | }) | |
59 | ||
60 | t.Run("Default", func(t *testing.T) { | |
61 | codec := Codec{} | |
62 | ||
63 | data := map[string]interface{}{ | |
64 | "default": map[string]interface{}{ | |
65 | "key": "value", | |
66 | }, | |
67 | "map": map[string]interface{}{ | |
68 | "key": "value", | |
69 | }, | |
70 | } | |
71 | ||
72 | b, err := codec.Encode(data) | |
73 | if err != nil { | |
74 | t.Fatal(err) | |
75 | } | |
76 | ||
77 | if encoded != string(b) { | |
78 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
79 | } | |
80 | }) | |
81 | } | |
82 | ||
83 | func TestCodec_Decode(t *testing.T) { | |
84 | t.Run("OK", func(t *testing.T) { | |
85 | codec := Codec{} | |
86 | ||
87 | v := map[string]interface{}{} | |
88 | ||
89 | err := codec.Decode([]byte(original), v) | |
90 | if err != nil { | |
91 | t.Fatal(err) | |
92 | } | |
93 | ||
94 | if !reflect.DeepEqual(decoded, v) { | |
95 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded) | |
96 | } | |
97 | }) | |
98 | ||
99 | t.Run("InvalidData", func(t *testing.T) { | |
100 | codec := Codec{} | |
101 | ||
102 | v := map[string]interface{}{} | |
103 | ||
104 | err := codec.Decode([]byte(`invalid data`), v) | |
105 | if err == nil { | |
106 | t.Fatal("expected decoding to fail") | |
107 | } | |
108 | ||
109 | t.Logf("decoding failed as expected: %s", err) | |
110 | }) | |
111 | } |
0 | package ini | |
1 | ||
2 | import ( | |
3 | "strings" | |
4 | ||
5 | "github.com/spf13/cast" | |
6 | ) | |
7 | ||
8 | // THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED | |
9 | // AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE | |
10 | // deepSearch scans deep maps, following the key indexes listed in the | |
11 | // sequence "path". | |
12 | // The last value is expected to be another map, and is returned. | |
13 | // | |
14 | // In case intermediate keys do not exist, or map to a non-map value, | |
15 | // a new map is created and inserted, and the search continues from there: | |
16 | // the initial map "m" may be modified! | |
17 | func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { | |
18 | for _, k := range path { | |
19 | m2, ok := m[k] | |
20 | if !ok { | |
21 | // intermediate key does not exist | |
22 | // => create it and continue from there | |
23 | m3 := make(map[string]interface{}) | |
24 | m[k] = m3 | |
25 | m = m3 | |
26 | continue | |
27 | } | |
28 | m3, ok := m2.(map[string]interface{}) | |
29 | if !ok { | |
30 | // intermediate key is a value | |
31 | // => replace with a new map | |
32 | m3 = make(map[string]interface{}) | |
33 | m[k] = m3 | |
34 | } | |
35 | // continue search from here | |
36 | m = m3 | |
37 | } | |
38 | return m | |
39 | } | |
40 | ||
41 | // flattenAndMergeMap recursively flattens the given map into a new map | |
42 | // Code is based on the function with the same name in tha main package. | |
43 | // TODO: move it to a common place | |
44 | func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} { | |
45 | if shadow != nil && prefix != "" && shadow[prefix] != nil { | |
46 | // prefix is shadowed => nothing more to flatten | |
47 | return shadow | |
48 | } | |
49 | if shadow == nil { | |
50 | shadow = make(map[string]interface{}) | |
51 | } | |
52 | ||
53 | var m2 map[string]interface{} | |
54 | if prefix != "" { | |
55 | prefix += delimiter | |
56 | } | |
57 | for k, val := range m { | |
58 | fullKey := prefix + k | |
59 | switch val.(type) { | |
60 | case map[string]interface{}: | |
61 | m2 = val.(map[string]interface{}) | |
62 | case map[interface{}]interface{}: | |
63 | m2 = cast.ToStringMap(val) | |
64 | default: | |
65 | // immediate value | |
66 | shadow[strings.ToLower(fullKey)] = val | |
67 | continue | |
68 | } | |
69 | // recursively merge to shadow map | |
70 | shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) | |
71 | } | |
72 | return shadow | |
73 | } |
0 | package javaproperties | |
1 | ||
2 | import ( | |
3 | "bytes" | |
4 | "sort" | |
5 | "strings" | |
6 | ||
7 | "github.com/magiconair/properties" | |
8 | "github.com/spf13/cast" | |
9 | ) | |
10 | ||
11 | // Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding. | |
12 | type Codec struct { | |
13 | KeyDelimiter string | |
14 | ||
15 | // Store read properties on the object so that we can write back in order with comments. | |
16 | // This will only be used if the configuration read is a properties file. | |
17 | // TODO: drop this feature in v2 | |
18 | // TODO: make use of the global properties object optional | |
19 | Properties *properties.Properties | |
20 | } | |
21 | ||
22 | func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) { | |
23 | if c.Properties == nil { | |
24 | c.Properties = properties.NewProperties() | |
25 | } | |
26 | ||
27 | flattened := map[string]interface{}{} | |
28 | ||
29 | flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter()) | |
30 | ||
31 | keys := make([]string, 0, len(flattened)) | |
32 | ||
33 | for key := range flattened { | |
34 | keys = append(keys, key) | |
35 | } | |
36 | ||
37 | sort.Strings(keys) | |
38 | ||
39 | for _, key := range keys { | |
40 | _, _, err := c.Properties.Set(key, cast.ToString(flattened[key])) | |
41 | if err != nil { | |
42 | return nil, err | |
43 | } | |
44 | } | |
45 | ||
46 | var buf bytes.Buffer | |
47 | ||
48 | _, err := c.Properties.WriteComment(&buf, "#", properties.UTF8) | |
49 | if err != nil { | |
50 | return nil, err | |
51 | } | |
52 | ||
53 | return buf.Bytes(), nil | |
54 | } | |
55 | ||
56 | func (c *Codec) Decode(b []byte, v map[string]interface{}) error { | |
57 | var err error | |
58 | c.Properties, err = properties.Load(b, properties.UTF8) | |
59 | if err != nil { | |
60 | return err | |
61 | } | |
62 | ||
63 | for _, key := range c.Properties.Keys() { | |
64 | // ignore existence check: we know it's there | |
65 | value, _ := c.Properties.Get(key) | |
66 | ||
67 | // recursively build nested maps | |
68 | path := strings.Split(key, c.keyDelimiter()) | |
69 | lastKey := strings.ToLower(path[len(path)-1]) | |
70 | deepestMap := deepSearch(v, path[0:len(path)-1]) | |
71 | ||
72 | // set innermost value | |
73 | deepestMap[lastKey] = value | |
74 | } | |
75 | ||
76 | return nil | |
77 | } | |
78 | ||
79 | func (c Codec) keyDelimiter() string { | |
80 | if c.KeyDelimiter == "" { | |
81 | return "." | |
82 | } | |
83 | ||
84 | return c.KeyDelimiter | |
85 | } |
0 | package javaproperties | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | // original form of the data | |
8 | const original = `#key-value pair | |
9 | key = value | |
10 | map.key = value | |
11 | ` | |
12 | ||
13 | // encoded form of the data | |
14 | const encoded = `key = value | |
15 | map.key = value | |
16 | ` | |
17 | ||
18 | // Viper's internal representation | |
19 | var data = map[string]interface{}{ | |
20 | "key": "value", | |
21 | "map": map[string]interface{}{ | |
22 | "key": "value", | |
23 | }, | |
24 | } | |
25 | ||
26 | func TestCodec_Encode(t *testing.T) { | |
27 | codec := Codec{} | |
28 | ||
29 | b, err := codec.Encode(data) | |
30 | if err != nil { | |
31 | t.Fatal(err) | |
32 | } | |
33 | ||
34 | if encoded != string(b) { | |
35 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
36 | } | |
37 | } | |
38 | ||
39 | func TestCodec_Decode(t *testing.T) { | |
40 | t.Run("OK", func(t *testing.T) { | |
41 | codec := Codec{} | |
42 | ||
43 | v := map[string]interface{}{} | |
44 | ||
45 | err := codec.Decode([]byte(original), v) | |
46 | if err != nil { | |
47 | t.Fatal(err) | |
48 | } | |
49 | ||
50 | if !reflect.DeepEqual(data, v) { | |
51 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data) | |
52 | } | |
53 | }) | |
54 | ||
55 | t.Run("InvalidData", func(t *testing.T) { | |
56 | t.Skip("TODO: needs invalid data example") | |
57 | ||
58 | codec := Codec{} | |
59 | ||
60 | v := map[string]interface{}{} | |
61 | ||
62 | codec.Decode([]byte(``), v) | |
63 | ||
64 | if len(v) > 0 { | |
65 | t.Fatalf("expected map to be empty when data is invalid\nactual: %#v", v) | |
66 | } | |
67 | }) | |
68 | } | |
69 | ||
70 | func TestCodec_DecodeEncode(t *testing.T) { | |
71 | codec := Codec{} | |
72 | ||
73 | v := map[string]interface{}{} | |
74 | ||
75 | err := codec.Decode([]byte(original), v) | |
76 | if err != nil { | |
77 | t.Fatal(err) | |
78 | } | |
79 | ||
80 | b, err := codec.Encode(data) | |
81 | if err != nil { | |
82 | t.Fatal(err) | |
83 | } | |
84 | ||
85 | if original != string(b) { | |
86 | t.Fatalf("encoded value does not match the original\nactual: %#v\nexpected: %#v", string(b), original) | |
87 | } | |
88 | } |
0 | package javaproperties | |
1 | ||
2 | import ( | |
3 | "strings" | |
4 | ||
5 | "github.com/spf13/cast" | |
6 | ) | |
7 | ||
8 | // THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED | |
9 | // AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE | |
10 | // deepSearch scans deep maps, following the key indexes listed in the | |
11 | // sequence "path". | |
12 | // The last value is expected to be another map, and is returned. | |
13 | // | |
14 | // In case intermediate keys do not exist, or map to a non-map value, | |
15 | // a new map is created and inserted, and the search continues from there: | |
16 | // the initial map "m" may be modified! | |
17 | func deepSearch(m map[string]interface{}, path []string) map[string]interface{} { | |
18 | for _, k := range path { | |
19 | m2, ok := m[k] | |
20 | if !ok { | |
21 | // intermediate key does not exist | |
22 | // => create it and continue from there | |
23 | m3 := make(map[string]interface{}) | |
24 | m[k] = m3 | |
25 | m = m3 | |
26 | continue | |
27 | } | |
28 | m3, ok := m2.(map[string]interface{}) | |
29 | if !ok { | |
30 | // intermediate key is a value | |
31 | // => replace with a new map | |
32 | m3 = make(map[string]interface{}) | |
33 | m[k] = m3 | |
34 | } | |
35 | // continue search from here | |
36 | m = m3 | |
37 | } | |
38 | return m | |
39 | } | |
40 | ||
41 | // flattenAndMergeMap recursively flattens the given map into a new map | |
42 | // Code is based on the function with the same name in tha main package. | |
43 | // TODO: move it to a common place | |
44 | func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} { | |
45 | if shadow != nil && prefix != "" && shadow[prefix] != nil { | |
46 | // prefix is shadowed => nothing more to flatten | |
47 | return shadow | |
48 | } | |
49 | if shadow == nil { | |
50 | shadow = make(map[string]interface{}) | |
51 | } | |
52 | ||
53 | var m2 map[string]interface{} | |
54 | if prefix != "" { | |
55 | prefix += delimiter | |
56 | } | |
57 | for k, val := range m { | |
58 | fullKey := prefix + k | |
59 | switch val.(type) { | |
60 | case map[string]interface{}: | |
61 | m2 = val.(map[string]interface{}) | |
62 | case map[interface{}]interface{}: | |
63 | m2 = cast.ToStringMap(val) | |
64 | default: | |
65 | // immediate value | |
66 | shadow[strings.ToLower(fullKey)] = val | |
67 | continue | |
68 | } | |
69 | // recursively merge to shadow map | |
70 | shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter) | |
71 | } | |
72 | return shadow | |
73 | } |
6 | 6 | // Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding. |
7 | 7 | type Codec struct{} |
8 | 8 | |
9 | func (Codec) Encode(v interface{}) ([]byte, error) { | |
9 | func (Codec) Encode(v map[string]interface{}) ([]byte, error) { | |
10 | 10 | // TODO: expose prefix and indent in the Codec as setting? |
11 | 11 | return json.MarshalIndent(v, "", " ") |
12 | 12 | } |
13 | 13 | |
14 | func (Codec) Decode(b []byte, v interface{}) error { | |
15 | return json.Unmarshal(b, v) | |
14 | func (Codec) Decode(b []byte, v map[string]interface{}) error { | |
15 | return json.Unmarshal(b, &v) | |
16 | 16 | } |
0 | package json | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | // encoded form of the data | |
8 | const encoded = `{ | |
9 | "key": "value", | |
10 | "list": [ | |
11 | "item1", | |
12 | "item2", | |
13 | "item3" | |
14 | ], | |
15 | "map": { | |
16 | "key": "value" | |
17 | }, | |
18 | "nested_map": { | |
19 | "map": { | |
20 | "key": "value", | |
21 | "list": [ | |
22 | "item1", | |
23 | "item2", | |
24 | "item3" | |
25 | ] | |
26 | } | |
27 | } | |
28 | }` | |
29 | ||
30 | // Viper's internal representation | |
31 | var data = map[string]interface{}{ | |
32 | "key": "value", | |
33 | "list": []interface{}{ | |
34 | "item1", | |
35 | "item2", | |
36 | "item3", | |
37 | }, | |
38 | "map": map[string]interface{}{ | |
39 | "key": "value", | |
40 | }, | |
41 | "nested_map": map[string]interface{}{ | |
42 | "map": map[string]interface{}{ | |
43 | "key": "value", | |
44 | "list": []interface{}{ | |
45 | "item1", | |
46 | "item2", | |
47 | "item3", | |
48 | }, | |
49 | }, | |
50 | }, | |
51 | } | |
52 | ||
53 | func TestCodec_Encode(t *testing.T) { | |
54 | codec := Codec{} | |
55 | ||
56 | b, err := codec.Encode(data) | |
57 | if err != nil { | |
58 | t.Fatal(err) | |
59 | } | |
60 | ||
61 | if encoded != string(b) { | |
62 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
63 | } | |
64 | } | |
65 | ||
66 | func TestCodec_Decode(t *testing.T) { | |
67 | t.Run("OK", func(t *testing.T) { | |
68 | codec := Codec{} | |
69 | ||
70 | v := map[string]interface{}{} | |
71 | ||
72 | err := codec.Decode([]byte(encoded), v) | |
73 | if err != nil { | |
74 | t.Fatal(err) | |
75 | } | |
76 | ||
77 | if !reflect.DeepEqual(data, v) { | |
78 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data) | |
79 | } | |
80 | }) | |
81 | ||
82 | t.Run("InvalidData", func(t *testing.T) { | |
83 | codec := Codec{} | |
84 | ||
85 | v := map[string]interface{}{} | |
86 | ||
87 | err := codec.Decode([]byte(`invalid data`), v) | |
88 | if err == nil { | |
89 | t.Fatal("expected decoding to fail") | |
90 | } | |
91 | ||
92 | t.Logf("decoding failed as expected: %s", err) | |
93 | }) | |
94 | } |
0 | //go:build !viper_toml2 | |
1 | // +build !viper_toml2 | |
2 | ||
0 | 3 | package toml |
1 | 4 | |
2 | 5 | import ( |
6 | 9 | // Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding. |
7 | 10 | type Codec struct{} |
8 | 11 | |
9 | func (Codec) Encode(v interface{}) ([]byte, error) { | |
10 | if m, ok := v.(map[string]interface{}); ok { | |
11 | t, err := toml.TreeFromMap(m) | |
12 | if err != nil { | |
13 | return nil, err | |
14 | } | |
15 | ||
16 | s, err := t.ToTomlString() | |
17 | if err != nil { | |
18 | return nil, err | |
19 | } | |
20 | ||
21 | return []byte(s), nil | |
12 | func (Codec) Encode(v map[string]interface{}) ([]byte, error) { | |
13 | t, err := toml.TreeFromMap(v) | |
14 | if err != nil { | |
15 | return nil, err | |
22 | 16 | } |
23 | 17 | |
24 | return toml.Marshal(v) | |
18 | s, err := t.ToTomlString() | |
19 | if err != nil { | |
20 | return nil, err | |
21 | } | |
22 | ||
23 | return []byte(s), nil | |
25 | 24 | } |
26 | 25 | |
27 | func (Codec) Decode(b []byte, v interface{}) error { | |
26 | func (Codec) Decode(b []byte, v map[string]interface{}) error { | |
28 | 27 | tree, err := toml.LoadBytes(b) |
29 | 28 | if err != nil { |
30 | 29 | return err |
31 | 30 | } |
32 | 31 | |
33 | if m, ok := v.(*map[string]interface{}); ok { | |
34 | vmap := *m | |
35 | tmap := tree.ToMap() | |
36 | for k, v := range tmap { | |
37 | vmap[k] = v | |
38 | } | |
39 | ||
40 | return nil | |
32 | tmap := tree.ToMap() | |
33 | for key, value := range tmap { | |
34 | v[key] = value | |
41 | 35 | } |
42 | 36 | |
43 | return tree.Unmarshal(v) | |
37 | return nil | |
44 | 38 | } |
0 | //go:build viper_toml2 | |
1 | // +build viper_toml2 | |
2 | ||
3 | package toml | |
4 | ||
5 | import ( | |
6 | "github.com/pelletier/go-toml/v2" | |
7 | ) | |
8 | ||
9 | // Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding. | |
10 | type Codec struct{} | |
11 | ||
12 | func (Codec) Encode(v map[string]interface{}) ([]byte, error) { | |
13 | return toml.Marshal(v) | |
14 | } | |
15 | ||
16 | func (Codec) Decode(b []byte, v map[string]interface{}) error { | |
17 | return toml.Unmarshal(b, &v) | |
18 | } |
0 | //go:build viper_toml2 | |
1 | // +build viper_toml2 | |
2 | ||
3 | package toml | |
4 | ||
5 | import ( | |
6 | "reflect" | |
7 | "testing" | |
8 | ) | |
9 | ||
10 | // original form of the data | |
11 | const original = `# key-value pair | |
12 | key = "value" | |
13 | list = ["item1", "item2", "item3"] | |
14 | ||
15 | [map] | |
16 | key = "value" | |
17 | ||
18 | # nested | |
19 | # map | |
20 | [nested_map] | |
21 | [nested_map.map] | |
22 | key = "value" | |
23 | list = [ | |
24 | "item1", | |
25 | "item2", | |
26 | "item3", | |
27 | ] | |
28 | ` | |
29 | ||
30 | // encoded form of the data | |
31 | const encoded = `key = 'value' | |
32 | list = ['item1', 'item2', 'item3'] | |
33 | [map] | |
34 | key = 'value' | |
35 | ||
36 | [nested_map] | |
37 | [nested_map.map] | |
38 | key = 'value' | |
39 | list = ['item1', 'item2', 'item3'] | |
40 | ||
41 | ||
42 | ` | |
43 | ||
44 | // Viper's internal representation | |
45 | var data = map[string]interface{}{ | |
46 | "key": "value", | |
47 | "list": []interface{}{ | |
48 | "item1", | |
49 | "item2", | |
50 | "item3", | |
51 | }, | |
52 | "map": map[string]interface{}{ | |
53 | "key": "value", | |
54 | }, | |
55 | "nested_map": map[string]interface{}{ | |
56 | "map": map[string]interface{}{ | |
57 | "key": "value", | |
58 | "list": []interface{}{ | |
59 | "item1", | |
60 | "item2", | |
61 | "item3", | |
62 | }, | |
63 | }, | |
64 | }, | |
65 | } | |
66 | ||
67 | func TestCodec_Encode(t *testing.T) { | |
68 | codec := Codec{} | |
69 | ||
70 | b, err := codec.Encode(data) | |
71 | if err != nil { | |
72 | t.Fatal(err) | |
73 | } | |
74 | ||
75 | if encoded != string(b) { | |
76 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
77 | } | |
78 | } | |
79 | ||
80 | func TestCodec_Decode(t *testing.T) { | |
81 | t.Run("OK", func(t *testing.T) { | |
82 | codec := Codec{} | |
83 | ||
84 | v := map[string]interface{}{} | |
85 | ||
86 | err := codec.Decode([]byte(original), v) | |
87 | if err != nil { | |
88 | t.Fatal(err) | |
89 | } | |
90 | ||
91 | if !reflect.DeepEqual(data, v) { | |
92 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data) | |
93 | } | |
94 | }) | |
95 | ||
96 | t.Run("InvalidData", func(t *testing.T) { | |
97 | codec := Codec{} | |
98 | ||
99 | v := map[string]interface{}{} | |
100 | ||
101 | err := codec.Decode([]byte(`invalid data`), v) | |
102 | if err == nil { | |
103 | t.Fatal("expected decoding to fail") | |
104 | } | |
105 | ||
106 | t.Logf("decoding failed as expected: %s", err) | |
107 | }) | |
108 | } |
0 | //go:build !viper_toml2 | |
1 | // +build !viper_toml2 | |
2 | ||
3 | package toml | |
4 | ||
5 | import ( | |
6 | "reflect" | |
7 | "testing" | |
8 | ) | |
9 | ||
10 | // original form of the data | |
11 | const original = `# key-value pair | |
12 | key = "value" | |
13 | list = ["item1", "item2", "item3"] | |
14 | ||
15 | [map] | |
16 | key = "value" | |
17 | ||
18 | # nested | |
19 | # map | |
20 | [nested_map] | |
21 | [nested_map.map] | |
22 | key = "value" | |
23 | list = [ | |
24 | "item1", | |
25 | "item2", | |
26 | "item3", | |
27 | ] | |
28 | ` | |
29 | ||
30 | // encoded form of the data | |
31 | const encoded = `key = "value" | |
32 | list = ["item1", "item2", "item3"] | |
33 | ||
34 | [map] | |
35 | key = "value" | |
36 | ||
37 | [nested_map] | |
38 | ||
39 | [nested_map.map] | |
40 | key = "value" | |
41 | list = ["item1", "item2", "item3"] | |
42 | ` | |
43 | ||
44 | // Viper's internal representation | |
45 | var data = map[string]interface{}{ | |
46 | "key": "value", | |
47 | "list": []interface{}{ | |
48 | "item1", | |
49 | "item2", | |
50 | "item3", | |
51 | }, | |
52 | "map": map[string]interface{}{ | |
53 | "key": "value", | |
54 | }, | |
55 | "nested_map": map[string]interface{}{ | |
56 | "map": map[string]interface{}{ | |
57 | "key": "value", | |
58 | "list": []interface{}{ | |
59 | "item1", | |
60 | "item2", | |
61 | "item3", | |
62 | }, | |
63 | }, | |
64 | }, | |
65 | } | |
66 | ||
67 | func TestCodec_Encode(t *testing.T) { | |
68 | codec := Codec{} | |
69 | ||
70 | b, err := codec.Encode(data) | |
71 | if err != nil { | |
72 | t.Fatal(err) | |
73 | } | |
74 | ||
75 | if encoded != string(b) { | |
76 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
77 | } | |
78 | } | |
79 | ||
80 | func TestCodec_Decode(t *testing.T) { | |
81 | t.Run("OK", func(t *testing.T) { | |
82 | codec := Codec{} | |
83 | ||
84 | v := map[string]interface{}{} | |
85 | ||
86 | err := codec.Decode([]byte(original), v) | |
87 | if err != nil { | |
88 | t.Fatal(err) | |
89 | } | |
90 | ||
91 | if !reflect.DeepEqual(data, v) { | |
92 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data) | |
93 | } | |
94 | }) | |
95 | ||
96 | t.Run("InvalidData", func(t *testing.T) { | |
97 | codec := Codec{} | |
98 | ||
99 | v := map[string]interface{}{} | |
100 | ||
101 | err := codec.Decode([]byte(`invalid data`), v) | |
102 | if err == nil { | |
103 | t.Fatal("expected decoding to fail") | |
104 | } | |
105 | ||
106 | t.Logf("decoding failed as expected: %s", err) | |
107 | }) | |
108 | } |
0 | 0 | package yaml |
1 | 1 | |
2 | import "gopkg.in/yaml.v2" | |
2 | // import "gopkg.in/yaml.v2" | |
3 | 3 | |
4 | 4 | // Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding. |
5 | 5 | type Codec struct{} |
6 | 6 | |
7 | func (Codec) Encode(v interface{}) ([]byte, error) { | |
7 | func (Codec) Encode(v map[string]interface{}) ([]byte, error) { | |
8 | 8 | return yaml.Marshal(v) |
9 | 9 | } |
10 | 10 | |
11 | func (Codec) Decode(b []byte, v interface{}) error { | |
12 | return yaml.Unmarshal(b, v) | |
11 | func (Codec) Decode(b []byte, v map[string]interface{}) error { | |
12 | return yaml.Unmarshal(b, &v) | |
13 | 13 | } |
0 | package yaml | |
1 | ||
2 | import ( | |
3 | "reflect" | |
4 | "testing" | |
5 | ) | |
6 | ||
7 | func TestCodec_Encode(t *testing.T) { | |
8 | codec := Codec{} | |
9 | ||
10 | b, err := codec.Encode(data) | |
11 | if err != nil { | |
12 | t.Fatal(err) | |
13 | } | |
14 | ||
15 | if encoded != string(b) { | |
16 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded) | |
17 | } | |
18 | } | |
19 | ||
20 | func TestCodec_Decode(t *testing.T) { | |
21 | t.Run("OK", func(t *testing.T) { | |
22 | codec := Codec{} | |
23 | ||
24 | v := map[string]interface{}{} | |
25 | ||
26 | err := codec.Decode([]byte(original), v) | |
27 | if err != nil { | |
28 | t.Fatal(err) | |
29 | } | |
30 | ||
31 | if !reflect.DeepEqual(decoded, v) { | |
32 | t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded) | |
33 | } | |
34 | }) | |
35 | ||
36 | t.Run("InvalidData", func(t *testing.T) { | |
37 | codec := Codec{} | |
38 | ||
39 | v := map[string]interface{}{} | |
40 | ||
41 | err := codec.Decode([]byte(`invalid data`), v) | |
42 | if err == nil { | |
43 | t.Fatal("expected decoding to fail") | |
44 | } | |
45 | ||
46 | t.Logf("decoding failed as expected: %s", err) | |
47 | }) | |
48 | } |
0 | //go:build !viper_yaml3 | |
1 | // +build !viper_yaml3 | |
2 | ||
3 | package yaml | |
4 | ||
5 | import yamlv2 "gopkg.in/yaml.v2" | |
6 | ||
7 | var yaml = struct { | |
8 | Marshal func(in interface{}) (out []byte, err error) | |
9 | Unmarshal func(in []byte, out interface{}) (err error) | |
10 | }{ | |
11 | Marshal: yamlv2.Marshal, | |
12 | Unmarshal: yamlv2.Unmarshal, | |
13 | } |
0 | //go:build !viper_yaml3 | |
1 | // +build !viper_yaml3 | |
2 | ||
3 | package yaml | |
4 | ||
5 | // original form of the data | |
6 | const original = `# key-value pair | |
7 | key: value | |
8 | list: | |
9 | - item1 | |
10 | - item2 | |
11 | - item3 | |
12 | map: | |
13 | key: value | |
14 | ||
15 | # nested | |
16 | # map | |
17 | nested_map: | |
18 | map: | |
19 | key: value | |
20 | list: | |
21 | - item1 | |
22 | - item2 | |
23 | - item3 | |
24 | ` | |
25 | ||
26 | // encoded form of the data | |
27 | const encoded = `key: value | |
28 | list: | |
29 | - item1 | |
30 | - item2 | |
31 | - item3 | |
32 | map: | |
33 | key: value | |
34 | nested_map: | |
35 | map: | |
36 | key: value | |
37 | list: | |
38 | - item1 | |
39 | - item2 | |
40 | - item3 | |
41 | ` | |
42 | ||
43 | // decoded form of the data | |
44 | // | |
45 | // in case of YAML it's slightly different from Viper's internal representation | |
46 | // (eg. map is decoded into a map with interface key) | |
47 | var decoded = map[string]interface{}{ | |
48 | "key": "value", | |
49 | "list": []interface{}{ | |
50 | "item1", | |
51 | "item2", | |
52 | "item3", | |
53 | }, | |
54 | "map": map[interface{}]interface{}{ | |
55 | "key": "value", | |
56 | }, | |
57 | "nested_map": map[interface{}]interface{}{ | |
58 | "map": map[interface{}]interface{}{ | |
59 | "key": "value", | |
60 | "list": []interface{}{ | |
61 | "item1", | |
62 | "item2", | |
63 | "item3", | |
64 | }, | |
65 | }, | |
66 | }, | |
67 | } | |
68 | ||
69 | // Viper's internal representation | |
70 | var data = map[string]interface{}{ | |
71 | "key": "value", | |
72 | "list": []interface{}{ | |
73 | "item1", | |
74 | "item2", | |
75 | "item3", | |
76 | }, | |
77 | "map": map[string]interface{}{ | |
78 | "key": "value", | |
79 | }, | |
80 | "nested_map": map[string]interface{}{ | |
81 | "map": map[string]interface{}{ | |
82 | "key": "value", | |
83 | "list": []interface{}{ | |
84 | "item1", | |
85 | "item2", | |
86 | "item3", | |
87 | }, | |
88 | }, | |
89 | }, | |
90 | } |
0 | //go:build viper_yaml3 | |
1 | // +build viper_yaml3 | |
2 | ||
3 | package yaml | |
4 | ||
5 | import yamlv3 "gopkg.in/yaml.v3" | |
6 | ||
7 | var yaml = struct { | |
8 | Marshal func(in interface{}) (out []byte, err error) | |
9 | Unmarshal func(in []byte, out interface{}) (err error) | |
10 | }{ | |
11 | Marshal: yamlv3.Marshal, | |
12 | Unmarshal: yamlv3.Unmarshal, | |
13 | } |
0 | //go:build viper_yaml3 | |
1 | // +build viper_yaml3 | |
2 | ||
3 | package yaml | |
4 | ||
5 | // original form of the data | |
6 | const original = `# key-value pair | |
7 | key: value | |
8 | list: | |
9 | - item1 | |
10 | - item2 | |
11 | - item3 | |
12 | map: | |
13 | key: value | |
14 | ||
15 | # nested | |
16 | # map | |
17 | nested_map: | |
18 | map: | |
19 | key: value | |
20 | list: | |
21 | - item1 | |
22 | - item2 | |
23 | - item3 | |
24 | ` | |
25 | ||
26 | // encoded form of the data | |
27 | const encoded = `key: value | |
28 | list: | |
29 | - item1 | |
30 | - item2 | |
31 | - item3 | |
32 | map: | |
33 | key: value | |
34 | nested_map: | |
35 | map: | |
36 | key: value | |
37 | list: | |
38 | - item1 | |
39 | - item2 | |
40 | - item3 | |
41 | ` | |
42 | ||
43 | // decoded form of the data | |
44 | // | |
45 | // in case of YAML it's slightly different from Viper's internal representation | |
46 | // (eg. map is decoded into a map with interface key) | |
47 | var decoded = map[string]interface{}{ | |
48 | "key": "value", | |
49 | "list": []interface{}{ | |
50 | "item1", | |
51 | "item2", | |
52 | "item3", | |
53 | }, | |
54 | "map": map[string]interface{}{ | |
55 | "key": "value", | |
56 | }, | |
57 | "nested_map": map[string]interface{}{ | |
58 | "map": map[string]interface{}{ | |
59 | "key": "value", | |
60 | "list": []interface{}{ | |
61 | "item1", | |
62 | "item2", | |
63 | "item3", | |
64 | }, | |
65 | }, | |
66 | }, | |
67 | } | |
68 | ||
69 | // Viper's internal representation | |
70 | var data = map[string]interface{}{ | |
71 | "key": "value", | |
72 | "list": []interface{}{ | |
73 | "item1", | |
74 | "item2", | |
75 | "item3", | |
76 | }, | |
77 | "map": map[string]interface{}{ | |
78 | "key": "value", | |
79 | }, | |
80 | "nested_map": map[string]interface{}{ | |
81 | "map": map[string]interface{}{ | |
82 | "key": "value", | |
83 | "list": []interface{}{ | |
84 | "item1", | |
85 | "item2", | |
86 | "item3", | |
87 | }, | |
88 | }, | |
89 | }, | |
90 | } |
34 | 34 | "time" |
35 | 35 | |
36 | 36 | "github.com/fsnotify/fsnotify" |
37 | "github.com/magiconair/properties" | |
38 | 37 | "github.com/mitchellh/mapstructure" |
39 | 38 | "github.com/spf13/afero" |
40 | 39 | "github.com/spf13/cast" |
41 | 40 | "github.com/spf13/pflag" |
42 | "github.com/subosito/gotenv" | |
43 | "gopkg.in/ini.v1" | |
44 | 41 | |
45 | 42 | "github.com/spf13/viper/internal/encoding" |
43 | "github.com/spf13/viper/internal/encoding/dotenv" | |
46 | 44 | "github.com/spf13/viper/internal/encoding/hcl" |
45 | "github.com/spf13/viper/internal/encoding/ini" | |
46 | "github.com/spf13/viper/internal/encoding/javaproperties" | |
47 | 47 | "github.com/spf13/viper/internal/encoding/json" |
48 | 48 | "github.com/spf13/viper/internal/encoding/toml" |
49 | 49 | "github.com/spf13/viper/internal/encoding/yaml" |
66 | 66 | Error error |
67 | 67 | } |
68 | 68 | |
69 | var ( | |
70 | encoderRegistry = encoding.NewEncoderRegistry() | |
71 | decoderRegistry = encoding.NewDecoderRegistry() | |
72 | ) | |
73 | ||
74 | 69 | func init() { |
75 | 70 | v = New() |
76 | ||
77 | { | |
78 | codec := yaml.Codec{} | |
79 | ||
80 | encoderRegistry.RegisterEncoder("yaml", codec) | |
81 | decoderRegistry.RegisterDecoder("yaml", codec) | |
82 | ||
83 | encoderRegistry.RegisterEncoder("yml", codec) | |
84 | decoderRegistry.RegisterDecoder("yml", codec) | |
85 | } | |
86 | ||
87 | { | |
88 | codec := json.Codec{} | |
89 | ||
90 | encoderRegistry.RegisterEncoder("json", codec) | |
91 | decoderRegistry.RegisterDecoder("json", codec) | |
92 | } | |
93 | ||
94 | { | |
95 | codec := toml.Codec{} | |
96 | ||
97 | encoderRegistry.RegisterEncoder("toml", codec) | |
98 | decoderRegistry.RegisterDecoder("toml", codec) | |
99 | } | |
100 | ||
101 | { | |
102 | codec := hcl.Codec{} | |
103 | ||
104 | encoderRegistry.RegisterEncoder("hcl", codec) | |
105 | decoderRegistry.RegisterDecoder("hcl", codec) | |
106 | ||
107 | encoderRegistry.RegisterEncoder("tfvars", codec) | |
108 | decoderRegistry.RegisterDecoder("tfvars", codec) | |
109 | } | |
110 | 71 | } |
111 | 72 | |
112 | 73 | type remoteConfigFactory interface { |
253 | 214 | aliases map[string]string |
254 | 215 | typeByDefValue bool |
255 | 216 | |
256 | // Store read properties on the object so that we can write back in order with comments. | |
257 | // This will only be used if the configuration read is a properties file. | |
258 | properties *properties.Properties | |
259 | ||
260 | 217 | onConfigChange func(fsnotify.Event) |
261 | 218 | |
262 | 219 | logger Logger |
220 | ||
221 | // TODO: should probably be protected with a mutex | |
222 | encoderRegistry *encoding.EncoderRegistry | |
223 | decoderRegistry *encoding.DecoderRegistry | |
263 | 224 | } |
264 | 225 | |
265 | 226 | // New returns an initialized Viper instance. |
279 | 240 | v.typeByDefValue = false |
280 | 241 | v.logger = jwwLogger{} |
281 | 242 | |
243 | v.resetEncoding() | |
244 | ||
282 | 245 | return v |
283 | 246 | } |
284 | 247 | |
324 | 287 | for _, opt := range opts { |
325 | 288 | opt.apply(v) |
326 | 289 | } |
290 | ||
291 | v.resetEncoding() | |
327 | 292 | |
328 | 293 | return v |
329 | 294 | } |
335 | 300 | v = New() |
336 | 301 | SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "tfvars", "dotenv", "env", "ini"} |
337 | 302 | SupportedRemoteProviders = []string{"etcd", "consul", "firestore"} |
303 | } | |
304 | ||
305 | // TODO: make this lazy initialization instead | |
306 | func (v *Viper) resetEncoding() { | |
307 | encoderRegistry := encoding.NewEncoderRegistry() | |
308 | decoderRegistry := encoding.NewDecoderRegistry() | |
309 | ||
310 | { | |
311 | codec := yaml.Codec{} | |
312 | ||
313 | encoderRegistry.RegisterEncoder("yaml", codec) | |
314 | decoderRegistry.RegisterDecoder("yaml", codec) | |
315 | ||
316 | encoderRegistry.RegisterEncoder("yml", codec) | |
317 | decoderRegistry.RegisterDecoder("yml", codec) | |
318 | } | |
319 | ||
320 | { | |
321 | codec := json.Codec{} | |
322 | ||
323 | encoderRegistry.RegisterEncoder("json", codec) | |
324 | decoderRegistry.RegisterDecoder("json", codec) | |
325 | } | |
326 | ||
327 | { | |
328 | codec := toml.Codec{} | |
329 | ||
330 | encoderRegistry.RegisterEncoder("toml", codec) | |
331 | decoderRegistry.RegisterDecoder("toml", codec) | |
332 | } | |
333 | ||
334 | { | |
335 | codec := hcl.Codec{} | |
336 | ||
337 | encoderRegistry.RegisterEncoder("hcl", codec) | |
338 | decoderRegistry.RegisterDecoder("hcl", codec) | |
339 | ||
340 | encoderRegistry.RegisterEncoder("tfvars", codec) | |
341 | decoderRegistry.RegisterDecoder("tfvars", codec) | |
342 | } | |
343 | ||
344 | { | |
345 | codec := ini.Codec{ | |
346 | KeyDelimiter: v.keyDelim, | |
347 | LoadOptions: v.iniLoadOptions, | |
348 | } | |
349 | ||
350 | encoderRegistry.RegisterEncoder("ini", codec) | |
351 | decoderRegistry.RegisterDecoder("ini", codec) | |
352 | } | |
353 | ||
354 | { | |
355 | codec := &javaproperties.Codec{ | |
356 | KeyDelimiter: v.keyDelim, | |
357 | } | |
358 | ||
359 | encoderRegistry.RegisterEncoder("properties", codec) | |
360 | decoderRegistry.RegisterDecoder("properties", codec) | |
361 | ||
362 | encoderRegistry.RegisterEncoder("props", codec) | |
363 | decoderRegistry.RegisterDecoder("props", codec) | |
364 | ||
365 | encoderRegistry.RegisterEncoder("prop", codec) | |
366 | decoderRegistry.RegisterDecoder("prop", codec) | |
367 | } | |
368 | ||
369 | { | |
370 | codec := &dotenv.Codec{} | |
371 | ||
372 | encoderRegistry.RegisterEncoder("dotenv", codec) | |
373 | decoderRegistry.RegisterDecoder("dotenv", codec) | |
374 | ||
375 | encoderRegistry.RegisterEncoder("env", codec) | |
376 | decoderRegistry.RegisterDecoder("env", codec) | |
377 | } | |
378 | ||
379 | v.encoderRegistry = encoderRegistry | |
380 | v.decoderRegistry = decoderRegistry | |
338 | 381 | } |
339 | 382 | |
340 | 383 | type defaultRemoteProvider struct { |
432 | 475 | v.onConfigChange(event) |
433 | 476 | } |
434 | 477 | } else if filepath.Clean(event.Name) == configFile && |
435 | event.Op&fsnotify.Remove&fsnotify.Remove != 0 { | |
478 | event.Op&fsnotify.Remove != 0 { | |
436 | 479 | eventsWG.Done() |
437 | 480 | return |
438 | 481 | } |
1633 | 1676 | buf.ReadFrom(in) |
1634 | 1677 | |
1635 | 1678 | switch format := strings.ToLower(v.getConfigType()); format { |
1636 | case "yaml", "yml", "json", "toml", "hcl", "tfvars": | |
1637 | err := decoderRegistry.Decode(format, buf.Bytes(), &c) | |
1679 | case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "properties", "props", "prop", "dotenv", "env": | |
1680 | err := v.decoderRegistry.Decode(format, buf.Bytes(), c) | |
1638 | 1681 | if err != nil { |
1639 | 1682 | return ConfigParseError{err} |
1640 | } | |
1641 | ||
1642 | case "dotenv", "env": | |
1643 | env, err := gotenv.StrictParse(buf) | |
1644 | if err != nil { | |
1645 | return ConfigParseError{err} | |
1646 | } | |
1647 | for k, v := range env { | |
1648 | c[k] = v | |
1649 | } | |
1650 | ||
1651 | case "properties", "props", "prop": | |
1652 | v.properties = properties.NewProperties() | |
1653 | var err error | |
1654 | if v.properties, err = properties.Load(buf.Bytes(), properties.UTF8); err != nil { | |
1655 | return ConfigParseError{err} | |
1656 | } | |
1657 | for _, key := range v.properties.Keys() { | |
1658 | value, _ := v.properties.Get(key) | |
1659 | // recursively build nested maps | |
1660 | path := strings.Split(key, ".") | |
1661 | lastKey := strings.ToLower(path[len(path)-1]) | |
1662 | deepestMap := deepSearch(c, path[0:len(path)-1]) | |
1663 | // set innermost value | |
1664 | deepestMap[lastKey] = value | |
1665 | } | |
1666 | ||
1667 | case "ini": | |
1668 | cfg := ini.Empty(v.iniLoadOptions) | |
1669 | err := cfg.Append(buf.Bytes()) | |
1670 | if err != nil { | |
1671 | return ConfigParseError{err} | |
1672 | } | |
1673 | sections := cfg.Sections() | |
1674 | for i := 0; i < len(sections); i++ { | |
1675 | section := sections[i] | |
1676 | keys := section.Keys() | |
1677 | for j := 0; j < len(keys); j++ { | |
1678 | key := keys[j] | |
1679 | value := cfg.Section(section.Name()).Key(key.Name()).String() | |
1680 | c[section.Name()+"."+key.Name()] = value | |
1681 | } | |
1682 | 1683 | } |
1683 | 1684 | } |
1684 | 1685 | |
1690 | 1691 | func (v *Viper) marshalWriter(f afero.File, configType string) error { |
1691 | 1692 | c := v.AllSettings() |
1692 | 1693 | switch configType { |
1693 | case "yaml", "yml", "json", "toml", "hcl", "tfvars": | |
1694 | b, err := encoderRegistry.Encode(configType, c) | |
1694 | case "yaml", "yml", "json", "toml", "hcl", "tfvars", "ini", "prop", "props", "properties", "dotenv", "env": | |
1695 | b, err := v.encoderRegistry.Encode(configType, c) | |
1695 | 1696 | if err != nil { |
1696 | 1697 | return ConfigMarshalError{err} |
1697 | 1698 | } |
1700 | 1701 | if err != nil { |
1701 | 1702 | return ConfigMarshalError{err} |
1702 | 1703 | } |
1703 | ||
1704 | case "prop", "props", "properties": | |
1705 | if v.properties == nil { | |
1706 | v.properties = properties.NewProperties() | |
1707 | } | |
1708 | p := v.properties | |
1709 | for _, key := range v.AllKeys() { | |
1710 | _, _, err := p.Set(key, v.GetString(key)) | |
1711 | if err != nil { | |
1712 | return ConfigMarshalError{err} | |
1713 | } | |
1714 | } | |
1715 | _, err := p.WriteComment(f, "#", properties.UTF8) | |
1716 | if err != nil { | |
1717 | return ConfigMarshalError{err} | |
1718 | } | |
1719 | ||
1720 | case "dotenv", "env": | |
1721 | lines := []string{} | |
1722 | for _, key := range v.AllKeys() { | |
1723 | envName := strings.ToUpper(strings.Replace(key, ".", "_", -1)) | |
1724 | val := v.Get(key) | |
1725 | lines = append(lines, fmt.Sprintf("%v=%v", envName, val)) | |
1726 | } | |
1727 | s := strings.Join(lines, "\n") | |
1728 | if _, err := f.WriteString(s); err != nil { | |
1729 | return ConfigMarshalError{err} | |
1730 | } | |
1731 | ||
1732 | case "ini": | |
1733 | keys := v.AllKeys() | |
1734 | cfg := ini.Empty() | |
1735 | ini.PrettyFormat = false | |
1736 | for i := 0; i < len(keys); i++ { | |
1737 | key := keys[i] | |
1738 | lastSep := strings.LastIndex(key, ".") | |
1739 | sectionName := key[:(lastSep)] | |
1740 | keyName := key[(lastSep + 1):] | |
1741 | if sectionName == "default" { | |
1742 | sectionName = "" | |
1743 | } | |
1744 | cfg.Section(sectionName).Key(keyName).SetValue(v.GetString(key)) | |
1745 | } | |
1746 | cfg.WriteTo(f) | |
1747 | 1704 | } |
1748 | 1705 | return nil |
1749 | 1706 | } |
1822 | 1779 | |
1823 | 1780 | svType := reflect.TypeOf(sv) |
1824 | 1781 | tvType := reflect.TypeOf(tv) |
1825 | if tvType != nil && svType != tvType { // Allow for the target to be nil | |
1826 | v.logger.Error( | |
1827 | "svType != tvType", | |
1828 | "key", sk, | |
1829 | "st", svType, | |
1830 | "tt", tvType, | |
1831 | "sv", sv, | |
1832 | "tv", tv, | |
1833 | ) | |
1834 | continue | |
1835 | } | |
1836 | 1782 | |
1837 | 1783 | v.logger.Trace( |
1838 | 1784 | "processing", |
1846 | 1792 | switch ttv := tv.(type) { |
1847 | 1793 | case map[interface{}]interface{}: |
1848 | 1794 | v.logger.Trace("merging maps (must convert)") |
1849 | tsv := sv.(map[interface{}]interface{}) | |
1795 | tsv, ok := sv.(map[interface{}]interface{}) | |
1796 | if !ok { | |
1797 | v.logger.Error( | |
1798 | "Could not cast sv to map[interface{}]interface{}; key=%s, st=%v, tt=%v, sv=%v, tv=%v", | |
1799 | sk, svType, tvType, sv, tv) | |
1800 | continue | |
1801 | } | |
1802 | ||
1850 | 1803 | ssv := castToMapStringInterface(tsv) |
1851 | 1804 | stv := castToMapStringInterface(ttv) |
1852 | 1805 | mergeMaps(ssv, stv, ttv) |
1853 | 1806 | case map[string]interface{}: |
1854 | 1807 | v.logger.Trace("merging maps") |
1855 | mergeMaps(sv.(map[string]interface{}), ttv, nil) | |
1808 | tsv, ok := sv.(map[string]interface{}) | |
1809 | if !ok { | |
1810 | v.logger.Error( | |
1811 | "Could not cast sv to map[string]interface{}; key=%s, st=%v, tt=%v, sv=%v, tv=%v", | |
1812 | sk, svType, tvType, sv, tv) | |
1813 | continue | |
1814 | } | |
1815 | mergeMaps(tsv, ttv, nil) | |
1856 | 1816 | default: |
1857 | 1817 | v.logger.Trace("setting value") |
1858 | 1818 | tgt[tk] = sv |
32 | 32 | "github.com/spf13/viper/internal/testutil" |
33 | 33 | ) |
34 | 34 | |
35 | var yamlExample = []byte(`Hacker: true | |
36 | name: steve | |
37 | hobbies: | |
38 | - skateboarding | |
39 | - snowboarding | |
40 | - go | |
41 | clothing: | |
42 | jacket: leather | |
43 | trousers: denim | |
44 | pants: | |
45 | size: large | |
46 | age: 35 | |
47 | eyes : brown | |
48 | beard: true | |
49 | `) | |
35 | // var yamlExample = []byte(`Hacker: true | |
36 | // name: steve | |
37 | // hobbies: | |
38 | // - skateboarding | |
39 | // - snowboarding | |
40 | // - go | |
41 | // clothing: | |
42 | // jacket: leather | |
43 | // trousers: denim | |
44 | // pants: | |
45 | // size: large | |
46 | // age: 35 | |
47 | // eyes : brown | |
48 | // beard: true | |
49 | // `) | |
50 | 50 | |
51 | 51 | var yamlExampleWithExtras = []byte(`Existing: true |
52 | 52 | Bogus: true |
1557 | 1557 | p_batters.batter.type = Regular |
1558 | 1558 | `) |
1559 | 1559 | |
1560 | var yamlWriteExpected = []byte(`age: 35 | |
1561 | beard: true | |
1562 | clothing: | |
1563 | jacket: leather | |
1564 | pants: | |
1565 | size: large | |
1566 | trousers: denim | |
1567 | eyes: brown | |
1568 | hacker: true | |
1569 | hobbies: | |
1570 | - skateboarding | |
1571 | - snowboarding | |
1572 | - go | |
1573 | name: steve | |
1574 | `) | |
1560 | // var yamlWriteExpected = []byte(`age: 35 | |
1561 | // beard: true | |
1562 | // clothing: | |
1563 | // jacket: leather | |
1564 | // pants: | |
1565 | // size: large | |
1566 | // trousers: denim | |
1567 | // eyes: brown | |
1568 | // hacker: true | |
1569 | // hobbies: | |
1570 | // - skateboarding | |
1571 | // - snowboarding | |
1572 | // - go | |
1573 | // name: steve | |
1574 | // `) | |
1575 | 1575 | |
1576 | 1576 | func TestWriteConfig(t *testing.T) { |
1577 | 1577 | fs := afero.NewMemMapFs() |
1911 | 1911 | fu: bar |
1912 | 1912 | `) |
1913 | 1913 | |
1914 | var jsonMergeExampleTgt = []byte(` | |
1915 | { | |
1916 | "hello": { | |
1917 | "foo": null, | |
1918 | "pop": 123456 | |
1919 | } | |
1920 | } | |
1921 | `) | |
1922 | ||
1923 | var jsonMergeExampleSrc = []byte(` | |
1924 | { | |
1925 | "hello": { | |
1926 | "foo": "foo str", | |
1927 | "pop": "pop str" | |
1928 | } | |
1929 | } | |
1930 | `) | |
1931 | ||
1914 | 1932 | func TestMergeConfig(t *testing.T) { |
1915 | 1933 | v := New() |
1916 | 1934 | v.SetConfigType("yml") |
1980 | 1998 | |
1981 | 1999 | if fu := v.GetString("fu"); fu != "bar" { |
1982 | 2000 | t.Fatalf("fu != \"bar\", = %s", fu) |
2001 | } | |
2002 | } | |
2003 | ||
2004 | func TestMergeConfigOverrideType(t *testing.T) { | |
2005 | v := New() | |
2006 | v.SetConfigType("json") | |
2007 | if err := v.ReadConfig(bytes.NewBuffer(jsonMergeExampleTgt)); err != nil { | |
2008 | t.Fatal(err) | |
2009 | } | |
2010 | ||
2011 | if err := v.MergeConfig(bytes.NewBuffer(jsonMergeExampleSrc)); err != nil { | |
2012 | t.Fatal(err) | |
2013 | } | |
2014 | ||
2015 | if pop := v.GetString("hello.pop"); pop != "pop str" { | |
2016 | t.Fatalf("pop != \"pop str\", = %s", pop) | |
2017 | } | |
2018 | ||
2019 | if foo := v.GetString("hello.foo"); foo != "foo str" { | |
2020 | t.Fatalf("foo != \"foo str\", = %s", foo) | |
1983 | 2021 | } |
1984 | 2022 | } |
1985 | 2023 | |
2409 | 2447 | assert.Equal(t, "cobra_flag", config.Foo.Bar) |
2410 | 2448 | } |
2411 | 2449 | |
2412 | var yamlExampleWithDot = []byte(`Hacker: true | |
2413 | name: steve | |
2414 | hobbies: | |
2415 | - skateboarding | |
2416 | - snowboarding | |
2417 | - go | |
2418 | clothing: | |
2419 | jacket: leather | |
2420 | trousers: denim | |
2421 | pants: | |
2422 | size: large | |
2423 | age: 35 | |
2424 | eyes : brown | |
2425 | beard: true | |
2426 | emails: | |
2427 | steve@hacker.com: | |
2428 | created: 01/02/03 | |
2429 | active: true | |
2430 | `) | |
2450 | // var yamlExampleWithDot = []byte(`Hacker: true | |
2451 | // name: steve | |
2452 | // hobbies: | |
2453 | // - skateboarding | |
2454 | // - snowboarding | |
2455 | // - go | |
2456 | // clothing: | |
2457 | // jacket: leather | |
2458 | // trousers: denim | |
2459 | // pants: | |
2460 | // size: large | |
2461 | // age: 35 | |
2462 | // eyes : brown | |
2463 | // beard: true | |
2464 | // emails: | |
2465 | // steve@hacker.com: | |
2466 | // created: 01/02/03 | |
2467 | // active: true | |
2468 | // `) | |
2431 | 2469 | |
2432 | 2470 | func TestKeyDelimiter(t *testing.T) { |
2433 | 2471 | v := NewWithOptions(KeyDelimiter("::")) |
0 | //go:build !viper_yaml3 | |
1 | // +build !viper_yaml3 | |
2 | ||
3 | package viper | |
4 | ||
5 | var yamlExample = []byte(`Hacker: true | |
6 | name: steve | |
7 | hobbies: | |
8 | - skateboarding | |
9 | - snowboarding | |
10 | - go | |
11 | clothing: | |
12 | jacket: leather | |
13 | trousers: denim | |
14 | pants: | |
15 | size: large | |
16 | age: 35 | |
17 | eyes : brown | |
18 | beard: true | |
19 | `) | |
20 | ||
21 | var yamlWriteExpected = []byte(`age: 35 | |
22 | beard: true | |
23 | clothing: | |
24 | jacket: leather | |
25 | pants: | |
26 | size: large | |
27 | trousers: denim | |
28 | eyes: brown | |
29 | hacker: true | |
30 | hobbies: | |
31 | - skateboarding | |
32 | - snowboarding | |
33 | - go | |
34 | name: steve | |
35 | `) | |
36 | ||
37 | var yamlExampleWithDot = []byte(`Hacker: true | |
38 | name: steve | |
39 | hobbies: | |
40 | - skateboarding | |
41 | - snowboarding | |
42 | - go | |
43 | clothing: | |
44 | jacket: leather | |
45 | trousers: denim | |
46 | pants: | |
47 | size: large | |
48 | age: 35 | |
49 | eyes : brown | |
50 | beard: true | |
51 | emails: | |
52 | steve@hacker.com: | |
53 | created: 01/02/03 | |
54 | active: true | |
55 | `) |
0 | //go:build viper_yaml3 | |
1 | // +build viper_yaml3 | |
2 | ||
3 | package viper | |
4 | ||
5 | var yamlExample = []byte(`Hacker: true | |
6 | name: steve | |
7 | hobbies: | |
8 | - skateboarding | |
9 | - snowboarding | |
10 | - go | |
11 | clothing: | |
12 | jacket: leather | |
13 | trousers: denim | |
14 | pants: | |
15 | size: large | |
16 | age: 35 | |
17 | eyes : brown | |
18 | beard: true | |
19 | `) | |
20 | ||
21 | var yamlWriteExpected = []byte(`age: 35 | |
22 | beard: true | |
23 | clothing: | |
24 | jacket: leather | |
25 | pants: | |
26 | size: large | |
27 | trousers: denim | |
28 | eyes: brown | |
29 | hacker: true | |
30 | hobbies: | |
31 | - skateboarding | |
32 | - snowboarding | |
33 | - go | |
34 | name: steve | |
35 | `) | |
36 | ||
37 | var yamlExampleWithDot = []byte(`Hacker: true | |
38 | name: steve | |
39 | hobbies: | |
40 | - skateboarding | |
41 | - snowboarding | |
42 | - go | |
43 | clothing: | |
44 | jacket: leather | |
45 | trousers: denim | |
46 | pants: | |
47 | size: large | |
48 | age: 35 | |
49 | eyes : brown | |
50 | beard: true | |
51 | emails: | |
52 | steve@hacker.com: | |
53 | created: 01/02/03 | |
54 | active: true | |
55 | `) |