forked from mirror/viper
Compare commits
329 Commits
bugfix/wat
...
master
Author | SHA1 | Date |
---|---|---|
re | dde12c83d6 | |
re | fc6b005986 | |
re | 113c07be9e | |
dependabot[bot] | 41d75b2770 | |
dependabot[bot] | 38b5494903 | |
Mark Sagi-Kazar | b89e554a96 | |
Mark Sagi-Kazar | db9f89ac41 | |
Mark Sagi-Kazar | 4b8d14881e | |
Mark Sagi-Kazar | 2e99a57324 | |
Mark Sagi-Kazar | dcb7f30f39 | |
Mark Sagi-Kazar | 2e04739b68 | |
Mark Sagi-Kazar | b2234f214f | |
Mark Sagi-Kazar | 52009d3493 | |
dependabot[bot] | b274f639e0 | |
dependabot[bot] | 7c62cfdbac | |
dependabot[bot] | f1d2c470bf | |
dependabot[bot] | 419fd86e49 | |
Mark Sagi-Kazar | 91aa484d1d | |
Mark Sagi-Kazar | 57cc9a000f | |
dependabot[bot] | 8030d5b976 | |
Brad P. Crochet | 312417a0c5 | |
Christian Banse | 202060b3a2 | |
Mark Sagi-Kazar | 97591f0083 | |
Mark Sagi-Kazar | 9af8daeeab | |
Mark Sagi-Kazar | 7b4f2b27cd | |
Mark Sagi-Kazar | 601ec815ba | |
dependabot[bot] | d7f4832bd3 | |
dependabot[bot] | c2f42f3060 | |
Andrew Richardson | 5247643f02 | |
Kevin Franklin Kim | 98c63ede11 | |
Kevin Franklin Kim | 1bc0a5ac7a | |
Sungyun Hur | a4e4f65a0c | |
dependabot[bot] | d55cd57115 | |
dependabot[bot] | 0add84cdf2 | |
dependabot[bot] | 501b966b5d | |
dependabot[bot] | bd03e926b9 | |
mjmaisey | b1b8b3d4d4 | |
dependabot[bot] | 2b83d46130 | |
dependabot[bot] | 22b9573a99 | |
dependabot[bot] | 32d4cbb62f | |
Mark Sagi-Kazar | 4322cf20e9 | |
Mark Sagi-Kazar | 8d0299919d | |
Mark Sagi-Kazar | 7c35aa91d2 | |
Mark Sagi-Kazar | 433821fa47 | |
Mark Sagi-Kazar | 2080d43fa5 | |
Wade Carpenter | da55858fff | |
Michael Wilson | f50ce904a9 | |
dependabot[bot] | 3b836e5088 | |
dependabot[bot] | 5d65186f1e | |
dependabot[bot] | 9f85518c5d | |
dependabot[bot] | ffad330c91 | |
dependabot[bot] | 5f023c7e56 | |
dependabot[bot] | 55a46638f2 | |
dependabot[bot] | 4eb3e0bc2f | |
dependabot[bot] | 81089eeca9 | |
Mark Sagi-Kazar | 5243f253fc | |
Mark Sagi-Kazar | 6986c0ab48 | |
Mark Sagi-Kazar | 65293ecec2 | |
Mark Sagi-Kazar | 6804da723f | |
Mark Sagi-Kazar | 5b21ca137d | |
Mark Sagi-Kazar | 55fac1047e | |
Mark Sagi-Kazar | e0bf4ac05d | |
dependabot[bot] | 973c265115 | |
dependabot[bot] | 129e4f973c | |
dependabot[bot] | 9a8603d8f8 | |
dependabot[bot] | dc76f3c0a9 | |
dependabot[bot] | 343434eb50 | |
dependabot[bot] | 860326d9e7 | |
dependabot[bot] | bba19c61d6 | |
dependabot[bot] | 5986bd9c0c | |
dependabot[bot] | 9f6bc5da85 | |
dependabot[bot] | 9fbab61cfd | |
Mark Sagi-Kazar | daf9560784 | |
Mark Sagi-Kazar | 5f0a660bb4 | |
Illarion Kovalchuk | 97664ba020 | |
illarion Kovalchuk | 0353c6ea50 | |
dependabot[bot] | 788403a47c | |
Mark Sagi-Kazar | 5924acc506 | |
Mark Sagi-Kazar | b13f0963f6 | |
Mark Sagi-Kazar | 98c10c3c31 | |
Mark Sagi-Kazar | f8a13cf704 | |
dependabot[bot] | 9934fe79e7 | |
dependabot[bot] | a0bc8ee4c3 | |
Mark Sagi-Kazar | 7b68d33505 | |
Mark Sagi-Kazar | 04d3a0cb02 | |
Mark Sagi-Kazar | 1d11247e33 | |
Mark Sagi-Kazar | 72453f720e | |
Mark Sagi-Kazar | 858ffb6bd0 | |
Mark Sagi-Kazar | 430936044e | |
Mark Sagi-Kazar | dd62da434f | |
Mark Sagi-Kazar | 38a4fbd769 | |
Mark Sagi-Kazar | e1924e3858 | |
Mark Sagi-Kazar | 4b307cc0f3 | |
Mark Sagi-Kazar | e54e7a53a5 | |
Stephen Wodecki | 6c1745665b | |
Mark Sagi-Kazar | f646c50b18 | |
Mark Sagi-Kazar | a4bfcd9ea0 | |
dependabot[bot] | 1cb6606f6e | |
Mark Sagi-Kazar | a785a79f22 | |
Mark Sagi-Kazar | f1f6b2122c | |
dependabot[bot] | c43197d858 | |
dependabot[bot] | 2abe0ddbd4 | |
Mark Sagi-Kazar | 8ec82f8998 | |
Mark Sagi-Kazar | 35877c8f77 | |
Mark Sagi-Kazar | 655a0aa730 | |
Mark Sagi-Kazar | 946ae75247 | |
Mark Sagi-Kazar | d40d641ca7 | |
Mark Sagi-Kazar | 41ec2aaf27 | |
Mark Sagi-Kazar | ba606cd9f1 | |
dependabot[bot] | 91b237fdbf | |
dependabot[bot] | 793ee22246 | |
Mark Sagi-Kazar | 0594522560 | |
Mark Sagi-Kazar | 14c9dc6a57 | |
Mark Sagi-Kazar | eae2327cb5 | |
Mark Sagi-Kazar | 237f373936 | |
Mark Sagi-Kazar | 78228f56c5 | |
Mark Sagi-Kazar | 8b7777d3c6 | |
Mark Sagi-Kazar | 15c88da25c | |
Mark Sagi-Kazar | 552e3bd0ca | |
Mark Sagi-Kazar | 1a7008fa32 | |
Mark Sagi-Kazar | 077ee305c4 | |
Mark Sagi-Kazar | 5a4e555471 | |
Mark Sagi-Kazar | 08ba8ca7fc | |
Mark Sagi-Kazar | 4e595cec77 | |
Mark Sagi-Kazar | 557c5d64e0 | |
Mark Sagi-Kazar | 8e71595a4a | |
Mark Sagi-Kazar | ce82267a11 | |
Mark Sagi-Kazar | 558a299a01 | |
Jim Razmus II | b1fdc47b0d | |
Mark Sagi-Kazar | 65f16c1738 | |
dependabot[bot] | 0d7e8034ee | |
dependabot[bot] | 6a29539e59 | |
Séra Zoltán | fa3412d7ea | |
Séra Zoltán | a1f26b11bd | |
Séra Zoltán | 46a61e6fbd | |
Vasily Ovchinnikov | e606f7496e | |
Márk Sági-Kazár | 2062cd6ee6 | |
Mark Sagi-Kazar | c4687f7766 | |
Mark Sagi-Kazar | 5a4d2a0519 | |
Mark Sagi-Kazar | 6937864bf6 | |
dependabot[bot] | e897cbf546 | |
连修明 | eb876e1a15 | |
dependabot[bot] | 38e00eefc4 | |
Mark Sagi-Kazar | 5c0d079c4e | |
dependabot[bot] | cf6565fd72 | |
Márk Sági-Kazár | a7cfd8b8e0 | |
Márk Sági-Kazár | 294bb31a4b | |
dependabot[bot] | f2053fabba | |
Mark Sagi-Kazar | ab4b05adc6 | |
Márk Sági-Kazár | e866eaa591 | |
Márk Sági-Kazár | bc5df54485 | |
dependabot[bot] | 409a7ba1d6 | |
Márk Sági-Kazár | 489df740ac | |
Matthieu MOREL | 899b682f8c | |
Mark Sagi-Kazar | 030b739e60 | |
Mark Sagi-Kazar | a02f9864fa | |
Mark Sagi-Kazar | 699d749768 | |
Mark Sagi-Kazar | 6f15444771 | |
Mark Sagi-Kazar | 186266359b | |
Mark Sagi-Kazar | 0e854bf27b | |
Mark Sagi-Kazar | a00caae79f | |
Mikhail f. Shiryaev | bd03865899 | |
lmx-Hexagram | 3fcad43618 | |
Mark Sagi-Kazar | faa8ba0c53 | |
Mark Sagi-Kazar | 65ee98690c | |
Mark Sagi-Kazar | 04ef5fa07d | |
Carlos Henrique Guardão Gandarez | acd965b54e | |
Mark Sagi-Kazar | 5f4d053c3e | |
Mark Sagi-Kazar | dd57ae6279 | |
Carolyn Van Slyck | cdb5e5976f | |
Mark Sagi-Kazar | 36be6bf91f | |
Chris Waldon | 727a41c38a | |
Chris Waldon | cb41ae0ab8 | |
Mark Sagi-Kazar | 7fdb267c73 | |
Mark Sagi-Kazar | 52536944d5 | |
r-stepanenko | fb4eafdd97 | |
bridgetbarnes | 3c7b44f0bc | |
Mark Sagi-Kazar | 4613c4a95f | |
Mark Sagi-Kazar | a86148e76e | |
Mark Sagi-Kazar | e66f940bcc | |
Mark Sagi-Kazar | d10c856f6c | |
Márk Sági-Kazár | da70fee083 | |
Mark Sagi-Kazar | 99da8b22a1 | |
Mark Sagi-Kazar | 811f0e6937 | |
Mark Sagi-Kazar | bba82cfc61 | |
Benjamin Lee | 6ddd35486b | |
Márk Sági-Kazár | 493643fd5e | |
Pablo Santiago Blum de Aguiar | f415025b98 | |
Meysam GanJi | 4938331709 | |
Mark Sagi-Kazar | 8c89438499 | |
dylandreimerink | 33bcdc91ea | |
Mark Sagi-Kazar | 44e6ee8945 | |
Mark Sagi-Kazar | 656b8771b8 | |
Mark Sagi-Kazar | 0a45372c6c | |
Mark Sagi-Kazar | 82c2ddf493 | |
Mark Sagi-Kazar | 16dc0f72ce | |
Mark Sagi-Kazar | f67a901790 | |
Mark Sagi-Kazar | d2df377935 | |
Mark Sagi-Kazar | cfcfed504d | |
Gabriel Aszalos | ea890285a5 | |
Gabriel Aszalos | b655224c01 | |
Mark Sagi-Kazar | b534983313 | |
Mark Sagi-Kazar | f3c095442f | |
Mark Sagi-Kazar | dd2e0fa15f | |
Mark Sagi-Kazar | bed424f7c0 | |
Mark Sagi-Kazar | 29c3027c49 | |
Mark Sagi-Kazar | 6d4eb764b6 | |
Mark Sagi-Kazar | 406ea27dc0 | |
Dan Rollo | a0285163e1 | |
Mark Sagi-Kazar | d9d7dcdc63 | |
Mark Sagi-Kazar | a5152092c6 | |
Mark Sagi-Kazar | ae12c841bc | |
Mark Sagi-Kazar | 387404d518 | |
Mark Sagi-Kazar | f26928cd87 | |
Mark Sagi-Kazar | 9c7144ec1e | |
John Gosset | 3826be3135 | |
João Abecasis | ce534045f9 | |
Oleg Butuzov | 13494e8047 | |
Mark Sagi-Kazar | 13df721090 | |
Trevor Foster | 3856c05f99 | |
flow00 | c6ee9808ab | |
John McBride | c42a305a4b | |
Mark Sagi-Kazar | e34fb51dd7 | |
Mark Sagi-Kazar | 7eea3718bf | |
Mark Sagi-Kazar | aa8e4d4983 | |
Mark Sagi-Kazar | 9b03d15591 | |
Gustavo Bazan | 97ee7adfef | |
Alexey Maslov | b31a49291e | |
Alexey Maslov | 9c81997cb1 | |
Alexey Maslov | 502400c0d9 | |
aeneasr | 7ddaa61d67 | |
Mark Sagi-Kazar | f2cbaea4c2 | |
Mark Sagi-Kazar | df9f4af211 | |
Mark Sagi-Kazar | 1b7d3e05fd | |
Mark Sagi-Kazar | 16fb4b9d29 | |
Mark Sagi-Kazar | 4525543ce4 | |
Pedro Silva | 9cd571279d | |
Mark Sagi-Kazar | 06ab5a4b62 | |
Mark Sagi-Kazar | eabbc68a3e | |
Mark Sagi-Kazar | 5ad4bc05cf | |
Gregory Haynes | bcb420b705 | |
Mark Sagi-Kazar | 4ad4c8df70 | |
Mark Sagi-Kazar | 6fcf985c5a | |
Mark Sagi-Kazar | bdf2db0ff8 | |
Mark Sagi-Kazar | a842b8f618 | |
Mark Sagi-Kazar | 9a405be5c0 | |
Mark Sagi-Kazar | a73303ee89 | |
Mark Sagi-Kazar | b6ced70067 | |
Mark Sagi-Kazar | 52836e66ad | |
Mark Sagi-Kazar | 6895c083d6 | |
Mark Sagi-Kazar | c4fedd192b | |
Mark Sagi-Kazar | 2fd264d3d1 | |
Mark Sagi-Kazar | 8b7fbcaa4b | |
Mark Sagi-Kazar | 29bb3ee94f | |
Mark Sagi-Kazar | 7b5adba788 | |
Mark Sagi-Kazar | cea8b9dfcd | |
Mark Sagi-Kazar | e316012b4d | |
Mark Sagi-Kazar | 78a0e37a24 | |
Mark Sagi-Kazar | fae3a81867 | |
Mark Sagi-Kazar | d52c544291 | |
Mark Sagi-Kazar | abdeaff171 | |
Mark Sagi-Kazar | 8feab54f0e | |
Matti R | 351bfe9719 | |
javaducky | 3a19b6e0d9 | |
javaducky | a708479794 | |
Benoit Masson | 9e353e395e | |
Benoit Masson | 4e1ebbdaba | |
Kévin Dunglas | 01d7d76eb0 | |
Pedro Silva | d1c60d9e69 | |
Vivek V | 72b022eb35 | |
Navid Shaikh | 40e41dd224 | |
Mark Sagi-Kazar | 71509d2887 | |
Mark Sagi-Kazar | 398adc5a7d | |
Mark Sagi-Kazar | 371f39c3ab | |
Mark Sagi-Kazar | c171232d3a | |
Lars Lehtonen | 583f79b3ea | |
inkychris | 99520c81d8 | |
Mark Sagi-Kazar | bd1db6bb8c | |
TwiN | e697d557b7 | |
Christian Muehlhaeuser | 33bf76add3 | |
Christian Muehlhaeuser | 1b33e8258e | |
Christian Muehlhaeuser | d65fa7608b | |
Rodrigo Chiossi | cdccc8152c | |
bpizzi | e02bc9eca5 | |
Nicolas Martin | e325492b82 | |
Márk Sági-Kazár | e6d1c6bc9a | |
CodeLingo Bot | 275a36d0a0 | |
Márk Sági-Kazár | 7fdad0204e | |
AGirard | b8221cf4ee | |
Márk Sági-Kazár | 72cbe340cb | |
lucperkins | 5ae3a072d1 | |
Márk Sági-Kazár | d34be8d9ee | |
Bruce Wang | 96441b4b74 | |
victor23d | 2b4f7d3cde | |
Márk Sági-Kazár | 3349bd9cc2 | |
okazu-dm | 3b4aca7571 | |
liubog2008 | b235f72abb | |
Alpha | fde59dd3e4 | |
Pawan Rawal | 0c777cfac1 | |
Márk Sági-Kazár | ad5ed02fa4 | |
EmilLuta | 0da4d41b2d | |
0xflotus | a52795991d | |
Leonardo Fedalto | 33688bf23c | |
mexisme | 3620d3d9e1 | |
Jean de Klerk | b5bf975e58 | |
Anthony Fok | 2bd2732789 | |
Anthony Fok | 93066f92c6 | |
Mitch Connors | 7a605a50e6 | |
Kris | fccfc2c271 | |
Bjørn Erik Pedersen | 9e56dacc08 | |
Mark Sagi-Kazar | d104d259b3 | |
Bjørn Erik Pedersen | 6d33b5a963 | |
Bjørn Erik Pedersen | 41cd1c3aa3 | |
Bjørn Erik Pedersen | 3535c75fa8 | |
Bjørn Erik Pedersen | 06c7c0d9b3 | |
Benoit Masson | ae103d7e59 | |
Benoit Masson | 69647fb422 | |
Benoit Masson | cc7e906d88 | |
Márk Sági-Kazár | b7a3b95476 | |
Andrew Stuart | 62edee3196 | |
Panagiotis Moustafellos | b56071875a | |
Andrew Stuart | 8e194e8ad2 | |
Andrew Stuart | 0d783e7344 | |
Andrew Stuart | 2c12c60302 | |
Xavier Coulon | 41f829b2c9 | |
Xavier Coulon | e12d3d32d1 | |
Xavier Coulon | c1250e5dd7 | |
Xavier Coulon | 242f4890f5 | |
Xavier Coulon | e0f7631cf3 |
|
@ -0,0 +1,15 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[{Makefile,*.mk}]
|
||||||
|
indent_style = tab
|
|
@ -0,0 +1,2 @@
|
||||||
|
[{*.yml,*.yaml}]
|
||||||
|
indent_size = 2
|
|
@ -0,0 +1,119 @@
|
||||||
|
name: 🐛 Bug report
|
||||||
|
description: Report a bug to help us improve Viper
|
||||||
|
labels: [kind/bug]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for submitting a bug report!
|
||||||
|
|
||||||
|
Please fill out the template below to make it easier to debug your problem.
|
||||||
|
|
||||||
|
If you are not sure if it is a bug or not, you can contact us via the available [support channels](https://git.internal/re/viper/issues/new/choose).
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Preflight Checklist
|
||||||
|
description: Please ensure you've completed all of the following.
|
||||||
|
options:
|
||||||
|
- label: I have searched the [issue tracker](https://www.git.internal/re/viper/issues) for an issue that matches the one I want to file, without success.
|
||||||
|
required: true
|
||||||
|
- label: I am not looking for support or already pursued the available [support channels](https://git.internal/re/viper/issues/new/choose) without success.
|
||||||
|
required: true
|
||||||
|
- label: I have checked the [troubleshooting guide](https://git.internal/re/viper/blob/master/TROUBLESHOOTING.md) for my problem, without success.
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Viper Version
|
||||||
|
description: What version of Viper are you using?
|
||||||
|
placeholder: 1.8.1
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Go Version
|
||||||
|
description: What version of Go are you using?
|
||||||
|
placeholder: "1.16"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Config Source
|
||||||
|
description: What sources do you load configuration from?
|
||||||
|
options:
|
||||||
|
- Manual set
|
||||||
|
- Flags
|
||||||
|
- Environment variables
|
||||||
|
- Files
|
||||||
|
- Remove K/V stores
|
||||||
|
- Defaults
|
||||||
|
multiple: true
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Format
|
||||||
|
description: Which file formats do you use?
|
||||||
|
options:
|
||||||
|
- JSON
|
||||||
|
- YAML
|
||||||
|
- TOML
|
||||||
|
- Dotenv
|
||||||
|
- HCL
|
||||||
|
- Java properties
|
||||||
|
- INI
|
||||||
|
- Other (specify below)
|
||||||
|
multiple: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Repl.it link
|
||||||
|
description: Complete example on Repl.it reproducing the issue. [Here](https://repl.it/@sagikazarmark/Viper-example) is an example you can use.
|
||||||
|
placeholder: https://repl.it/@sagikazarmark/Viper-example
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Code reproducing the issue
|
||||||
|
description: Please provide a Repl.it link if possible.
|
||||||
|
render: go
|
||||||
|
placeholder: |
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.internal/re/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
v := viper.New()
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
|
||||||
|
err = v.Unmarshal(&config)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: A clear and concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Actual Behavior
|
||||||
|
description: A clear description of what actually happens.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps To Reproduce
|
||||||
|
description: Steps to reproduce the behavior if it is not self-explanatory.
|
||||||
|
placeholder: |
|
||||||
|
1. In this environment...
|
||||||
|
2. With this config...
|
||||||
|
3. Run '...'
|
||||||
|
4. See error...
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
description: Links? References? Anything that will give us more context about the issue that you are encountering!
|
|
@ -0,0 +1,13 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: ❓ Ask a question
|
||||||
|
url: https://git.internal/re/viper/discussions/new?category=q-a
|
||||||
|
about: Ask and discuss questions with other Viper community members
|
||||||
|
|
||||||
|
- name: 📓 Reference
|
||||||
|
url: https://pkg.go.dev/mod/git.internal/re/viper
|
||||||
|
about: Check the Go code reference
|
||||||
|
|
||||||
|
- name: 💬 Slack channel
|
||||||
|
url: https://gophers.slack.com/messages/viper
|
||||||
|
about: Please ask and answer questions here
|
|
@ -0,0 +1,39 @@
|
||||||
|
name: 🎉 Feature request
|
||||||
|
description: Suggest an idea for Viper
|
||||||
|
labels: [kind/enhancement]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for submitting a feature request!
|
||||||
|
|
||||||
|
Please describe what you would like to change/add and why in detail by filling out the template below.
|
||||||
|
|
||||||
|
If you are not sure if your request fits into Viper, you can contact us via the available [support channels](https://git.internal/re/viper/issues/new/choose).
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Preflight Checklist
|
||||||
|
description: Please ensure you've completed all of the following.
|
||||||
|
options:
|
||||||
|
- label: I have searched the [issue tracker](https://www.git.internal/re/viper/issues) for an issue that matches the one I want to file, without success.
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Problem Description
|
||||||
|
description: A clear and concise description of the problem you are seeking to solve with this feature request.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Proposed Solution
|
||||||
|
description: A clear and concise description of what would you like to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Alternatives Considered
|
||||||
|
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional Information
|
||||||
|
description: Add any other context about the problem here.
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!--
|
||||||
|
Thank you for sending a pull request! Here some tips for contributors:
|
||||||
|
|
||||||
|
1. Fill the description template below.
|
||||||
|
2. Include appropriate tests (if necessary). Make sure that all CI checks passed.
|
||||||
|
3. If the Pull Request is a work in progress, make use of GitHub's "Draft PR" feature and mark it as such.
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Overview**:
|
||||||
|
<!-- Describe your changes briefly here. -->
|
||||||
|
|
||||||
|
**What problem does it solve?**:
|
||||||
|
<!--
|
||||||
|
- Please state in detail why we need this PR and what it solves.
|
||||||
|
- If your PR closes some of the existing issues, please add links to them here.
|
||||||
|
Mentioned issues will be automatically closed.
|
||||||
|
Usage: "Closes #<issue number>", or "Closes (paste link of issue)"
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Special notes for a reviewer**:
|
|
@ -0,0 +1,16 @@
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: gomod
|
||||||
|
directory: /
|
||||||
|
labels:
|
||||||
|
- area/dependencies
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
||||||
|
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
labels:
|
||||||
|
- area/dependencies
|
||||||
|
schedule:
|
||||||
|
interval: daily
|
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -0,0 +1,30 @@
|
||||||
|
changelog:
|
||||||
|
exclude:
|
||||||
|
labels:
|
||||||
|
- release-note/ignore
|
||||||
|
categories:
|
||||||
|
- title: Exciting New Features 🎉
|
||||||
|
labels:
|
||||||
|
- kind/feature
|
||||||
|
- release-note/new-feature
|
||||||
|
- title: Enhancements 🚀
|
||||||
|
labels:
|
||||||
|
- kind/enhancement
|
||||||
|
- release-note/enhancement
|
||||||
|
- title: Bug Fixes 🐛
|
||||||
|
labels:
|
||||||
|
- kind/bug
|
||||||
|
- release-note/bug-fix
|
||||||
|
- title: Breaking Changes 🛠
|
||||||
|
labels:
|
||||||
|
- release-note/breaking-change
|
||||||
|
- title: Deprecations ❌
|
||||||
|
labels:
|
||||||
|
- release-note/deprecation
|
||||||
|
- title: Dependency Updates ⬆️
|
||||||
|
labels:
|
||||||
|
- area/dependencies
|
||||||
|
- release-note/dependency-update
|
||||||
|
- title: Other Changes
|
||||||
|
labels:
|
||||||
|
- "*"
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: PR Checks
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, labeled, unlabeled, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-label:
|
||||||
|
name: Release note label
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check minimum labels
|
||||||
|
uses: mheap/github-action-required-labels@v2
|
||||||
|
with:
|
||||||
|
mode: minimum
|
||||||
|
count: 1
|
||||||
|
labels: "release-note/ignore, kind/feature, release-note/new-feature, kind/enhancement, release-note/enhancement, kind/bug, release-note/bug-fix, release-note/breaking-change, release-note/deprecation, area/dependencies, release-note/dependency-update"
|
|
@ -0,0 +1,86 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- goos: js
|
||||||
|
goarch: wasm
|
||||||
|
- goos: aix
|
||||||
|
goarch: ppc64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.19
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: go build .
|
||||||
|
env:
|
||||||
|
GOOS: ${{ matrix.goos }}
|
||||||
|
GOARCH: ${{ matrix.goarch }}
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
# Fail fast is disabled because there are Go version specific features and tests
|
||||||
|
# that should be able to fail independently.
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
go: ['1.16', '1.17', '1.18', '1.19']
|
||||||
|
tags: ['', 'viper_yaml2', 'viper_toml1']
|
||||||
|
env:
|
||||||
|
GOFLAGS: -mod=readonly
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -race -tags '${{ matrix.tags }}' -v ./...
|
||||||
|
if: runner.os != 'Windows'
|
||||||
|
|
||||||
|
- name: Test (without race detector)
|
||||||
|
run: go test -tags '${{ matrix.tags }}' -v ./...
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
GOFLAGS: -mod=readonly
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.19
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
uses: golangci/golangci-lint-action@v3
|
||||||
|
with:
|
||||||
|
version: v1.49
|
|
@ -0,0 +1,72 @@
|
||||||
|
# For most projects, this workflow file will not need changing; you simply need
|
||||||
|
# to commit it to your repository.
|
||||||
|
#
|
||||||
|
# You may wish to alter this file to override the set of languages analyzed,
|
||||||
|
# or to provide custom queries or build logic.
|
||||||
|
#
|
||||||
|
# ******** NOTE ********
|
||||||
|
# We have attempted to detect the languages in your repository. Please check
|
||||||
|
# the `language` matrix defined below to confirm you have the correct set of
|
||||||
|
# supported CodeQL languages.
|
||||||
|
#
|
||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '22 16 * * 3'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: `👋 Thanks for reporting!
|
||||||
|
|
||||||
|
A maintainer will take a look at your issue shortly. 👀
|
||||||
|
|
||||||
|
In the meantime: We are working on **Viper v2** and we would love to hear your thoughts about what you like or don't like about Viper, so we can improve or fix those issues.
|
||||||
|
|
||||||
|
⏰ If you have a couple minutes, please take some time and share your thoughts: https://forms.gle/R6faU74qPRPAzchZ9
|
||||||
|
|
||||||
|
📣 If you've already given us your feedback, you can still help by spreading the news,
|
||||||
|
either by sharing the above link or telling people about this on Twitter:
|
||||||
|
|
||||||
|
https://twitter.com/sagikazarmark/status/1306904078967074816
|
||||||
|
|
||||||
|
**Thank you!** ❤️
|
||||||
|
`,
|
||||||
|
})
|
|
@ -0,0 +1,32 @@
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
github.rest.issues.createComment({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: `👋 Thanks for contributing to Viper! You are awesome! 🎉
|
||||||
|
|
||||||
|
A maintainer will take a look at your pull request shortly. 👀
|
||||||
|
|
||||||
|
In the meantime: We are working on **Viper v2** and we would love to hear your thoughts about what you like or don't like about Viper, so we can improve or fix those issues.
|
||||||
|
|
||||||
|
⏰ If you have a couple minutes, please take some time and share your thoughts: https://forms.gle/R6faU74qPRPAzchZ9
|
||||||
|
|
||||||
|
📣 If you've already given us your feedback, you can still help by spreading the news,
|
||||||
|
either by sharing the above link or telling people about this on Twitter:
|
||||||
|
|
||||||
|
https://twitter.com/sagikazarmark/status/1306904078967074816
|
||||||
|
|
||||||
|
**Thank you!** ❤️
|
||||||
|
`,
|
||||||
|
})
|
|
@ -1,24 +1,5 @@
|
||||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
/.idea/
|
||||||
*.o
|
/bin/
|
||||||
*.a
|
/build/
|
||||||
*.so
|
/var/
|
||||||
|
/vendor/
|
||||||
# Folders
|
|
||||||
_obj
|
|
||||||
_test
|
|
||||||
|
|
||||||
# Architecture specific extensions/prefixes
|
|
||||||
*.[568vq]
|
|
||||||
[568vq].out
|
|
||||||
|
|
||||||
*.cgo1.go
|
|
||||||
*.cgo2.c
|
|
||||||
_cgo_defun.c
|
|
||||||
_cgo_gotypes.go
|
|
||||||
_cgo_export.*
|
|
||||||
|
|
||||||
_testmain.go
|
|
||||||
|
|
||||||
*.exe
|
|
||||||
*.test
|
|
||||||
*.bench
|
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
sections:
|
||||||
|
- standard
|
||||||
|
- default
|
||||||
|
- prefix(git.internal/re/viper)
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
goimports:
|
||||||
|
local-prefixes: git.internal/re/viper
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- bodyclose
|
||||||
|
- deadcode
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- durationcheck
|
||||||
|
- exhaustive
|
||||||
|
- exportloopref
|
||||||
|
- gci
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- gomoddirectives
|
||||||
|
- goprintffuncname
|
||||||
|
- govet
|
||||||
|
- importas
|
||||||
|
- ineffassign
|
||||||
|
- makezero
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nilerr
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- prealloc
|
||||||
|
- predeclared
|
||||||
|
- revive
|
||||||
|
- rowserrcheck
|
||||||
|
- sqlclosecheck
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- stylecheck
|
||||||
|
- tparallel
|
||||||
|
- typecheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
- wastedassign
|
||||||
|
- whitespace
|
||||||
|
|
||||||
|
# fixme
|
||||||
|
# - cyclop
|
||||||
|
# - errcheck
|
||||||
|
# - errorlint
|
||||||
|
# - exhaustivestruct
|
||||||
|
# - forbidigo
|
||||||
|
# - forcetypeassert
|
||||||
|
# - gochecknoglobals
|
||||||
|
# - gochecknoinits
|
||||||
|
# - gocognit
|
||||||
|
# - goconst
|
||||||
|
# - gocritic
|
||||||
|
# - gocyclo
|
||||||
|
# - godot
|
||||||
|
# - gosec
|
||||||
|
# - gosimple
|
||||||
|
# - ifshort
|
||||||
|
# - lll
|
||||||
|
# - nlreturn
|
||||||
|
# - paralleltest
|
||||||
|
# - scopelint
|
||||||
|
# - thelper
|
||||||
|
# - wrapcheck
|
||||||
|
|
||||||
|
# unused
|
||||||
|
# - depguard
|
||||||
|
# - goheader
|
||||||
|
# - gomodguard
|
||||||
|
|
||||||
|
# don't enable:
|
||||||
|
# - asciicheck
|
||||||
|
# - funlen
|
||||||
|
# - godox
|
||||||
|
# - goerr113
|
||||||
|
# - gomnd
|
||||||
|
# - interfacer
|
||||||
|
# - maligned
|
||||||
|
# - nestif
|
||||||
|
# - testpackage
|
||||||
|
# - wsl
|
27
.travis.yml
27
.travis.yml
|
@ -1,27 +0,0 @@
|
||||||
go_import_path: github.com/spf13/viper
|
|
||||||
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
- tip
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
fast_finish: true
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go install ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go test -v ./...
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- go get -u -d github.com/spf13/hugo
|
|
||||||
- cd $GOPATH/src/github.com/spf13/hugo && make && ./hugo -s docs && cd -
|
|
||||||
|
|
||||||
sudo: false
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html
|
||||||
|
|
||||||
|
OS = $(shell uname | tr A-Z a-z)
|
||||||
|
export PATH := $(abspath bin/):${PATH}
|
||||||
|
|
||||||
|
# Build variables
|
||||||
|
BUILD_DIR ?= build
|
||||||
|
export CGO_ENABLED ?= 0
|
||||||
|
export GOOS = $(shell go env GOOS)
|
||||||
|
ifeq (${VERBOSE}, 1)
|
||||||
|
ifeq ($(filter -v,${GOARGS}),)
|
||||||
|
GOARGS += -v
|
||||||
|
endif
|
||||||
|
TEST_FORMAT = short-verbose
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Dependency versions
|
||||||
|
GOTESTSUM_VERSION = 1.8.0
|
||||||
|
GOLANGCI_VERSION = 1.49.0
|
||||||
|
|
||||||
|
# Add the ability to override some variables
|
||||||
|
# Use with care
|
||||||
|
-include override.mk
|
||||||
|
|
||||||
|
.PHONY: clear
|
||||||
|
clear: ## Clear the working area and the project
|
||||||
|
rm -rf bin/
|
||||||
|
|
||||||
|
.PHONY: check
|
||||||
|
check: test lint ## Run tests and linters
|
||||||
|
|
||||||
|
bin/gotestsum: bin/gotestsum-${GOTESTSUM_VERSION}
|
||||||
|
@ln -sf gotestsum-${GOTESTSUM_VERSION} bin/gotestsum
|
||||||
|
bin/gotestsum-${GOTESTSUM_VERSION}:
|
||||||
|
@mkdir -p bin
|
||||||
|
curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_${OS}_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum-${GOTESTSUM_VERSION} && chmod +x ./bin/gotestsum-${GOTESTSUM_VERSION}
|
||||||
|
|
||||||
|
TEST_PKGS ?= ./...
|
||||||
|
.PHONY: test
|
||||||
|
test: TEST_FORMAT ?= short
|
||||||
|
test: SHELL = /bin/bash
|
||||||
|
test: export CGO_ENABLED=1
|
||||||
|
test: bin/gotestsum ## Run tests
|
||||||
|
@mkdir -p ${BUILD_DIR}
|
||||||
|
bin/gotestsum --no-summary=skipped --junitfile ${BUILD_DIR}/coverage.xml --format ${TEST_FORMAT} -- -race -coverprofile=${BUILD_DIR}/coverage.txt -covermode=atomic $(filter-out -v,${GOARGS}) $(if ${TEST_PKGS},${TEST_PKGS},./...)
|
||||||
|
|
||||||
|
bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION}
|
||||||
|
@ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint
|
||||||
|
bin/golangci-lint-${GOLANGCI_VERSION}:
|
||||||
|
@mkdir -p bin
|
||||||
|
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION}
|
||||||
|
@mv bin/golangci-lint "$@"
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: bin/golangci-lint ## Run linter
|
||||||
|
bin/golangci-lint run
|
||||||
|
|
||||||
|
.PHONY: fix
|
||||||
|
fix: bin/golangci-lint ## Fix lint violations
|
||||||
|
bin/golangci-lint run --fix
|
||||||
|
|
||||||
|
# Add custom targets here
|
||||||
|
-include custom.mk
|
||||||
|
|
||||||
|
.PHONY: list
|
||||||
|
list: ## List all make targets
|
||||||
|
@${MAKE} -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | sort
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
help:
|
||||||
|
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
|
||||||
|
|
||||||
|
# Variable outputting/exporting rules
|
||||||
|
var-%: ; @echo $($*)
|
||||||
|
varexport-%: ; @echo $*=$($*)
|
379
README.md
379
README.md
|
@ -1,6 +1,20 @@
|
||||||
![viper logo](https://cloud.githubusercontent.com/assets/173412/10886745/998df88a-8151-11e5-9448-4736db51020d.png)
|
> ## Viper v2 feedback
|
||||||
|
> Viper is heading towards v2 and we would love to hear what _**you**_ would like to see in it. Share your thoughts here: https://forms.gle/R6faU74qPRPAzchZ9
|
||||||
|
>
|
||||||
|
> **Thank you!**
|
||||||
|
|
||||||
Go configuration with fangs!
|
![Viper](.github/logo.png?raw=true)
|
||||||
|
|
||||||
|
[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#configuration)
|
||||||
|
[![run on repl.it](https://repl.it/badge/github/sagikazarmark/Viper-example)](https://repl.it/@sagikazarmark/Viper-example#main.go)
|
||||||
|
|
||||||
|
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/spf13/viper/CI?style=flat-square)](https://git.internal/re/viper/actions?query=workflow%3ACI)
|
||||||
|
[![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/git.internal/re/viper?style=flat-square)](https://goreportcard.com/report/git.internal/re/viper)
|
||||||
|
![Go Version](https://img.shields.io/badge/go%20version-%3E=1.16-61CFDD.svg?style=flat-square)
|
||||||
|
[![PkgGoDev](https://pkg.go.dev/badge/mod/git.internal/re/viper)](https://pkg.go.dev/mod/git.internal/re/viper)
|
||||||
|
|
||||||
|
**Go configuration with fangs!**
|
||||||
|
|
||||||
Many Go projects are built using Viper including:
|
Many Go projects are built using Viper including:
|
||||||
|
|
||||||
|
@ -12,8 +26,16 @@ Many Go projects are built using Viper including:
|
||||||
* [BloomApi](https://www.bloomapi.com/)
|
* [BloomApi](https://www.bloomapi.com/)
|
||||||
* [doctl](https://github.com/digitalocean/doctl)
|
* [doctl](https://github.com/digitalocean/doctl)
|
||||||
* [Clairctl](https://github.com/jgsqware/clairctl)
|
* [Clairctl](https://github.com/jgsqware/clairctl)
|
||||||
|
* [Mercure](https://mercure.rocks)
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) [![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/spf13/viper?status.svg)](https://godoc.org/github.com/spf13/viper)
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go get git.internal/re/viper
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Viper uses [Go Modules](https://github.com/golang/go/wiki/Modules) to manage dependencies.
|
||||||
|
|
||||||
|
|
||||||
## What is Viper?
|
## What is Viper?
|
||||||
|
@ -23,7 +45,7 @@ to work within an application, and can handle all types of configuration needs
|
||||||
and formats. It supports:
|
and formats. It supports:
|
||||||
|
|
||||||
* setting defaults
|
* setting defaults
|
||||||
* reading from JSON, TOML, YAML, HCL, and Java properties config files
|
* reading from JSON, TOML, YAML, HCL, envfile and Java properties config files
|
||||||
* live watching and re-reading of config files (optional)
|
* live watching and re-reading of config files (optional)
|
||||||
* reading from environment variables
|
* reading from environment variables
|
||||||
* reading from remote config systems (etcd or Consul), and watching changes
|
* reading from remote config systems (etcd or Consul), and watching changes
|
||||||
|
@ -31,8 +53,8 @@ and formats. It supports:
|
||||||
* reading from buffer
|
* reading from buffer
|
||||||
* setting explicit values
|
* setting explicit values
|
||||||
|
|
||||||
Viper can be thought of as a registry for all of your applications
|
Viper can be thought of as a registry for all of your applications configuration needs.
|
||||||
configuration needs.
|
|
||||||
|
|
||||||
## Why Viper?
|
## Why Viper?
|
||||||
|
|
||||||
|
@ -42,34 +64,31 @@ Viper is here to help with that.
|
||||||
|
|
||||||
Viper does the following for you:
|
Viper does the following for you:
|
||||||
|
|
||||||
1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, or Java properties formats.
|
1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, envfile or Java properties formats.
|
||||||
2. Provide a mechanism to set default values for your different
|
2. Provide a mechanism to set default values for your different configuration options.
|
||||||
configuration options.
|
3. Provide a mechanism to set override values for options specified through command line flags.
|
||||||
3. Provide a mechanism to set override values for options specified through
|
4. Provide an alias system to easily rename parameters without breaking existing code.
|
||||||
command line flags.
|
5. Make it easy to tell the difference between when a user has provided a command line or config file which is the same as the default.
|
||||||
4. Provide an alias system to easily rename parameters without breaking existing
|
|
||||||
code.
|
|
||||||
5. Make it easy to tell the difference between when a user has provided a
|
|
||||||
command line or config file which is the same as the default.
|
|
||||||
|
|
||||||
Viper uses the following precedence order. Each item takes precedence over the
|
Viper uses the following precedence order. Each item takes precedence over the item below it:
|
||||||
item below it:
|
|
||||||
|
|
||||||
* explicit call to Set
|
* explicit call to `Set`
|
||||||
* flag
|
* flag
|
||||||
* env
|
* env
|
||||||
* config
|
* config
|
||||||
* key/value store
|
* key/value store
|
||||||
* default
|
* default
|
||||||
|
|
||||||
Viper configuration keys are case insensitive.
|
**Important:** Viper configuration keys are case insensitive.
|
||||||
|
There are ongoing discussions about making that optional.
|
||||||
|
|
||||||
|
|
||||||
## Putting Values into Viper
|
## Putting Values into Viper
|
||||||
|
|
||||||
### Establishing Defaults
|
### Establishing Defaults
|
||||||
|
|
||||||
A good configuration system will support default values. A default value is not
|
A good configuration system will support default values. A default value is not
|
||||||
required for a key, but it’s useful in the event that a key hasn’t been set via
|
required for a key, but it’s useful in the event that a key hasn't been set via
|
||||||
config file, environment variable, remote configuration or flag.
|
config file, environment variable, remote configuration or flag.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
@ -83,7 +102,7 @@ viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "cat
|
||||||
### Reading Config Files
|
### Reading Config Files
|
||||||
|
|
||||||
Viper requires minimal configuration so it knows where to look for config files.
|
Viper requires minimal configuration so it knows where to look for config files.
|
||||||
Viper supports JSON, TOML, YAML, HCL, and Java Properties files. Viper can search multiple paths, but
|
Viper supports JSON, TOML, YAML, HCL, INI, envfile and Java Properties files. Viper can search multiple paths, but
|
||||||
currently a single Viper instance only supports a single configuration file.
|
currently a single Viper instance only supports a single configuration file.
|
||||||
Viper does not default to any configuration search paths leaving defaults decision
|
Viper does not default to any configuration search paths leaving defaults decision
|
||||||
to an application.
|
to an application.
|
||||||
|
@ -94,15 +113,54 @@ where a configuration file is expected.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
viper.SetConfigName("config") // name of config file (without extension)
|
viper.SetConfigName("config") // name of config file (without extension)
|
||||||
|
viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
|
||||||
viper.AddConfigPath("/etc/appname/") // path to look for the config file in
|
viper.AddConfigPath("/etc/appname/") // path to look for the config file in
|
||||||
viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths
|
viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths
|
||||||
viper.AddConfigPath(".") // optionally look for config in the working directory
|
viper.AddConfigPath(".") // optionally look for config in the working directory
|
||||||
err := viper.ReadInConfig() // Find and read the config file
|
err := viper.ReadInConfig() // Find and read the config file
|
||||||
if err != nil { // Handle errors reading the config file
|
if err != nil { // Handle errors reading the config file
|
||||||
panic(fmt.Errorf("Fatal error config file: %s \n", err))
|
panic(fmt.Errorf("fatal error config file: %w", err))
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can handle the specific case where no config file is found like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
|
||||||
|
// Config file not found; ignore error if desired
|
||||||
|
} else {
|
||||||
|
// Config file was found but another error was produced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config file found and successfully parsed
|
||||||
|
```
|
||||||
|
|
||||||
|
*NOTE [since 1.6]:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc`
|
||||||
|
|
||||||
|
### Writing Config Files
|
||||||
|
|
||||||
|
Reading from config files is useful, but at times you want to store all modifications made at run time.
|
||||||
|
For that, a bunch of commands are available, each with its own purpose:
|
||||||
|
|
||||||
|
* WriteConfig - writes the current viper configuration to the predefined path, if exists. Errors if no predefined path. Will overwrite the current config file, if it exists.
|
||||||
|
* SafeWriteConfig - writes the current viper configuration to the predefined path. Errors if no predefined path. Will not overwrite the current config file, if it exists.
|
||||||
|
* WriteConfigAs - writes the current viper configuration to the given filepath. Will overwrite the given file, if it exists.
|
||||||
|
* SafeWriteConfigAs - writes the current viper configuration to the given filepath. Will not overwrite the given file, if it exists.
|
||||||
|
|
||||||
|
As a rule of the thumb, everything marked with safe won't overwrite any file, but just create if not existent, whilst the default behavior is to create or truncate.
|
||||||
|
|
||||||
|
A small examples section:
|
||||||
|
|
||||||
|
```go
|
||||||
|
viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName'
|
||||||
|
viper.SafeWriteConfig()
|
||||||
|
viper.WriteConfigAs("/path/to/my/.config")
|
||||||
|
viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written
|
||||||
|
viper.SafeWriteConfigAs("/path/to/my/.other_config")
|
||||||
|
```
|
||||||
|
|
||||||
### Watching and re-reading config files
|
### Watching and re-reading config files
|
||||||
|
|
||||||
Viper supports the ability to have your application live read a config file while running.
|
Viper supports the ability to have your application live read a config file while running.
|
||||||
|
@ -117,10 +175,10 @@ Optionally you can provide a function for Viper to run each time a change occurs
|
||||||
**Make sure you add all of the configPaths prior to calling `WatchConfig()`**
|
**Make sure you add all of the configPaths prior to calling `WatchConfig()`**
|
||||||
|
|
||||||
```go
|
```go
|
||||||
viper.WatchConfig()
|
|
||||||
viper.OnConfigChange(func(e fsnotify.Event) {
|
viper.OnConfigChange(func(e fsnotify.Event) {
|
||||||
fmt.Println("Config file changed:", e.Name)
|
fmt.Println("Config file changed:", e.Name)
|
||||||
})
|
})
|
||||||
|
viper.WatchConfig()
|
||||||
```
|
```
|
||||||
|
|
||||||
### Reading Config from io.Reader
|
### Reading Config from io.Reader
|
||||||
|
@ -179,13 +237,14 @@ viper.GetBool("verbose") // true
|
||||||
### Working with Environment Variables
|
### Working with Environment Variables
|
||||||
|
|
||||||
Viper has full support for environment variables. This enables 12 factor
|
Viper has full support for environment variables. This enables 12 factor
|
||||||
applications out of the box. There are four methods that exist to aid working
|
applications out of the box. There are five methods that exist to aid working
|
||||||
with ENV:
|
with ENV:
|
||||||
|
|
||||||
* `AutomaticEnv()`
|
* `AutomaticEnv()`
|
||||||
* `BindEnv(string...) : error`
|
* `BindEnv(string...) : error`
|
||||||
* `SetEnvPrefix(string)`
|
* `SetEnvPrefix(string)`
|
||||||
* `SetEnvKeyReplacer(string...) *strings.Replacer`
|
* `SetEnvKeyReplacer(string...) *strings.Replacer`
|
||||||
|
* `AllowEmptyEnv(bool)`
|
||||||
|
|
||||||
_When working with ENV variables, it’s important to recognize that Viper
|
_When working with ENV variables, it’s important to recognize that Viper
|
||||||
treats ENV variables as case sensitive._
|
treats ENV variables as case sensitive._
|
||||||
|
@ -195,12 +254,13 @@ using `SetEnvPrefix`, you can tell Viper to use a prefix while reading from
|
||||||
the environment variables. Both `BindEnv` and `AutomaticEnv` will use this
|
the environment variables. Both `BindEnv` and `AutomaticEnv` will use this
|
||||||
prefix.
|
prefix.
|
||||||
|
|
||||||
`BindEnv` takes one or two parameters. The first parameter is the key name, the
|
`BindEnv` takes one or more parameters. The first parameter is the key name, the
|
||||||
second is the name of the environment variable. The name of the environment
|
rest are the name of the environment variables to bind to this key. If more than
|
||||||
variable is case sensitive. If the ENV variable name is not provided, then
|
one are provided, they will take precedence in the specified order. The name of
|
||||||
Viper will automatically assume that the key name matches the ENV variable name,
|
the environment variable is case sensitive. If the ENV variable name is not provided, then
|
||||||
but the ENV variable is IN ALL CAPS. When you explicitly provide the ENV
|
Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter),
|
||||||
variable name, it **does not** automatically add the prefix.
|
it **does not** automatically add the prefix. For example if the second parameter is "id",
|
||||||
|
Viper will look for the ENV variable "ID".
|
||||||
|
|
||||||
One important thing to recognize when working with ENV variables is that the
|
One important thing to recognize when working with ENV variables is that the
|
||||||
value will be read each time it is accessed. Viper does not fix the value when
|
value will be read each time it is accessed. Viper does not fix the value when
|
||||||
|
@ -209,7 +269,7 @@ the `BindEnv` is called.
|
||||||
`AutomaticEnv` is a powerful helper especially when combined with
|
`AutomaticEnv` is a powerful helper especially when combined with
|
||||||
`SetEnvPrefix`. When called, Viper will check for an environment variable any
|
`SetEnvPrefix`. When called, Viper will check for an environment variable any
|
||||||
time a `viper.Get` request is made. It will apply the following rules. It will
|
time a `viper.Get` request is made. It will apply the following rules. It will
|
||||||
check for a environment variable with a name matching the key uppercased and
|
check for an environment variable with a name matching the key uppercased and
|
||||||
prefixed with the `EnvPrefix` if set.
|
prefixed with the `EnvPrefix` if set.
|
||||||
|
|
||||||
`SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env
|
`SetEnvKeyReplacer` allows you to use a `strings.Replacer` object to rewrite Env
|
||||||
|
@ -217,6 +277,13 @@ keys to an extent. This is useful if you want to use `-` or something in your
|
||||||
`Get()` calls, but want your environmental variables to use `_` delimiters. An
|
`Get()` calls, but want your environmental variables to use `_` delimiters. An
|
||||||
example of using it can be found in `viper_test.go`.
|
example of using it can be found in `viper_test.go`.
|
||||||
|
|
||||||
|
Alternatively, you can use `EnvKeyReplacer` with `NewWithOptions` factory function.
|
||||||
|
Unlike `SetEnvKeyReplacer`, it accepts a `StringReplacer` interface allowing you to write custom string replacing logic.
|
||||||
|
|
||||||
|
By default empty environment variables are considered unset and will fall back to
|
||||||
|
the next configuration source. To treat empty environment variables as set, use
|
||||||
|
the `AllowEmptyEnv` method.
|
||||||
|
|
||||||
#### Env example
|
#### Env example
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
@ -287,7 +354,7 @@ func main() {
|
||||||
|
|
||||||
i := viper.GetInt("flagname") // retrieve value from viper
|
i := viper.GetInt("flagname") // retrieve value from viper
|
||||||
|
|
||||||
...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -339,14 +406,14 @@ viper.BindFlagValues("my-flags", fSet)
|
||||||
To enable remote support in Viper, do a blank import of the `viper/remote`
|
To enable remote support in Viper, do a blank import of the `viper/remote`
|
||||||
package:
|
package:
|
||||||
|
|
||||||
`import _ "github.com/spf13/viper/remote"`
|
`import _ "git.internal/re/viper/remote"`
|
||||||
|
|
||||||
Viper will read a config string (as JSON, TOML, YAML or HCL) retrieved from a path
|
Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved from a path
|
||||||
in a Key/Value store such as etcd or Consul. These values take precedence over
|
in a Key/Value store such as etcd or Consul. These values take precedence over
|
||||||
default values, but are overridden by configuration values retrieved from disk,
|
default values, but are overridden by configuration values retrieved from disk,
|
||||||
flags, or environment variables.
|
flags, or environment variables.
|
||||||
|
|
||||||
Viper uses [crypt](https://github.com/xordataexchange/crypt) to retrieve
|
Viper uses [crypt](https://github.com/bketelsen/crypt) to retrieve
|
||||||
configuration from the K/V store, which means that you can store your
|
configuration from the K/V store, which means that you can store your
|
||||||
configuration values encrypted and have them automatically decrypted if you have
|
configuration values encrypted and have them automatically decrypted if you have
|
||||||
the correct gpg keyring. Encryption is optional.
|
the correct gpg keyring. Encryption is optional.
|
||||||
|
@ -358,7 +425,7 @@ independently of it.
|
||||||
K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001.
|
K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go get github.com/xordataexchange/crypt/bin/crypt
|
$ go get github.com/bketelsen/crypt/bin/crypt
|
||||||
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
|
$ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -376,7 +443,14 @@ how to use Consul.
|
||||||
#### etcd
|
#### etcd
|
||||||
```go
|
```go
|
||||||
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
|
viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json")
|
||||||
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
|
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
|
||||||
|
err := viper.ReadRemoteConfig()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### etcd3
|
||||||
|
```go
|
||||||
|
viper.AddRemoteProvider("etcd3", "http://127.0.0.1:4001","/config/hugo.json")
|
||||||
|
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
|
||||||
err := viper.ReadRemoteConfig()
|
err := viper.ReadRemoteConfig()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -400,11 +474,21 @@ fmt.Println(viper.Get("port")) // 8080
|
||||||
fmt.Println(viper.Get("hostname")) // myhostname.com
|
fmt.Println(viper.Get("hostname")) // myhostname.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Firestore
|
||||||
|
|
||||||
|
```go
|
||||||
|
viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document")
|
||||||
|
viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml"
|
||||||
|
err := viper.ReadRemoteConfig()
|
||||||
|
```
|
||||||
|
|
||||||
|
Of course, you're allowed to use `SecureRemoteProvider` also
|
||||||
|
|
||||||
### Remote Key/Value Store Example - Encrypted
|
### Remote Key/Value Store Example - Encrypted
|
||||||
|
|
||||||
```go
|
```go
|
||||||
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
|
viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg")
|
||||||
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
|
viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
|
||||||
err := viper.ReadRemoteConfig()
|
err := viper.ReadRemoteConfig()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -415,7 +499,7 @@ err := viper.ReadRemoteConfig()
|
||||||
var runtime_viper = viper.New()
|
var runtime_viper = viper.New()
|
||||||
|
|
||||||
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
|
runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml")
|
||||||
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop"
|
runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv"
|
||||||
|
|
||||||
// read from remote config the first time.
|
// read from remote config the first time.
|
||||||
err := runtime_viper.ReadRemoteConfig()
|
err := runtime_viper.ReadRemoteConfig()
|
||||||
|
@ -426,18 +510,18 @@ runtime_viper.Unmarshal(&runtime_conf)
|
||||||
// open a goroutine to watch remote changes forever
|
// open a goroutine to watch remote changes forever
|
||||||
go func(){
|
go func(){
|
||||||
for {
|
for {
|
||||||
time.Sleep(time.Second * 5) // delay after each request
|
time.Sleep(time.Second * 5) // delay after each request
|
||||||
|
|
||||||
// currently, only tested with etcd support
|
// currently, only tested with etcd support
|
||||||
err := runtime_viper.WatchRemoteConfig()
|
err := runtime_viper.WatchRemoteConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("unable to read remote config: %v", err)
|
log.Errorf("unable to read remote config: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshal new config into our runtime config struct. you can also use channel
|
// unmarshal new config into our runtime config struct. you can also use channel
|
||||||
// to implement a signal to notify the system of the changes
|
// to implement a signal to notify the system of the changes
|
||||||
runtime_viper.Unmarshal(&runtime_conf)
|
runtime_viper.Unmarshal(&runtime_conf)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
```
|
```
|
||||||
|
@ -451,6 +535,7 @@ The following functions and methods exist:
|
||||||
* `GetBool(key string) : bool`
|
* `GetBool(key string) : bool`
|
||||||
* `GetFloat64(key string) : float64`
|
* `GetFloat64(key string) : float64`
|
||||||
* `GetInt(key string) : int`
|
* `GetInt(key string) : int`
|
||||||
|
* `GetIntSlice(key string) : []int`
|
||||||
* `GetString(key string) : string`
|
* `GetString(key string) : string`
|
||||||
* `GetStringMap(key string) : map[string]interface{}`
|
* `GetStringMap(key string) : map[string]interface{}`
|
||||||
* `GetStringMapString(key string) : map[string]string`
|
* `GetStringMapString(key string) : map[string]string`
|
||||||
|
@ -468,7 +553,7 @@ Example:
|
||||||
```go
|
```go
|
||||||
viper.GetString("logfile") // case-insensitive Setting & Getting
|
viper.GetString("logfile") // case-insensitive Setting & Getting
|
||||||
if viper.GetBool("verbose") {
|
if viper.GetBool("verbose") {
|
||||||
fmt.Println("verbose enabled")
|
fmt.Println("verbose enabled")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
### Accessing nested keys
|
### Accessing nested keys
|
||||||
|
@ -514,10 +599,37 @@ the `Set()` method, …) with an immediate value, then all sub-keys of
|
||||||
`datastore.metric` become undefined, they are “shadowed” by the higher-priority
|
`datastore.metric` become undefined, they are “shadowed” by the higher-priority
|
||||||
configuration level.
|
configuration level.
|
||||||
|
|
||||||
|
Viper can access array indices by using numbers in the path. For example:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"host": {
|
||||||
|
"address": "localhost",
|
||||||
|
"ports": [
|
||||||
|
5799,
|
||||||
|
6029
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"datastore": {
|
||||||
|
"metric": {
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"port": 3099
|
||||||
|
},
|
||||||
|
"warehouse": {
|
||||||
|
"host": "198.0.0.1",
|
||||||
|
"port": 2112
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GetInt("host.ports.1") // returns 6029
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
Lastly, if there exists a key that matches the delimited key path, its value
|
Lastly, if there exists a key that matches the delimited key path, its value
|
||||||
will be returned instead. E.g.
|
will be returned instead. E.g.
|
||||||
|
|
||||||
```json
|
```jsonc
|
||||||
{
|
{
|
||||||
"datastore.metric.host": "0.0.0.0",
|
"datastore.metric.host": "0.0.0.0",
|
||||||
"host": {
|
"host": {
|
||||||
|
@ -539,14 +651,15 @@ will be returned instead. E.g.
|
||||||
GetString("datastore.metric.host") // returns "0.0.0.0"
|
GetString("datastore.metric.host") // returns "0.0.0.0"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Extract sub-tree
|
### Extracting a sub-tree
|
||||||
|
|
||||||
Extract sub-tree from Viper.
|
When developing reusable modules, it's often useful to extract a subset of the configuration
|
||||||
|
and pass it to a module. This way the module can be instantiated more than once, with different configurations.
|
||||||
|
|
||||||
For example, `viper` represents:
|
For example, an application might use multiple different cache stores for different purposes:
|
||||||
|
|
||||||
```json
|
```yaml
|
||||||
app:
|
cache:
|
||||||
cache1:
|
cache1:
|
||||||
max-items: 100
|
max-items: 100
|
||||||
item-size: 64
|
item-size: 64
|
||||||
|
@ -555,35 +668,36 @@ app:
|
||||||
item-size: 80
|
item-size: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
After executing:
|
We could pass the cache name to a module (eg. `NewCache("cache1")`),
|
||||||
|
but it would require weird concatenation for accessing config keys and would be less separated from the global config.
|
||||||
|
|
||||||
|
So instead of doing that let's pass a Viper instance to the constructor that represents a subset of the configuration:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
subv := viper.Sub("app.cache1")
|
cache1Config := viper.Sub("cache.cache1")
|
||||||
|
if cache1Config == nil { // Sub returns nil if the key cannot be found
|
||||||
|
panic("cache configuration not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
cache1 := NewCache(cache1Config)
|
||||||
```
|
```
|
||||||
|
|
||||||
`subv` represents:
|
**Note:** Always check the return value of `Sub`. It returns `nil` if a key cannot be found.
|
||||||
|
|
||||||
```json
|
Internally, the `NewCache` function can address `max-items` and `item-size` keys directly:
|
||||||
max-items: 100
|
|
||||||
item-size: 64
|
|
||||||
```
|
|
||||||
|
|
||||||
Suppose we have:
|
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func NewCache(cfg *Viper) *Cache {...}
|
func NewCache(v *Viper) *Cache {
|
||||||
|
return &Cache{
|
||||||
|
MaxItems: v.GetInt("max-items"),
|
||||||
|
ItemSize: v.GetInt("item-size"),
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
which creates a cache based on config information formatted as `subv`.
|
The resulting code is easy to test, since it's decoupled from the main config structure,
|
||||||
Now it’s easy to create these 2 caches separately as:
|
and easier to reuse (for the same reason).
|
||||||
|
|
||||||
```go
|
|
||||||
cfg1 := viper.Sub("app.cache1")
|
|
||||||
cache1 := NewCache(cfg1)
|
|
||||||
|
|
||||||
cfg2 := viper.Sub("app.cache2")
|
|
||||||
cache2 := NewCache(cfg2)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unmarshaling
|
### Unmarshaling
|
||||||
|
|
||||||
|
@ -606,29 +720,97 @@ type config struct {
|
||||||
|
|
||||||
var C config
|
var C config
|
||||||
|
|
||||||
err := Unmarshal(&C)
|
err := viper.Unmarshal(&C)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to decode into struct, %v", err)
|
t.Fatalf("unable to decode into struct, %v", err)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you want to unmarshal configuration where the keys themselves contain dot (the default key delimiter),
|
||||||
|
you have to change the delimiter:
|
||||||
|
|
||||||
|
```go
|
||||||
|
v := viper.NewWithOptions(viper.KeyDelimiter("::"))
|
||||||
|
|
||||||
|
v.SetDefault("chart::values", map[string]interface{}{
|
||||||
|
"ingress": map[string]interface{}{
|
||||||
|
"annotations": map[string]interface{}{
|
||||||
|
"traefik.frontend.rule.type": "PathPrefix",
|
||||||
|
"traefik.ingress.kubernetes.io/ssl-redirect": "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
Chart struct{
|
||||||
|
Values map[string]interface{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var C config
|
||||||
|
|
||||||
|
v.Unmarshal(&C)
|
||||||
|
```
|
||||||
|
|
||||||
|
Viper also supports unmarshaling into embedded structs:
|
||||||
|
|
||||||
|
```go
|
||||||
|
/*
|
||||||
|
Example config:
|
||||||
|
|
||||||
|
module:
|
||||||
|
enabled: true
|
||||||
|
token: 89h3f98hbwf987h3f98wenf89ehf
|
||||||
|
*/
|
||||||
|
type config struct {
|
||||||
|
Module struct {
|
||||||
|
Enabled bool
|
||||||
|
|
||||||
|
moduleConfig `mapstructure:",squash"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// moduleConfig could be in a module specific package
|
||||||
|
type moduleConfig struct {
|
||||||
|
Token string
|
||||||
|
}
|
||||||
|
|
||||||
|
var C config
|
||||||
|
|
||||||
|
err := viper.Unmarshal(&C)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to decode into struct, %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default.
|
||||||
|
|
||||||
|
### Decoding custom formats
|
||||||
|
|
||||||
|
A frequently requested feature for Viper is adding more value formats and decoders.
|
||||||
|
For example, parsing character (dot, comma, semicolon, etc) separated strings into slices.
|
||||||
|
|
||||||
|
This is already available in Viper using mapstructure decode hooks.
|
||||||
|
|
||||||
|
Read more about the details in [this blog post](https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/).
|
||||||
|
|
||||||
### Marshalling to string
|
### Marshalling to string
|
||||||
|
|
||||||
You may need to marhsal all the settings held in viper into a string rather than write them to a file.
|
You may need to marshal all the settings held in viper into a string rather than write them to a file.
|
||||||
You can use your favorite format's marshaller with the config returned by `AllSettings()`.
|
You can use your favorite format's marshaller with the config returned by `AllSettings()`.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import (
|
import (
|
||||||
yaml "gopkg.in/yaml.v2"
|
yaml "gopkg.in/yaml.v2"
|
||||||
// ...
|
// ...
|
||||||
)
|
)
|
||||||
|
|
||||||
func yamlStringSettings() string {
|
func yamlStringSettings() string {
|
||||||
c := viper.AllSettings()
|
c := viper.AllSettings()
|
||||||
bs, err := yaml.Marshal(c)
|
bs, err := yaml.Marshal(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to marshal config to YAML: %v", err)
|
log.Fatalf("unable to marshal config to YAML: %v", err)
|
||||||
}
|
}
|
||||||
return string(bs)
|
return string(bs)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -665,22 +847,35 @@ y.SetDefault("ContentDir", "foobar")
|
||||||
When working with multiple vipers, it is up to the user to keep track of the
|
When working with multiple vipers, it is up to the user to keep track of the
|
||||||
different vipers.
|
different vipers.
|
||||||
|
|
||||||
|
|
||||||
## Q & A
|
## Q & A
|
||||||
|
|
||||||
Q: Why not INI files?
|
### Why is it called “Viper”?
|
||||||
|
|
||||||
A: Ini files are pretty awful. There’s no standard format, and they are hard to
|
|
||||||
validate. Viper is designed to work with JSON, TOML or YAML files. If someone
|
|
||||||
really wants to add this feature, I’d be happy to merge it. It’s easy to specify
|
|
||||||
which formats your application will permit.
|
|
||||||
|
|
||||||
Q: Why is it called “Viper”?
|
|
||||||
|
|
||||||
A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe))
|
A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe))
|
||||||
to [Cobra](https://github.com/spf13/cobra). While both can operate completely
|
to [Cobra](https://github.com/spf13/cobra). While both can operate completely
|
||||||
independently, together they make a powerful pair to handle much of your
|
independently, together they make a powerful pair to handle much of your
|
||||||
application foundation needs.
|
application foundation needs.
|
||||||
|
|
||||||
Q: Why is it called “Cobra”?
|
### Why is it called “Cobra”?
|
||||||
|
|
||||||
A: Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)?
|
Is there a better name for a [commander](http://en.wikipedia.org/wiki/Cobra_Commander)?
|
||||||
|
|
||||||
|
### Does Viper support case sensitive keys?
|
||||||
|
|
||||||
|
**tl;dr:** No.
|
||||||
|
|
||||||
|
Viper merges configuration from various sources, many of which are either case insensitive or uses different casing than the rest of the sources (eg. env vars).
|
||||||
|
In order to provide the best experience when using multiple sources, the decision has been made to make all keys case insensitive.
|
||||||
|
|
||||||
|
There has been several attempts to implement case sensitivity, but unfortunately it's not that trivial. We might take a stab at implementing it in [Viper v2](https://git.internal/re/viper/issues/772), but despite the initial noise, it does not seem to be requested that much.
|
||||||
|
|
||||||
|
You can vote for case sensitivity by filling out this feedback form: https://forms.gle/R6faU74qPRPAzchZ9
|
||||||
|
|
||||||
|
### Is it safe to concurrently read and write to a viper?
|
||||||
|
|
||||||
|
No, you will need to synchronize access to the viper yourself (for example by using the `sync` package). Concurrent reads and writes can cause a panic.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
See [TROUBLESHOOTING.md](TROUBLESHOOTING.md).
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
## Unmarshaling doesn't work
|
||||||
|
|
||||||
|
The most common reason for this issue is improper use of struct tags (eg. `yaml` or `json`). Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default. Please refer to the library's documentation for using other struct tags.
|
||||||
|
|
||||||
|
## Cannot find package
|
||||||
|
|
||||||
|
Viper installation seems to fail a lot lately with the following (or a similar) error:
|
||||||
|
|
||||||
|
```
|
||||||
|
cannot find package "github.com/hashicorp/hcl/tree/hcl1" in any of:
|
||||||
|
/usr/local/Cellar/go/1.15.7_1/libexec/src/github.com/hashicorp/hcl/tree/hcl1 (from $GOROOT)
|
||||||
|
/Users/user/go/src/github.com/hashicorp/hcl/tree/hcl1 (from $GOPATH)
|
||||||
|
```
|
||||||
|
|
||||||
|
As the error message suggests, Go tries to look up dependencies in `GOPATH` mode (as it's commonly called) from the `GOPATH`.
|
||||||
|
Viper opted to use [Go Modules](https://github.com/golang/go/wiki/Modules) to manage its dependencies. While in many cases the two methods are interchangeable, once a dependency releases new (major) versions, `GOPATH` mode is no longer able to decide which version to use, so it'll either use one that's already present or pick a version (usually the `master` branch).
|
||||||
|
|
||||||
|
The solution is easy: switch to using Go Modules.
|
||||||
|
Please refer to the [wiki](https://github.com/golang/go/wiki/Modules) on how to do that.
|
||||||
|
|
||||||
|
**tl;dr* `export GO111MODULE=on`
|
||||||
|
|
||||||
|
## Unquoted 'y' and 'n' characters get replaced with _true_ and _false_ when reading a YAML file
|
||||||
|
|
||||||
|
This is a YAML 1.1 feature according to [go-yaml/yaml#740](https://github.com/go-yaml/yaml/issues/740).
|
||||||
|
|
||||||
|
Potential solutions are:
|
||||||
|
|
||||||
|
1. Quoting values resolved as boolean
|
||||||
|
1. Upgrading to YAML v3 (for the time being this is possible by passing the `viper_yaml3` tag to your build)
|
|
@ -0,0 +1,11 @@
|
||||||
|
//go:build viper_logger
|
||||||
|
// +build viper_logger
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
// WithLogger sets a custom logger.
|
||||||
|
func WithLogger(l Logger) Option {
|
||||||
|
return optionFunc(func(v *Viper) {
|
||||||
|
v.logger = l
|
||||||
|
})
|
||||||
|
}
|
2
flags.go
2
flags.go
|
@ -36,7 +36,7 @@ type pflagValue struct {
|
||||||
flag *pflag.Flag
|
flag *pflag.Flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasChanges returns whether the flag has changes or not.
|
// HasChanged returns whether the flag has changes or not.
|
||||||
func (p pflagValue) HasChanged() bool {
|
func (p pflagValue) HasChanged() bool {
|
||||||
return p.flag.Changed
|
return p.flag.Changed
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,13 @@ import (
|
||||||
func TestBindFlagValueSet(t *testing.T) {
|
func TestBindFlagValueSet(t *testing.T) {
|
||||||
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
flagSet := pflag.NewFlagSet("test", pflag.ContinueOnError)
|
||||||
|
|
||||||
var testValues = map[string]*string{
|
testValues := map[string]*string{
|
||||||
"host": nil,
|
"host": nil,
|
||||||
"port": nil,
|
"port": nil,
|
||||||
"endpoint": nil,
|
"endpoint": nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
var mutatedTestValues = map[string]string{
|
mutatedTestValues := map[string]string{
|
||||||
"host": "localhost",
|
"host": "localhost",
|
||||||
"port": "6060",
|
"port": "6060",
|
||||||
"endpoint": "/public",
|
"endpoint": "/public",
|
||||||
|
@ -44,8 +44,8 @@ func TestBindFlagValueSet(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBindFlagValue(t *testing.T) {
|
func TestBindFlagValue(t *testing.T) {
|
||||||
var testString = "testing"
|
testString := "testing"
|
||||||
var testValue = newStringValue(testString, &testString)
|
testValue := newStringValue(testString, &testString)
|
||||||
|
|
||||||
flag := &pflag.Flag{
|
flag := &pflag.Flag{
|
||||||
Name: "testflag",
|
Name: "testflag",
|
||||||
|
@ -59,7 +59,7 @@ func TestBindFlagValue(t *testing.T) {
|
||||||
assert.Equal(t, testString, Get("testvalue"))
|
assert.Equal(t, testString, Get("testvalue"))
|
||||||
|
|
||||||
flag.Value.Set("testing_mutate")
|
flag.Value.Set("testing_mutate")
|
||||||
flag.Changed = true //hack for pflag usage
|
flag.Changed = true // hack for pflag usage
|
||||||
|
|
||||||
assert.Equal(t, "testing_mutate", Get("testvalue"))
|
assert.Equal(t, "testing_mutate", Get("testvalue"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
//go:build go1.16 && finder
|
||||||
|
// +build go1.16,finder
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/fs"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
type finder struct {
|
||||||
|
paths []string
|
||||||
|
fileNames []string
|
||||||
|
extensions []string
|
||||||
|
|
||||||
|
withoutExtension bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f finder) Find(fsys fs.FS) (string, error) {
|
||||||
|
for _, searchPath := range f.paths {
|
||||||
|
for _, fileName := range f.fileNames {
|
||||||
|
for _, extension := range f.extensions {
|
||||||
|
filePath := path.Join(searchPath, fileName+"."+extension)
|
||||||
|
|
||||||
|
ok, err := fileExists(fsys, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return filePath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.withoutExtension {
|
||||||
|
filePath := path.Join(searchPath, fileName)
|
||||||
|
|
||||||
|
ok, err := fileExists(fsys, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return filePath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(fsys fs.FS, filePath string) (bool, error) {
|
||||||
|
fileInfo, err := fs.Stat(fsys, filePath)
|
||||||
|
if err == nil {
|
||||||
|
return !fileInfo.IsDir(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, err
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
//go:build go1.16 && finder
|
||||||
|
// +build go1.16,finder
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFinder(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fsys := fstest.MapFS{
|
||||||
|
"home/user/.config": &fstest.MapFile{},
|
||||||
|
"home/user/config.json": &fstest.MapFile{},
|
||||||
|
"home/user/config.yaml": &fstest.MapFile{},
|
||||||
|
"home/user/data.json": &fstest.MapFile{},
|
||||||
|
"etc/config/.config": &fstest.MapFile{},
|
||||||
|
"etc/config/a_random_file.txt": &fstest.MapFile{},
|
||||||
|
"etc/config/config.json": &fstest.MapFile{},
|
||||||
|
"etc/config/config.yaml": &fstest.MapFile{},
|
||||||
|
"etc/config/config.xml": &fstest.MapFile{},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
fsys func() fs.FS
|
||||||
|
finder finder
|
||||||
|
result string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "find file",
|
||||||
|
fsys: func() fs.FS { return fsys },
|
||||||
|
finder: finder{
|
||||||
|
paths: []string{"etc/config"},
|
||||||
|
fileNames: []string{"config"},
|
||||||
|
extensions: []string{"json"},
|
||||||
|
},
|
||||||
|
result: "etc/config/config.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "file not found",
|
||||||
|
fsys: func() fs.FS { return fsys },
|
||||||
|
finder: finder{
|
||||||
|
paths: []string{"var/config"},
|
||||||
|
fileNames: []string{"config"},
|
||||||
|
extensions: []string{"json"},
|
||||||
|
},
|
||||||
|
result: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty search params",
|
||||||
|
fsys: func() fs.FS { return fsys },
|
||||||
|
finder: finder{},
|
||||||
|
result: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "precedence",
|
||||||
|
fsys: func() fs.FS { return fsys },
|
||||||
|
finder: finder{
|
||||||
|
paths: []string{"var/config", "home/user", "etc/config"},
|
||||||
|
fileNames: []string{"aconfig", "config"},
|
||||||
|
extensions: []string{"zml", "xml", "json"},
|
||||||
|
},
|
||||||
|
result: "home/user/config.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "without extension",
|
||||||
|
fsys: func() fs.FS { return fsys },
|
||||||
|
finder: finder{
|
||||||
|
paths: []string{"var/config", "home/user", "etc/config"},
|
||||||
|
fileNames: []string{".config"},
|
||||||
|
extensions: []string{"zml", "xml", "json"},
|
||||||
|
|
||||||
|
withoutExtension: true,
|
||||||
|
},
|
||||||
|
result: "home/user/.config",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
testCase := testCase
|
||||||
|
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
fsys := testCase.fsys()
|
||||||
|
|
||||||
|
result, err := testCase.finder.Find(fsys)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.result, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
39
go.mod
39
go.mod
|
@ -1,16 +1,29 @@
|
||||||
module github.com/spf13/viper
|
module git.internal/re/viper
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
git.internal/re/afero v1.9.3
|
||||||
github.com/hashicorp/hcl v1.0.0
|
git.internal/re/cast v1.5.1
|
||||||
github.com/magiconair/properties v1.8.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
github.com/mitchellh/mapstructure v1.0.0
|
github.com/magiconair/properties v1.8.7
|
||||||
github.com/pelletier/go-toml v1.2.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/spf13/afero v1.1.2
|
github.com/pelletier/go-toml v1.9.5
|
||||||
github.com/spf13/cast v1.2.0
|
github.com/pelletier/go-toml/v2 v2.0.5
|
||||||
github.com/spf13/jwalterweatherman v1.0.0
|
github.com/spf13/jwalterweatherman v1.1.0
|
||||||
github.com/spf13/pflag v1.0.2
|
github.com/spf13/pflag v1.0.5
|
||||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 // indirect
|
github.com/stretchr/testify v1.8.1
|
||||||
golang.org/x/text v0.3.0 // indirect
|
github.com/subosito/gotenv v1.4.1
|
||||||
gopkg.in/yaml.v2 v2.2.1
|
gopkg.in/ini.v1 v1.67.0
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
|
||||||
|
golang.org/x/text v0.4.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
)
|
)
|
||||||
|
|
515
go.sum
515
go.sum
|
@ -1,26 +1,495 @@
|
||||||
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
|
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||||
|
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||||
|
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||||
|
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||||
|
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||||
|
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
|
||||||
|
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
|
||||||
|
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
|
||||||
|
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
|
||||||
|
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||||
|
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||||
|
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||||
|
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
|
||||||
|
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
|
||||||
|
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
|
||||||
|
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
|
||||||
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
|
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||||
|
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||||
|
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
|
||||||
|
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
|
||||||
|
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
|
||||||
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
|
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||||
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||||
|
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||||
|
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||||
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
|
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||||
|
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||||
|
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
|
||||||
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
git.internal/re/afero v1.9.3 h1:3E6gTxdfID9str9lOLWuOQu+QILArbRLUBWi8MywP18=
|
||||||
|
git.internal/re/afero v1.9.3/go.mod h1:uyj2gu0urns+v+G7KIkNO49qShF4zekJMCflZVvB84M=
|
||||||
|
git.internal/re/cast v1.5.1 h1:lmg2A9oeAp9CZjvC70Bcv2hOuQO5RB027bIbixTbok4=
|
||||||
|
git.internal/re/cast v1.5.1/go.mod h1:ydbf5sDush+h+PruReXSzPmL7F0nRL1ogRYehzUSHnE=
|
||||||
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
|
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||||
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
|
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||||
|
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||||
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
|
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||||
|
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||||
|
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||||
|
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||||
|
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
|
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
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/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||||
|
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||||
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||||
|
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||||
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
|
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
|
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||||
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
|
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
|
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
|
||||||
|
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
|
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||||
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY=
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
|
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
|
||||||
|
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
|
||||||
|
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
|
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
|
||||||
|
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
|
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
|
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||||
|
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||||
|
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
|
||||||
|
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||||
|
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
|
||||||
|
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
|
||||||
|
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
|
||||||
|
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
|
||||||
|
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
|
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||||
|
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||||
|
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||||
|
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
|
||||||
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
|
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
|
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
|
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||||
|
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||||
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
|
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
|
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
|
||||||
|
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
|
||||||
|
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
|
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
|
||||||
|
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
|
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||||
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decoder decodes the contents of b into v.
|
||||||
|
// It's primarily used for decoding contents of a file into a map[string]interface{}.
|
||||||
|
type Decoder interface {
|
||||||
|
Decode(b []byte, v map[string]interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ErrDecoderNotFound is returned when there is no decoder registered for a format.
|
||||||
|
ErrDecoderNotFound = encodingError("decoder not found for this format")
|
||||||
|
|
||||||
|
// ErrDecoderFormatAlreadyRegistered is returned when an decoder is already registered for a format.
|
||||||
|
ErrDecoderFormatAlreadyRegistered = encodingError("decoder already registered for this format")
|
||||||
|
)
|
||||||
|
|
||||||
|
// DecoderRegistry can choose an appropriate Decoder based on the provided format.
|
||||||
|
type DecoderRegistry struct {
|
||||||
|
decoders map[string]Decoder
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoderRegistry returns a new, initialized DecoderRegistry.
|
||||||
|
func NewDecoderRegistry() *DecoderRegistry {
|
||||||
|
return &DecoderRegistry{
|
||||||
|
decoders: make(map[string]Decoder),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterDecoder registers a Decoder for a format.
|
||||||
|
// Registering a Decoder for an already existing format is not supported.
|
||||||
|
func (e *DecoderRegistry) RegisterDecoder(format string, enc Decoder) error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := e.decoders[format]; ok {
|
||||||
|
return ErrDecoderFormatAlreadyRegistered
|
||||||
|
}
|
||||||
|
|
||||||
|
e.decoders[format] = enc
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode calls the underlying Decoder based on the format.
|
||||||
|
func (e *DecoderRegistry) Decode(format string, b []byte, v map[string]interface{}) error {
|
||||||
|
e.mu.RLock()
|
||||||
|
decoder, ok := e.decoders[format]
|
||||||
|
e.mu.RUnlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return ErrDecoderNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoder.Decode(b, v)
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type decoder struct {
|
||||||
|
v map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d decoder) Decode(_ []byte, v map[string]interface{}) error {
|
||||||
|
for key, value := range d.v {
|
||||||
|
v[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecoderRegistry_RegisterDecoder(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
registry := NewDecoderRegistry()
|
||||||
|
|
||||||
|
err := registry.RegisterDecoder("myformat", decoder{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AlreadyRegistered", func(t *testing.T) {
|
||||||
|
registry := NewDecoderRegistry()
|
||||||
|
|
||||||
|
err := registry.RegisterDecoder("myformat", decoder{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = registry.RegisterDecoder("myformat", decoder{})
|
||||||
|
if err != ErrDecoderFormatAlreadyRegistered {
|
||||||
|
t.Fatalf("expected ErrDecoderFormatAlreadyRegistered, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecoderRegistry_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
registry := NewDecoderRegistry()
|
||||||
|
decoder := decoder{
|
||||||
|
v: map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := registry.RegisterDecoder("myformat", decoder)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err = registry.Decode("myformat", []byte("key: value"), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(decoder.v, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %+v\nexpected: %+v", v, decoder.v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DecoderNotFound", func(t *testing.T) {
|
||||||
|
registry := NewDecoderRegistry()
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := registry.Decode("myformat", nil, v)
|
||||||
|
if err != ErrDecoderNotFound {
|
||||||
|
t.Fatalf("expected ErrDecoderNotFound, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package dotenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/subosito/gotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const keyDelimiter = "_"
|
||||||
|
|
||||||
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for encoding data containing environment variables
|
||||||
|
// (commonly called as dotenv format).
|
||||||
|
type Codec struct{}
|
||||||
|
|
||||||
|
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
flattened := map[string]interface{}{}
|
||||||
|
|
||||||
|
flattened = flattenAndMergeMap(flattened, v, "", keyDelimiter)
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(flattened))
|
||||||
|
|
||||||
|
for key := range flattened {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
_, err := buf.WriteString(fmt.Sprintf("%v=%v\n", strings.ToUpper(key), flattened[key]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
_, err := buf.Write(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
env, err := gotenv.StrictParse(&buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range env {
|
||||||
|
v[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package dotenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// original form of the data
|
||||||
|
const original = `# key-value pair
|
||||||
|
KEY=value
|
||||||
|
`
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `KEY=value
|
||||||
|
`
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"KEY": "value",
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Encode(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(original), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(data, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected decoding to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package dotenv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.internal/re/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// flattenAndMergeMap recursively flattens the given map into a new map
|
||||||
|
// Code is based on the function with the same name in tha main package.
|
||||||
|
// TODO: move it to a common place
|
||||||
|
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
|
||||||
|
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
||||||
|
// prefix is shadowed => nothing more to flatten
|
||||||
|
return shadow
|
||||||
|
}
|
||||||
|
if shadow == nil {
|
||||||
|
shadow = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var m2 map[string]interface{}
|
||||||
|
if prefix != "" {
|
||||||
|
prefix += delimiter
|
||||||
|
}
|
||||||
|
for k, val := range m {
|
||||||
|
fullKey := prefix + k
|
||||||
|
switch val.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
m2 = val.(map[string]interface{})
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
m2 = cast.ToStringMap(val)
|
||||||
|
default:
|
||||||
|
// immediate value
|
||||||
|
shadow[strings.ToLower(fullKey)] = val
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// recursively merge to shadow map
|
||||||
|
shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
|
||||||
|
}
|
||||||
|
return shadow
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encoder encodes the contents of v into a byte representation.
|
||||||
|
// It's primarily used for encoding a map[string]interface{} into a file format.
|
||||||
|
type Encoder interface {
|
||||||
|
Encode(v map[string]interface{}) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ErrEncoderNotFound is returned when there is no encoder registered for a format.
|
||||||
|
ErrEncoderNotFound = encodingError("encoder not found for this format")
|
||||||
|
|
||||||
|
// ErrEncoderFormatAlreadyRegistered is returned when an encoder is already registered for a format.
|
||||||
|
ErrEncoderFormatAlreadyRegistered = encodingError("encoder already registered for this format")
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncoderRegistry can choose an appropriate Encoder based on the provided format.
|
||||||
|
type EncoderRegistry struct {
|
||||||
|
encoders map[string]Encoder
|
||||||
|
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoderRegistry returns a new, initialized EncoderRegistry.
|
||||||
|
func NewEncoderRegistry() *EncoderRegistry {
|
||||||
|
return &EncoderRegistry{
|
||||||
|
encoders: make(map[string]Encoder),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterEncoder registers an Encoder for a format.
|
||||||
|
// Registering a Encoder for an already existing format is not supported.
|
||||||
|
func (e *EncoderRegistry) RegisterEncoder(format string, enc Encoder) error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := e.encoders[format]; ok {
|
||||||
|
return ErrEncoderFormatAlreadyRegistered
|
||||||
|
}
|
||||||
|
|
||||||
|
e.encoders[format] = enc
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EncoderRegistry) Encode(format string, v map[string]interface{}) ([]byte, error) {
|
||||||
|
e.mu.RLock()
|
||||||
|
encoder, ok := e.encoders[format]
|
||||||
|
e.mu.RUnlock()
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrEncoderNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoder.Encode(v)
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package encoding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type encoder struct {
|
||||||
|
b []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e encoder) Encode(_ map[string]interface{}) ([]byte, error) {
|
||||||
|
return e.b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncoderRegistry_RegisterEncoder(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
registry := NewEncoderRegistry()
|
||||||
|
|
||||||
|
err := registry.RegisterEncoder("myformat", encoder{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("AlreadyRegistered", func(t *testing.T) {
|
||||||
|
registry := NewEncoderRegistry()
|
||||||
|
|
||||||
|
err := registry.RegisterEncoder("myformat", encoder{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = registry.RegisterEncoder("myformat", encoder{})
|
||||||
|
if err != ErrEncoderFormatAlreadyRegistered {
|
||||||
|
t.Fatalf("expected ErrEncoderFormatAlreadyRegistered, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncoderRegistry_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
registry := NewEncoderRegistry()
|
||||||
|
encoder := encoder{
|
||||||
|
b: []byte("key: value"),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := registry.RegisterEncoder("myformat", encoder)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(b) != "key: value" {
|
||||||
|
t.Fatalf("expected 'key: value', got: %#v", string(b))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("EncoderNotFound", func(t *testing.T) {
|
||||||
|
registry := NewEncoderRegistry()
|
||||||
|
|
||||||
|
_, err := registry.Encode("myformat", map[string]interface{}{"key": "value"})
|
||||||
|
if err != ErrEncoderNotFound {
|
||||||
|
t.Fatalf("expected ErrEncoderNotFound, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package encoding
|
||||||
|
|
||||||
|
type encodingError string
|
||||||
|
|
||||||
|
func (e encodingError) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package hcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for HCL encoding.
|
||||||
|
// TODO: add printer config to the codec?
|
||||||
|
type Codec struct{}
|
||||||
|
|
||||||
|
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
// b, err := json.Marshal(v)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: use printer.Format? Is the trailing newline an issue?
|
||||||
|
|
||||||
|
// ast, err := hcl.Parse(string(b))
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// err = printer.Fprint(&buf, ast.Node)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
|
// return hcl.Unmarshal(b, &v)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,140 @@
|
||||||
|
package hcl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// original form of the data
|
||||||
|
const original = `# key-value pair
|
||||||
|
"key" = "value"
|
||||||
|
|
||||||
|
// list
|
||||||
|
"list" = ["item1", "item2", "item3"]
|
||||||
|
|
||||||
|
/* map */
|
||||||
|
"map" = {
|
||||||
|
"key" = "value"
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
nested map
|
||||||
|
*/
|
||||||
|
"nested_map" "map" {
|
||||||
|
"key" = "value"
|
||||||
|
|
||||||
|
"list" = ["item1", "item2", "item3"]
|
||||||
|
}`
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `"key" = "value"
|
||||||
|
|
||||||
|
"list" = ["item1", "item2", "item3"]
|
||||||
|
|
||||||
|
"map" = {
|
||||||
|
"key" = "value"
|
||||||
|
}
|
||||||
|
|
||||||
|
"nested_map" "map" {
|
||||||
|
"key" = "value"
|
||||||
|
|
||||||
|
"list" = ["item1", "item2", "item3"]
|
||||||
|
}`
|
||||||
|
|
||||||
|
// decoded form of the data
|
||||||
|
//
|
||||||
|
// in case of HCL it's slightly different from Viper's internal representation
|
||||||
|
// (eg. map is decoded into a list of maps)
|
||||||
|
var decoded = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nested_map": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"map": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]interface{}{
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Encode(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(original), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(decoded, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected decoding to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.internal/re/cast"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadOptions contains all customized options used for load data source(s).
|
||||||
|
// This type is added here for convenience: this way consumers can import a single package called "ini".
|
||||||
|
type LoadOptions = ini.LoadOptions
|
||||||
|
|
||||||
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for INI encoding.
|
||||||
|
type Codec struct {
|
||||||
|
KeyDelimiter string
|
||||||
|
LoadOptions LoadOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
cfg := ini.Empty()
|
||||||
|
ini.PrettyFormat = false
|
||||||
|
|
||||||
|
flattened := map[string]interface{}{}
|
||||||
|
|
||||||
|
flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(flattened))
|
||||||
|
|
||||||
|
for key := range flattened {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
sectionName, keyName := "", key
|
||||||
|
|
||||||
|
lastSep := strings.LastIndex(key, ".")
|
||||||
|
if lastSep != -1 {
|
||||||
|
sectionName = key[:(lastSep)]
|
||||||
|
keyName = key[(lastSep + 1):]
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is this a good idea?
|
||||||
|
if sectionName == "default" {
|
||||||
|
sectionName = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.Section(sectionName).Key(keyName).SetValue(cast.ToString(flattened[key]))
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
_, err := cfg.WriteTo(&buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
|
cfg := ini.Empty(c.LoadOptions)
|
||||||
|
|
||||||
|
err := cfg.Append(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sections := cfg.Sections()
|
||||||
|
|
||||||
|
for i := 0; i < len(sections); i++ {
|
||||||
|
section := sections[i]
|
||||||
|
keys := section.Keys()
|
||||||
|
|
||||||
|
for j := 0; j < len(keys); j++ {
|
||||||
|
key := keys[j]
|
||||||
|
value := cfg.Section(section.Name()).Key(key.Name()).String()
|
||||||
|
|
||||||
|
deepestMap := deepSearch(v, strings.Split(section.Name(), c.keyDelimiter()))
|
||||||
|
|
||||||
|
// set innermost value
|
||||||
|
deepestMap[key.Name()] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Codec) keyDelimiter() string {
|
||||||
|
if c.KeyDelimiter == "" {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.KeyDelimiter
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// original form of the data
|
||||||
|
const original = `; key-value pair
|
||||||
|
key=value ; key-value pair
|
||||||
|
|
||||||
|
# map
|
||||||
|
[map] # map
|
||||||
|
key=%(key)s
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `key=value
|
||||||
|
|
||||||
|
[map]
|
||||||
|
key=value
|
||||||
|
`
|
||||||
|
|
||||||
|
// decoded form of the data
|
||||||
|
//
|
||||||
|
// in case of INI it's slightly different from Viper's internal representation
|
||||||
|
// (eg. top level keys land in a section called default)
|
||||||
|
var decoded = map[string]interface{}{
|
||||||
|
"DEFAULT": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Encode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Default", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"default": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(original), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(decoded, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected decoding to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package ini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.internal/re/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED
|
||||||
|
// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE
|
||||||
|
// deepSearch scans deep maps, following the key indexes listed in the
|
||||||
|
// sequence "path".
|
||||||
|
// The last value is expected to be another map, and is returned.
|
||||||
|
//
|
||||||
|
// In case intermediate keys do not exist, or map to a non-map value,
|
||||||
|
// a new map is created and inserted, and the search continues from there:
|
||||||
|
// the initial map "m" may be modified!
|
||||||
|
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
|
||||||
|
for _, k := range path {
|
||||||
|
m2, ok := m[k]
|
||||||
|
if !ok {
|
||||||
|
// intermediate key does not exist
|
||||||
|
// => create it and continue from there
|
||||||
|
m3 := make(map[string]interface{})
|
||||||
|
m[k] = m3
|
||||||
|
m = m3
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m3, ok := m2.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
// intermediate key is a value
|
||||||
|
// => replace with a new map
|
||||||
|
m3 = make(map[string]interface{})
|
||||||
|
m[k] = m3
|
||||||
|
}
|
||||||
|
// continue search from here
|
||||||
|
m = m3
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// flattenAndMergeMap recursively flattens the given map into a new map
|
||||||
|
// Code is based on the function with the same name in tha main package.
|
||||||
|
// TODO: move it to a common place
|
||||||
|
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
|
||||||
|
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
||||||
|
// prefix is shadowed => nothing more to flatten
|
||||||
|
return shadow
|
||||||
|
}
|
||||||
|
if shadow == nil {
|
||||||
|
shadow = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var m2 map[string]interface{}
|
||||||
|
if prefix != "" {
|
||||||
|
prefix += delimiter
|
||||||
|
}
|
||||||
|
for k, val := range m {
|
||||||
|
fullKey := prefix + k
|
||||||
|
switch val.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
m2 = val.(map[string]interface{})
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
m2 = cast.ToStringMap(val)
|
||||||
|
default:
|
||||||
|
// immediate value
|
||||||
|
shadow[strings.ToLower(fullKey)] = val
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// recursively merge to shadow map
|
||||||
|
shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
|
||||||
|
}
|
||||||
|
return shadow
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package javaproperties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.internal/re/cast"
|
||||||
|
"github.com/magiconair/properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for Java properties encoding.
|
||||||
|
type Codec struct {
|
||||||
|
KeyDelimiter string
|
||||||
|
|
||||||
|
// Store read properties on the object so that we can write back in order with comments.
|
||||||
|
// This will only be used if the configuration read is a properties file.
|
||||||
|
// TODO: drop this feature in v2
|
||||||
|
// TODO: make use of the global properties object optional
|
||||||
|
Properties *properties.Properties
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
if c.Properties == nil {
|
||||||
|
c.Properties = properties.NewProperties()
|
||||||
|
}
|
||||||
|
|
||||||
|
flattened := map[string]interface{}{}
|
||||||
|
|
||||||
|
flattened = flattenAndMergeMap(flattened, v, "", c.keyDelimiter())
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(flattened))
|
||||||
|
|
||||||
|
for key := range flattened {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
_, _, err := c.Properties.Set(key, cast.ToString(flattened[key]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
_, err := c.Properties.WriteComment(&buf, "#", properties.UTF8)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
|
var err error
|
||||||
|
c.Properties, err = properties.Load(b, properties.UTF8)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range c.Properties.Keys() {
|
||||||
|
// ignore existence check: we know it's there
|
||||||
|
value, _ := c.Properties.Get(key)
|
||||||
|
|
||||||
|
// recursively build nested maps
|
||||||
|
path := strings.Split(key, c.keyDelimiter())
|
||||||
|
lastKey := strings.ToLower(path[len(path)-1])
|
||||||
|
deepestMap := deepSearch(v, path[0:len(path)-1])
|
||||||
|
|
||||||
|
// set innermost value
|
||||||
|
deepestMap[lastKey] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Codec) keyDelimiter() string {
|
||||||
|
if c.KeyDelimiter == "" {
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.KeyDelimiter
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package javaproperties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// original form of the data
|
||||||
|
const original = `#key-value pair
|
||||||
|
key = value
|
||||||
|
map.key = value
|
||||||
|
`
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `key = value
|
||||||
|
map.key = value
|
||||||
|
`
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Encode(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(original), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(data, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
|
t.Skip("TODO: needs invalid data example")
|
||||||
|
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
codec.Decode([]byte(``), v)
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
t.Fatalf("expected map to be empty when data is invalid\nactual: %#v", v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_DecodeEncode(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(original), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if original != string(b) {
|
||||||
|
t.Fatalf("encoded value does not match the original\nactual: %#v\nexpected: %#v", string(b), original)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package javaproperties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.internal/re/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
// THIS CODE IS COPIED HERE: IT SHOULD NOT BE MODIFIED
|
||||||
|
// AT SOME POINT IT WILL BE MOVED TO A COMMON PLACE
|
||||||
|
// deepSearch scans deep maps, following the key indexes listed in the
|
||||||
|
// sequence "path".
|
||||||
|
// The last value is expected to be another map, and is returned.
|
||||||
|
//
|
||||||
|
// In case intermediate keys do not exist, or map to a non-map value,
|
||||||
|
// a new map is created and inserted, and the search continues from there:
|
||||||
|
// the initial map "m" may be modified!
|
||||||
|
func deepSearch(m map[string]interface{}, path []string) map[string]interface{} {
|
||||||
|
for _, k := range path {
|
||||||
|
m2, ok := m[k]
|
||||||
|
if !ok {
|
||||||
|
// intermediate key does not exist
|
||||||
|
// => create it and continue from there
|
||||||
|
m3 := make(map[string]interface{})
|
||||||
|
m[k] = m3
|
||||||
|
m = m3
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m3, ok := m2.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
// intermediate key is a value
|
||||||
|
// => replace with a new map
|
||||||
|
m3 = make(map[string]interface{})
|
||||||
|
m[k] = m3
|
||||||
|
}
|
||||||
|
// continue search from here
|
||||||
|
m = m3
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// flattenAndMergeMap recursively flattens the given map into a new map
|
||||||
|
// Code is based on the function with the same name in tha main package.
|
||||||
|
// TODO: move it to a common place
|
||||||
|
func flattenAndMergeMap(shadow map[string]interface{}, m map[string]interface{}, prefix string, delimiter string) map[string]interface{} {
|
||||||
|
if shadow != nil && prefix != "" && shadow[prefix] != nil {
|
||||||
|
// prefix is shadowed => nothing more to flatten
|
||||||
|
return shadow
|
||||||
|
}
|
||||||
|
if shadow == nil {
|
||||||
|
shadow = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var m2 map[string]interface{}
|
||||||
|
if prefix != "" {
|
||||||
|
prefix += delimiter
|
||||||
|
}
|
||||||
|
for k, val := range m {
|
||||||
|
fullKey := prefix + k
|
||||||
|
switch val.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
m2 = val.(map[string]interface{})
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
m2 = cast.ToStringMap(val)
|
||||||
|
default:
|
||||||
|
// immediate value
|
||||||
|
shadow[strings.ToLower(fullKey)] = val
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// recursively merge to shadow map
|
||||||
|
shadow = flattenAndMergeMap(shadow, m2, fullKey, delimiter)
|
||||||
|
}
|
||||||
|
return shadow
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for JSON encoding.
|
||||||
|
type Codec struct{}
|
||||||
|
|
||||||
|
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
// TODO: expose prefix and indent in the Codec as setting?
|
||||||
|
return json.MarshalIndent(v, "", " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
|
return json.Unmarshal(b, &v)
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `{
|
||||||
|
"key": "value",
|
||||||
|
"list": [
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3"
|
||||||
|
],
|
||||||
|
"map": {
|
||||||
|
"key": "value"
|
||||||
|
},
|
||||||
|
"nested_map": {
|
||||||
|
"map": {
|
||||||
|
"key": "value",
|
||||||
|
"list": [
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]interface{}{
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Encode(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(encoded), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(data, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected decoding to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
//go:build viper_toml1
|
||||||
|
// +build viper_toml1
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
|
||||||
|
type Codec struct{}
|
||||||
|
|
||||||
|
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
t, err := toml.TreeFromMap(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := t.ToTomlString()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
|
tree, err := toml.LoadBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmap := tree.ToMap()
|
||||||
|
for key, value := range tmap {
|
||||||
|
v[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
//go:build !viper_toml1
|
||||||
|
// +build !viper_toml1
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for TOML encoding.
|
||||||
|
type Codec struct{}
|
||||||
|
|
||||||
|
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
return toml.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
|
return toml.Unmarshal(b, &v)
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
//go:build !viper_toml1
|
||||||
|
// +build !viper_toml1
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// original form of the data
|
||||||
|
const original = `# key-value pair
|
||||||
|
key = "value"
|
||||||
|
list = ["item1", "item2", "item3"]
|
||||||
|
|
||||||
|
[map]
|
||||||
|
key = "value"
|
||||||
|
|
||||||
|
# nested
|
||||||
|
# map
|
||||||
|
[nested_map]
|
||||||
|
[nested_map.map]
|
||||||
|
key = "value"
|
||||||
|
list = [
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
]
|
||||||
|
`
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `key = 'value'
|
||||||
|
list = ['item1', 'item2', 'item3']
|
||||||
|
|
||||||
|
[map]
|
||||||
|
key = 'value'
|
||||||
|
|
||||||
|
[nested_map]
|
||||||
|
[nested_map.map]
|
||||||
|
key = 'value'
|
||||||
|
list = ['item1', 'item2', 'item3']
|
||||||
|
`
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]interface{}{
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Encode(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(original), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(data, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected decoding to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
//go:build viper_toml1
|
||||||
|
// +build viper_toml1
|
||||||
|
|
||||||
|
package toml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// original form of the data
|
||||||
|
const original = `# key-value pair
|
||||||
|
key = "value"
|
||||||
|
list = ["item1", "item2", "item3"]
|
||||||
|
|
||||||
|
[map]
|
||||||
|
key = "value"
|
||||||
|
|
||||||
|
# nested
|
||||||
|
# map
|
||||||
|
[nested_map]
|
||||||
|
[nested_map.map]
|
||||||
|
key = "value"
|
||||||
|
list = [
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
]
|
||||||
|
`
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `key = "value"
|
||||||
|
list = ["item1", "item2", "item3"]
|
||||||
|
|
||||||
|
[map]
|
||||||
|
key = "value"
|
||||||
|
|
||||||
|
[nested_map]
|
||||||
|
|
||||||
|
[nested_map.map]
|
||||||
|
key = "value"
|
||||||
|
list = ["item1", "item2", "item3"]
|
||||||
|
`
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]interface{}{
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Encode(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(original), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(data, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected decoding to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
// import "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
// Codec implements the encoding.Encoder and encoding.Decoder interfaces for YAML encoding.
|
||||||
|
type Codec struct{}
|
||||||
|
|
||||||
|
func (Codec) Encode(v map[string]interface{}) ([]byte, error) {
|
||||||
|
return yaml.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Codec) Decode(b []byte, v map[string]interface{}) error {
|
||||||
|
return yaml.Unmarshal(b, &v)
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCodec_Encode(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
b, err := codec.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if encoded != string(b) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", string(b), encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodec_Decode(t *testing.T) {
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(original), v)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(decoded, v) {
|
||||||
|
t.Fatalf("decoded value does not match the expected one\nactual: %#v\nexpected: %#v", v, decoded)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidData", func(t *testing.T) {
|
||||||
|
codec := Codec{}
|
||||||
|
|
||||||
|
v := map[string]interface{}{}
|
||||||
|
|
||||||
|
err := codec.Decode([]byte(`invalid data`), v)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected decoding to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("decoding failed as expected: %s", err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build viper_yaml2
|
||||||
|
// +build viper_yaml2
|
||||||
|
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import yamlv2 "gopkg.in/yaml.v2"
|
||||||
|
|
||||||
|
var yaml = struct {
|
||||||
|
Marshal func(in interface{}) (out []byte, err error)
|
||||||
|
Unmarshal func(in []byte, out interface{}) (err error)
|
||||||
|
}{
|
||||||
|
Marshal: yamlv2.Marshal,
|
||||||
|
Unmarshal: yamlv2.Unmarshal,
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
//go:build viper_yaml2
|
||||||
|
// +build viper_yaml2
|
||||||
|
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
// original form of the data
|
||||||
|
const original = `# key-value pair
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
|
||||||
|
# nested
|
||||||
|
# map
|
||||||
|
nested_map:
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
`
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
nested_map:
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
`
|
||||||
|
|
||||||
|
// decoded form of the data
|
||||||
|
//
|
||||||
|
// in case of YAML it's slightly different from Viper's internal representation
|
||||||
|
// (eg. map is decoded into a map with interface key)
|
||||||
|
var decoded = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[interface{}]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[interface{}]interface{}{
|
||||||
|
"map": map[interface{}]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]interface{}{
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build !viper_yaml2
|
||||||
|
// +build !viper_yaml2
|
||||||
|
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
import yamlv3 "gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
var yaml = struct {
|
||||||
|
Marshal func(in interface{}) (out []byte, err error)
|
||||||
|
Unmarshal func(in []byte, out interface{}) (err error)
|
||||||
|
}{
|
||||||
|
Marshal: yamlv3.Marshal,
|
||||||
|
Unmarshal: yamlv3.Unmarshal,
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
//go:build !viper_yaml2
|
||||||
|
// +build !viper_yaml2
|
||||||
|
|
||||||
|
package yaml
|
||||||
|
|
||||||
|
// original form of the data
|
||||||
|
const original = `# key-value pair
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
|
||||||
|
# nested
|
||||||
|
# map
|
||||||
|
nested_map:
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
`
|
||||||
|
|
||||||
|
// encoded form of the data
|
||||||
|
const encoded = `key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
nested_map:
|
||||||
|
map:
|
||||||
|
key: value
|
||||||
|
list:
|
||||||
|
- item1
|
||||||
|
- item2
|
||||||
|
- item3
|
||||||
|
`
|
||||||
|
|
||||||
|
// decoded form of the data
|
||||||
|
//
|
||||||
|
// in case of YAML it's slightly different from Viper's internal representation
|
||||||
|
// (eg. map is decoded into a map with interface key)
|
||||||
|
var decoded = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]interface{}{
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Viper's internal representation
|
||||||
|
var data = map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
"nested_map": map[string]interface{}{
|
||||||
|
"map": map[string]interface{}{
|
||||||
|
"key": "value",
|
||||||
|
"list": []interface{}{
|
||||||
|
"item1",
|
||||||
|
"item2",
|
||||||
|
"item3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
//go:build !go1.17
|
||||||
|
// +build !go1.17
|
||||||
|
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Based on https://github.com/frankban/quicktest/blob/577841610793d24f99e31cc2c0ef3a541fefd7c7/patch.go#L34-L64
|
||||||
|
// Licensed under the MIT license
|
||||||
|
// Copyright (c) 2017 Canonical Ltd.
|
||||||
|
|
||||||
|
// Setenv sets an environment variable to a temporary value for the
|
||||||
|
// duration of the test.
|
||||||
|
//
|
||||||
|
// At the end of the test (see "Deferred execution" in the package docs), the
|
||||||
|
// environment variable is returned to its original value.
|
||||||
|
func Setenv(t *testing.T, name, val string) {
|
||||||
|
setenv(t, name, val, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setenv sets or unsets an environment variable to a temporary value for the
|
||||||
|
// duration of the test
|
||||||
|
func setenv(t *testing.T, name, val string, valOK bool) {
|
||||||
|
oldVal, oldOK := os.LookupEnv(name)
|
||||||
|
if valOK {
|
||||||
|
os.Setenv(name, val)
|
||||||
|
} else {
|
||||||
|
os.Unsetenv(name)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if oldOK {
|
||||||
|
os.Setenv(name, oldVal)
|
||||||
|
} else {
|
||||||
|
os.Unsetenv(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
//go:build go1.17
|
||||||
|
// +build go1.17
|
||||||
|
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setenv sets an environment variable to a temporary value for the
|
||||||
|
// duration of the test.
|
||||||
|
//
|
||||||
|
// This shim can be removed once support for Go <1.17 is dropped.
|
||||||
|
func Setenv(t *testing.T, name, val string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
t.Setenv(name, val)
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AbsFilePath calls filepath.Abs on path.
|
||||||
|
func AbsFilePath(t *testing.T, path string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
s, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
jww "github.com/spf13/jwalterweatherman"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Logger is a unified interface for various logging use cases and practices, including:
|
||||||
|
// - leveled logging
|
||||||
|
// - structured logging
|
||||||
|
type Logger interface {
|
||||||
|
// Trace logs a Trace event.
|
||||||
|
//
|
||||||
|
// Even more fine-grained information than Debug events.
|
||||||
|
// Loggers not supporting this level should fall back to Debug.
|
||||||
|
Trace(msg string, keyvals ...interface{})
|
||||||
|
|
||||||
|
// Debug logs a Debug event.
|
||||||
|
//
|
||||||
|
// A verbose series of information events.
|
||||||
|
// They are useful when debugging the system.
|
||||||
|
Debug(msg string, keyvals ...interface{})
|
||||||
|
|
||||||
|
// Info logs an Info event.
|
||||||
|
//
|
||||||
|
// General information about what's happening inside the system.
|
||||||
|
Info(msg string, keyvals ...interface{})
|
||||||
|
|
||||||
|
// Warn logs a Warn(ing) event.
|
||||||
|
//
|
||||||
|
// Non-critical events that should be looked at.
|
||||||
|
Warn(msg string, keyvals ...interface{})
|
||||||
|
|
||||||
|
// Error logs an Error event.
|
||||||
|
//
|
||||||
|
// Critical events that require immediate attention.
|
||||||
|
// Loggers commonly provide Fatal and Panic levels above Error level,
|
||||||
|
// but exiting and panicing is out of scope for a logging library.
|
||||||
|
Error(msg string, keyvals ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type jwwLogger struct{}
|
||||||
|
|
||||||
|
func (jwwLogger) Trace(msg string, keyvals ...interface{}) {
|
||||||
|
jww.TRACE.Printf(jwwLogMessage(msg, keyvals...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jwwLogger) Debug(msg string, keyvals ...interface{}) {
|
||||||
|
jww.DEBUG.Printf(jwwLogMessage(msg, keyvals...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jwwLogger) Info(msg string, keyvals ...interface{}) {
|
||||||
|
jww.INFO.Printf(jwwLogMessage(msg, keyvals...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jwwLogger) Warn(msg string, keyvals ...interface{}) {
|
||||||
|
jww.WARN.Printf(jwwLogMessage(msg, keyvals...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (jwwLogger) Error(msg string, keyvals ...interface{}) {
|
||||||
|
jww.ERROR.Printf(jwwLogMessage(msg, keyvals...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func jwwLogMessage(msg string, keyvals ...interface{}) string {
|
||||||
|
out := msg
|
||||||
|
|
||||||
|
if len(keyvals) > 0 && len(keyvals)%2 == 1 {
|
||||||
|
keyvals = append(keyvals, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i <= len(keyvals)-2; i += 2 {
|
||||||
|
out = fmt.Sprintf("%s %v=%v", out, keyvals[i], keyvals[i+1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"git.internal/re/cast"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -78,6 +78,7 @@ func TestNestedOverrides(t *testing.T) {
|
||||||
func overrideDefault(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
|
func overrideDefault(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
|
||||||
return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue)
|
return overrideFromLayer(defaultLayer, assert, firstPath, firstValue, secondPath, secondValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
func override(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
|
func override(assert *assert.Assertions, firstPath string, firstValue interface{}, secondPath string, secondValue interface{}) *Viper {
|
||||||
return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue)
|
return overrideFromLayer(overrideLayer, assert, firstPath, firstValue, secondPath, secondValue)
|
||||||
}
|
}
|
||||||
|
|
182
remote/remote.go
182
remote/remote.go
|
@ -6,100 +6,110 @@
|
||||||
// Package remote integrates the remote features of Viper.
|
// Package remote integrates the remote features of Viper.
|
||||||
package remote
|
package remote
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"bytes"
|
// "bytes"
|
||||||
"io"
|
// "io"
|
||||||
"os"
|
// "os"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
// crypt "github.com/sagikazarmark/crypt/config"
|
||||||
crypt "github.com/xordataexchange/crypt/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
type remoteConfigProvider struct{}
|
// "git.internal/re/viper"
|
||||||
|
// )
|
||||||
|
|
||||||
func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader, error) {
|
// type remoteConfigProvider struct{}
|
||||||
cm, err := getConfigManager(rp)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b, err := cm.Get(rp.Path())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bytes.NewReader(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) {
|
// func (rc remoteConfigProvider) Get(rp viper.RemoteProvider) (io.Reader, error) {
|
||||||
cm, err := getConfigManager(rp)
|
// cm, err := getConfigManager(rp)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
resp, err := cm.Get(rp.Path())
|
// b, err := cm.Get(rp.Path())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
// return bytes.NewReader(b), nil
|
||||||
|
// }
|
||||||
|
|
||||||
return bytes.NewReader(resp), nil
|
// func (rc remoteConfigProvider) Watch(rp viper.RemoteProvider) (io.Reader, error) {
|
||||||
}
|
// cm, err := getConfigManager(rp)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// resp, err := cm.Get(rp.Path())
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) {
|
// return bytes.NewReader(resp), nil
|
||||||
cm, err := getConfigManager(rp)
|
// }
|
||||||
if err != nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
quit := make(chan bool)
|
|
||||||
quitwc := make(chan bool)
|
|
||||||
viperResponsCh := make(chan *viper.RemoteResponse)
|
|
||||||
cryptoResponseCh := cm.Watch(rp.Path(), quit)
|
|
||||||
// need this function to convert the Channel response form crypt.Response to viper.Response
|
|
||||||
go func(cr <-chan *crypt.Response, vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quitwc:
|
|
||||||
quit <- true
|
|
||||||
return
|
|
||||||
case resp := <-cr:
|
|
||||||
vr <- &viper.RemoteResponse{
|
|
||||||
Error: resp.Error,
|
|
||||||
Value: resp.Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
// func (rc remoteConfigProvider) WatchChannel(rp viper.RemoteProvider) (<-chan *viper.RemoteResponse, chan bool) {
|
||||||
|
// cm, err := getConfigManager(rp)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, nil
|
||||||
|
// }
|
||||||
|
// quit := make(chan bool)
|
||||||
|
// quitwc := make(chan bool)
|
||||||
|
// viperResponsCh := make(chan *viper.RemoteResponse)
|
||||||
|
// cryptoResponseCh := cm.Watch(rp.Path(), quit)
|
||||||
|
// // need this function to convert the Channel response form crypt.Response to viper.Response
|
||||||
|
// go func(cr <-chan *crypt.Response, vr chan<- *viper.RemoteResponse, quitwc <-chan bool, quit chan<- bool) {
|
||||||
|
// for {
|
||||||
|
// select {
|
||||||
|
// case <-quitwc:
|
||||||
|
// quit <- true
|
||||||
|
// return
|
||||||
|
// case resp := <-cr:
|
||||||
|
// vr <- &viper.RemoteResponse{
|
||||||
|
// Error: resp.Error,
|
||||||
|
// Value: resp.Value,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }(cryptoResponseCh, viperResponsCh, quitwc, quit)
|
||||||
|
|
||||||
}
|
// return viperResponsCh, quitwc
|
||||||
}(cryptoResponseCh, viperResponsCh, quitwc, quit)
|
// }
|
||||||
|
|
||||||
return viperResponsCh, quitwc
|
// func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
|
||||||
}
|
// var cm crypt.ConfigManager
|
||||||
|
// var err error
|
||||||
|
|
||||||
func getConfigManager(rp viper.RemoteProvider) (crypt.ConfigManager, error) {
|
// if rp.SecretKeyring() != "" {
|
||||||
var cm crypt.ConfigManager
|
// var kr *os.File
|
||||||
var err error
|
// kr, err = os.Open(rp.SecretKeyring())
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// defer kr.Close()
|
||||||
|
// switch rp.Provider() {
|
||||||
|
// case "etcd":
|
||||||
|
// cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
|
||||||
|
// case "etcd3":
|
||||||
|
// cm, err = crypt.NewEtcdV3ConfigManager([]string{rp.Endpoint()}, kr)
|
||||||
|
// case "firestore":
|
||||||
|
// cm, err = crypt.NewFirestoreConfigManager([]string{rp.Endpoint()}, kr)
|
||||||
|
// default:
|
||||||
|
// cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// switch rp.Provider() {
|
||||||
|
// case "etcd":
|
||||||
|
// cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
|
||||||
|
// case "etcd3":
|
||||||
|
// cm, err = crypt.NewStandardEtcdV3ConfigManager([]string{rp.Endpoint()})
|
||||||
|
// case "firestore":
|
||||||
|
// cm, err = crypt.NewStandardFirestoreConfigManager([]string{rp.Endpoint()})
|
||||||
|
// default:
|
||||||
|
// cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()})
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
// return cm, nil
|
||||||
|
// }
|
||||||
|
|
||||||
if rp.SecretKeyring() != "" {
|
// func init() {
|
||||||
kr, err := os.Open(rp.SecretKeyring())
|
// viper.RemoteConfig = &remoteConfigProvider{}
|
||||||
defer kr.Close()
|
// }
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if rp.Provider() == "etcd" {
|
|
||||||
cm, err = crypt.NewEtcdConfigManager([]string{rp.Endpoint()}, kr)
|
|
||||||
} else {
|
|
||||||
cm, err = crypt.NewConsulConfigManager([]string{rp.Endpoint()}, kr)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if rp.Provider() == "etcd" {
|
|
||||||
cm, err = crypt.NewStandardEtcdConfigManager([]string{rp.Endpoint()})
|
|
||||||
} else {
|
|
||||||
cm, err = crypt.NewStandardConsulConfigManager([]string{rp.Endpoint()})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cm, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
viper.RemoteConfig = &remoteConfigProvider{}
|
|
||||||
}
|
|
||||||
|
|
64
util.go
64
util.go
|
@ -18,9 +18,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"git.internal/re/cast"
|
||||||
"github.com/spf13/cast"
|
|
||||||
jww "github.com/spf13/jwalterweatherman"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigParseError denotes failing to parse configuration file.
|
// ConfigParseError denotes failing to parse configuration file.
|
||||||
|
@ -66,18 +64,25 @@ func copyAndInsensitiviseMap(m map[string]interface{}) map[string]interface{} {
|
||||||
return nm
|
return nm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func insensitiviseVal(val interface{}) interface{} {
|
||||||
|
switch val.(type) {
|
||||||
|
case map[interface{}]interface{}:
|
||||||
|
// nested map: cast and recursively insensitivise
|
||||||
|
val = cast.ToStringMap(val)
|
||||||
|
insensitiviseMap(val.(map[string]interface{}))
|
||||||
|
case map[string]interface{}:
|
||||||
|
// nested map: recursively insensitivise
|
||||||
|
insensitiviseMap(val.(map[string]interface{}))
|
||||||
|
case []interface{}:
|
||||||
|
// nested array: recursively insensitivise
|
||||||
|
insensitiveArray(val.([]interface{}))
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
func insensitiviseMap(m map[string]interface{}) {
|
func insensitiviseMap(m map[string]interface{}) {
|
||||||
for key, val := range m {
|
for key, val := range m {
|
||||||
switch val.(type) {
|
val = insensitiviseVal(val)
|
||||||
case map[interface{}]interface{}:
|
|
||||||
// nested map: cast and recursively insensitivise
|
|
||||||
val = cast.ToStringMap(val)
|
|
||||||
insensitiviseMap(val.(map[string]interface{}))
|
|
||||||
case map[string]interface{}:
|
|
||||||
// nested map: recursively insensitivise
|
|
||||||
insensitiviseMap(val.(map[string]interface{}))
|
|
||||||
}
|
|
||||||
|
|
||||||
lower := strings.ToLower(key)
|
lower := strings.ToLower(key)
|
||||||
if key != lower {
|
if key != lower {
|
||||||
// remove old key (not lower-cased)
|
// remove old key (not lower-cased)
|
||||||
|
@ -88,17 +93,20 @@ func insensitiviseMap(m map[string]interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func absPathify(inPath string) string {
|
func insensitiveArray(a []interface{}) {
|
||||||
jww.INFO.Println("Trying to resolve absolute path to", inPath)
|
for i, val := range a {
|
||||||
|
a[i] = insensitiviseVal(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(inPath, "$HOME") {
|
func absPathify(logger Logger, inPath string) string {
|
||||||
|
logger.Info("trying to resolve absolute path", "path", inPath)
|
||||||
|
|
||||||
|
if inPath == "$HOME" || strings.HasPrefix(inPath, "$HOME"+string(os.PathSeparator)) {
|
||||||
inPath = userHomeDir() + inPath[5:]
|
inPath = userHomeDir() + inPath[5:]
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(inPath, "$") {
|
inPath = os.ExpandEnv(inPath)
|
||||||
end := strings.Index(inPath, string(os.PathSeparator))
|
|
||||||
inPath = os.Getenv(inPath[1:end]) + inPath[end:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if filepath.IsAbs(inPath) {
|
if filepath.IsAbs(inPath) {
|
||||||
return filepath.Clean(inPath)
|
return filepath.Clean(inPath)
|
||||||
|
@ -109,21 +117,9 @@ func absPathify(inPath string) string {
|
||||||
return filepath.Clean(p)
|
return filepath.Clean(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
jww.ERROR.Println("Couldn't discover absolute path")
|
logger.Error(fmt.Errorf("could not discover absolute path: %w", err).Error())
|
||||||
jww.ERROR.Println(err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if File / Directory Exists
|
return ""
|
||||||
func exists(fs afero.Fs, path string) (bool, error) {
|
|
||||||
_, err := fs.Stat(path)
|
|
||||||
if err == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringInSlice(a string, list []string) bool {
|
func stringInSlice(a string, list []string) bool {
|
||||||
|
|
53
util_test.go
53
util_test.go
|
@ -11,25 +11,29 @@
|
||||||
package viper
|
package viper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"git.internal/re/viper/internal/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCopyAndInsensitiviseMap(t *testing.T) {
|
func TestCopyAndInsensitiviseMap(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
given = map[string]interface{}{
|
given = map[string]interface{}{
|
||||||
"Foo": 32,
|
"Foo": 32,
|
||||||
"Bar": map[interface{}]interface {
|
"Bar": map[interface{}]interface{}{
|
||||||
}{
|
|
||||||
"ABc": "A",
|
"ABc": "A",
|
||||||
"cDE": "B"},
|
"cDE": "B",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
expected = map[string]interface{}{
|
expected = map[string]interface{}{
|
||||||
"foo": 32,
|
"foo": 32,
|
||||||
"bar": map[string]interface {
|
"bar": map[string]interface{}{
|
||||||
}{
|
|
||||||
"abc": "A",
|
"abc": "A",
|
||||||
"cde": "B"},
|
"cde": "B",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,3 +56,40 @@ func TestCopyAndInsensitiviseMap(t *testing.T) {
|
||||||
t.Fatal("Input map changed")
|
t.Fatal("Input map changed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAbsPathify(t *testing.T) {
|
||||||
|
skipWindows(t)
|
||||||
|
|
||||||
|
home := userHomeDir()
|
||||||
|
homer := filepath.Join(home, "homer")
|
||||||
|
wd, _ := os.Getwd()
|
||||||
|
|
||||||
|
testutil.Setenv(t, "HOMER_ABSOLUTE_PATH", homer)
|
||||||
|
testutil.Setenv(t, "VAR_WITH_RELATIVE_PATH", "relative")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
output string
|
||||||
|
}{
|
||||||
|
{"", wd},
|
||||||
|
{"sub", filepath.Join(wd, "sub")},
|
||||||
|
{"./", wd},
|
||||||
|
{"./sub", filepath.Join(wd, "sub")},
|
||||||
|
{"$HOME", home},
|
||||||
|
{"$HOME/", home},
|
||||||
|
{"$HOME/sub", filepath.Join(home, "sub")},
|
||||||
|
{"$HOMER_ABSOLUTE_PATH", homer},
|
||||||
|
{"$HOMER_ABSOLUTE_PATH/", homer},
|
||||||
|
{"$HOMER_ABSOLUTE_PATH/sub", filepath.Join(homer, "sub")},
|
||||||
|
{"$VAR_WITH_RELATIVE_PATH", filepath.Join(wd, "relative")},
|
||||||
|
{"$VAR_WITH_RELATIVE_PATH/", filepath.Join(wd, "relative")},
|
||||||
|
{"$VAR_WITH_RELATIVE_PATH/sub", filepath.Join(wd, "relative", "sub")},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
got := absPathify(jwwLogger{}, test.input)
|
||||||
|
if got != test.output {
|
||||||
|
t.Errorf("Got %v\nexpected\n%q", got, test.output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
//go:build !go1.16 || !finder
|
||||||
|
// +build !go1.16 !finder
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.internal/re/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Search all configPaths for any config file.
|
||||||
|
// Returns the first path that exists (and is a config file).
|
||||||
|
func (v *Viper) findConfigFile() (string, error) {
|
||||||
|
v.logger.Info("searching for config in paths", "paths", v.configPaths)
|
||||||
|
|
||||||
|
for _, cp := range v.configPaths {
|
||||||
|
file := v.searchInPath(cp)
|
||||||
|
if file != "" {
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Viper) searchInPath(in string) (filename string) {
|
||||||
|
v.logger.Debug("searching for config in path", "path", in)
|
||||||
|
for _, ext := range SupportedExts {
|
||||||
|
v.logger.Debug("checking if file exists", "file", filepath.Join(in, v.configName+"."+ext))
|
||||||
|
if b, _ := exists(v.fs, filepath.Join(in, v.configName+"."+ext)); b {
|
||||||
|
v.logger.Debug("found file", "file", filepath.Join(in, v.configName+"."+ext))
|
||||||
|
return filepath.Join(in, v.configName+"."+ext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.configType != "" {
|
||||||
|
if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b {
|
||||||
|
return filepath.Join(in, v.configName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file Exists
|
||||||
|
func exists(fs afero.Fs, path string) (bool, error) {
|
||||||
|
stat, err := fs.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
return !stat.IsDir(), nil
|
||||||
|
}
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
//go:build go1.16 && finder
|
||||||
|
// +build go1.16,finder
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.internal/re/afero"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Search all configPaths for any config file.
|
||||||
|
// Returns the first path that exists (and is a config file).
|
||||||
|
func (v *Viper) findConfigFile() (string, error) {
|
||||||
|
finder := finder{
|
||||||
|
paths: v.configPaths,
|
||||||
|
fileNames: []string{v.configName},
|
||||||
|
extensions: SupportedExts,
|
||||||
|
withoutExtension: v.configType != "",
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := finder.Find(afero.NewIOFS(v.fs))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if file == "" {
|
||||||
|
return "", ConfigFileNotFoundError{v.configName, fmt.Sprintf("%s", v.configPaths)}
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, nil
|
||||||
|
}
|
1574
viper_test.go
1574
viper_test.go
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,56 @@
|
||||||
|
//go:build viper_yaml2
|
||||||
|
// +build viper_yaml2
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
var yamlExample = []byte(`Hacker: true
|
||||||
|
name: steve
|
||||||
|
hobbies:
|
||||||
|
- skateboarding
|
||||||
|
- snowboarding
|
||||||
|
- go
|
||||||
|
clothing:
|
||||||
|
jacket: leather
|
||||||
|
trousers: denim
|
||||||
|
pants:
|
||||||
|
size: large
|
||||||
|
age: 35
|
||||||
|
eyes : brown
|
||||||
|
beard: true
|
||||||
|
`)
|
||||||
|
|
||||||
|
var yamlWriteExpected = []byte(`age: 35
|
||||||
|
beard: true
|
||||||
|
clothing:
|
||||||
|
jacket: leather
|
||||||
|
pants:
|
||||||
|
size: large
|
||||||
|
trousers: denim
|
||||||
|
eyes: brown
|
||||||
|
hacker: true
|
||||||
|
hobbies:
|
||||||
|
- skateboarding
|
||||||
|
- snowboarding
|
||||||
|
- go
|
||||||
|
name: steve
|
||||||
|
`)
|
||||||
|
|
||||||
|
var yamlExampleWithDot = []byte(`Hacker: true
|
||||||
|
name: steve
|
||||||
|
hobbies:
|
||||||
|
- skateboarding
|
||||||
|
- snowboarding
|
||||||
|
- go
|
||||||
|
clothing:
|
||||||
|
jacket: leather
|
||||||
|
trousers: denim
|
||||||
|
pants:
|
||||||
|
size: large
|
||||||
|
age: 35
|
||||||
|
eyes : brown
|
||||||
|
beard: true
|
||||||
|
emails:
|
||||||
|
steve@hacker.com:
|
||||||
|
created: 01/02/03
|
||||||
|
active: true
|
||||||
|
`)
|
|
@ -0,0 +1,56 @@
|
||||||
|
//go:build !viper_yaml2
|
||||||
|
// +build !viper_yaml2
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
var yamlExample = []byte(`Hacker: true
|
||||||
|
name: steve
|
||||||
|
hobbies:
|
||||||
|
- skateboarding
|
||||||
|
- snowboarding
|
||||||
|
- go
|
||||||
|
clothing:
|
||||||
|
jacket: leather
|
||||||
|
trousers: denim
|
||||||
|
pants:
|
||||||
|
size: large
|
||||||
|
age: 35
|
||||||
|
eyes : brown
|
||||||
|
beard: true
|
||||||
|
`)
|
||||||
|
|
||||||
|
var yamlWriteExpected = []byte(`age: 35
|
||||||
|
beard: true
|
||||||
|
clothing:
|
||||||
|
jacket: leather
|
||||||
|
pants:
|
||||||
|
size: large
|
||||||
|
trousers: denim
|
||||||
|
eyes: brown
|
||||||
|
hacker: true
|
||||||
|
hobbies:
|
||||||
|
- skateboarding
|
||||||
|
- snowboarding
|
||||||
|
- go
|
||||||
|
name: steve
|
||||||
|
`)
|
||||||
|
|
||||||
|
var yamlExampleWithDot = []byte(`Hacker: true
|
||||||
|
name: steve
|
||||||
|
hobbies:
|
||||||
|
- skateboarding
|
||||||
|
- snowboarding
|
||||||
|
- go
|
||||||
|
clothing:
|
||||||
|
jacket: leather
|
||||||
|
trousers: denim
|
||||||
|
pants:
|
||||||
|
size: large
|
||||||
|
age: 35
|
||||||
|
eyes : brown
|
||||||
|
beard: true
|
||||||
|
emails:
|
||||||
|
steve@hacker.com:
|
||||||
|
created: 01/02/03
|
||||||
|
active: true
|
||||||
|
`)
|
|
@ -0,0 +1,12 @@
|
||||||
|
//go:build darwin || dragonfly || freebsd || openbsd || linux || netbsd || solaris || windows
|
||||||
|
// +build darwin dragonfly freebsd openbsd linux netbsd solaris windows
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import "github.com/fsnotify/fsnotify"
|
||||||
|
|
||||||
|
type watcher = fsnotify.Watcher
|
||||||
|
|
||||||
|
func newWatcher() (*watcher, error) {
|
||||||
|
return fsnotify.NewWatcher()
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
|
||||||
|
// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
|
||||||
|
|
||||||
|
package viper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newWatcher() (*watcher, error) {
|
||||||
|
return &watcher{}, fmt.Errorf("fsnotify not supported on %s", runtime.GOOS)
|
||||||
|
}
|
||||||
|
|
||||||
|
type watcher struct {
|
||||||
|
Events chan fsnotify.Event
|
||||||
|
Errors chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*watcher) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*watcher) Add(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*watcher) Remove(name string) error {
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue