mirror of
https://github.com/Luzifer/rconfig.git
synced 2025-01-04 10:26:03 +00:00
Compare commits
2 commits
4c83f34ecf
...
55beba30f9
Author | SHA1 | Date | |
---|---|---|---|
55beba30f9 | |||
c1c878fab9 |
13 changed files with 361 additions and 126 deletions
67
.github/workflows/test-and-build.yml
vendored
Normal file
67
.github/workflows/test-and-build.yml
vendored
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
name: test-and-build
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ['*']
|
||||||
|
tags: ['v*']
|
||||||
|
|
||||||
|
schedule:
|
||||||
|
# Periodically test against current Go version
|
||||||
|
- cron: '23 4 * * 0'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-and-build:
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
container:
|
||||||
|
image: luzifer/archlinux
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
GOPATH: /go
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Enable custom AUR package repo
|
||||||
|
run: echo -e "[luzifer]\nSigLevel = Never\nServer = https://archrepo.hub.luzifer.io/\$arch" >>/etc/pacman.conf
|
||||||
|
|
||||||
|
- name: Install required packages
|
||||||
|
run: |
|
||||||
|
pacman -Syy --noconfirm \
|
||||||
|
base-devel \
|
||||||
|
git \
|
||||||
|
go \
|
||||||
|
golangci-lint-bin \
|
||||||
|
trivy
|
||||||
|
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Marking workdir safe
|
||||||
|
run: git config --global --add safe.directory /__w/rconfig/rconfig
|
||||||
|
|
||||||
|
- name: Run coverage-tests
|
||||||
|
run: |
|
||||||
|
go test -v -race -cover ./...
|
||||||
|
|
||||||
|
- name: Check code for linter errors
|
||||||
|
run: |
|
||||||
|
golangci-lint run ./...
|
||||||
|
|
||||||
|
- name: Run Trivy check
|
||||||
|
run: |
|
||||||
|
trivy fs . \
|
||||||
|
--dependency-tree \
|
||||||
|
--exit-code 1 \
|
||||||
|
--format table \
|
||||||
|
--ignore-unfixed \
|
||||||
|
--quiet \
|
||||||
|
--scanners license,misconfig,secret,vuln \
|
||||||
|
--severity HIGH,CRITICAL
|
||||||
|
|
||||||
|
...
|
174
.golangci.yml
Normal file
174
.golangci.yml
Normal file
|
@ -0,0 +1,174 @@
|
||||||
|
# Derived from https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
run:
|
||||||
|
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||||
|
timeout: 5m
|
||||||
|
# Force readonly modules usage for checking
|
||||||
|
modules-download-mode: readonly
|
||||||
|
|
||||||
|
output:
|
||||||
|
format: tab
|
||||||
|
|
||||||
|
issues:
|
||||||
|
# This disables the included exclude-list in golangci-lint as that
|
||||||
|
# list for example fully hides G304 gosec rule, errcheck, exported
|
||||||
|
# rule of revive and other errors one really wants to see.
|
||||||
|
# Smme detail: https://github.com/golangci/golangci-lint/issues/456
|
||||||
|
exclude-use-default: false
|
||||||
|
# Don't limit the number of shown issues: Report ALL of them
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers [fast: true, auto-fix: false]
|
||||||
|
- bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false]
|
||||||
|
- bodyclose # checks whether HTTP response body is closed successfully [fast: true, auto-fix: false]
|
||||||
|
- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: true, auto-fix: false]
|
||||||
|
- contextcheck # check the function whether use a non-inherited context [fast: false, auto-fix: false]
|
||||||
|
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
|
||||||
|
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false]
|
||||||
|
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
|
||||||
|
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false]
|
||||||
|
- exportloopref # checks for pointers to enclosing loop variables [fast: true, auto-fix: false]
|
||||||
|
- forbidigo # Forbids identifiers [fast: true, auto-fix: false]
|
||||||
|
- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
|
||||||
|
- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
|
||||||
|
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
|
||||||
|
- gocritic # The most opinionated Go source code linter [fast: true, auto-fix: false]
|
||||||
|
- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
|
||||||
|
- godox # Tool for detection of FIXME, TODO and other comment keywords [fast: true, auto-fix: false]
|
||||||
|
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification [fast: true, auto-fix: true]
|
||||||
|
- gofumpt # Gofumpt checks whether code was gofumpt-ed. [fast: true, auto-fix: true]
|
||||||
|
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports [fast: true, auto-fix: true]
|
||||||
|
- gomnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
|
||||||
|
- gosec # Inspects source code for security problems [fast: true, auto-fix: false]
|
||||||
|
- gosimple # Linter for Go source code that specializes in simplifying a code [fast: true, auto-fix: false]
|
||||||
|
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: true, auto-fix: false]
|
||||||
|
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
|
||||||
|
- misspell # Finds commonly misspelled English words in comments [fast: true, auto-fix: true]
|
||||||
|
- nakedret # Finds naked returns in functions greater than a specified function length [fast: true, auto-fix: false]
|
||||||
|
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false]
|
||||||
|
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false]
|
||||||
|
- noctx # noctx finds sending http request without context.Context [fast: true, auto-fix: false]
|
||||||
|
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: false]
|
||||||
|
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
|
||||||
|
- staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
|
||||||
|
- stylecheck # Stylecheck is a replacement for golint [fast: true, auto-fix: false]
|
||||||
|
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 [fast: false, auto-fix: false]
|
||||||
|
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, auto-fix: false]
|
||||||
|
- unconvert # Remove unnecessary type conversions [fast: true, auto-fix: false]
|
||||||
|
- unused # Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
|
||||||
|
- wastedassign # wastedassign finds wasted assignment statements. [fast: false, auto-fix: false]
|
||||||
|
- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 60
|
||||||
|
|
||||||
|
gocyclo:
|
||||||
|
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||||
|
min-complexity: 15
|
||||||
|
|
||||||
|
gomnd:
|
||||||
|
settings:
|
||||||
|
mnd:
|
||||||
|
ignored-functions: 'strconv.(?:Format|Parse)\B+'
|
||||||
|
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
#- name: add-constant # Suggests using constant for magic numbers and string literals
|
||||||
|
# Opinion: Makes sense for strings, not for numbers but checks numbers
|
||||||
|
#- name: argument-limit # Specifies the maximum number of arguments a function can receive | Opinion: Don't need this
|
||||||
|
- name: atomic # Check for common mistaken usages of the `sync/atomic` package
|
||||||
|
- name: banned-characters # Checks banned characters in identifiers
|
||||||
|
arguments:
|
||||||
|
- ';' # Greek question mark
|
||||||
|
- name: bare-return # Warns on bare returns
|
||||||
|
- name: blank-imports # Disallows blank imports
|
||||||
|
- name: bool-literal-in-expr # Suggests removing Boolean literals from logic expressions
|
||||||
|
- name: call-to-gc # Warns on explicit call to the garbage collector
|
||||||
|
#- name: cognitive-complexity # Sets restriction for maximum Cognitive complexity.
|
||||||
|
# There is a dedicated linter for this
|
||||||
|
- name: confusing-naming # Warns on methods with names that differ only by capitalization
|
||||||
|
- name: confusing-results # Suggests to name potentially confusing function results
|
||||||
|
- name: constant-logical-expr # Warns on constant logical expressions
|
||||||
|
- name: context-as-argument # `context.Context` should be the first argument of a function.
|
||||||
|
- name: context-keys-type # Disallows the usage of basic types in `context.WithValue`.
|
||||||
|
#- name: cyclomatic # Sets restriction for maximum Cyclomatic complexity.
|
||||||
|
# There is a dedicated linter for this
|
||||||
|
#- name: datarace # Spots potential dataraces
|
||||||
|
# Is not (yet) available?
|
||||||
|
- name: deep-exit # Looks for program exits in funcs other than `main()` or `init()`
|
||||||
|
- name: defer # Warns on some [defer gotchas](https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-iii-36a1ab3d6ef1)
|
||||||
|
- name: dot-imports # Forbids `.` imports.
|
||||||
|
- name: duplicated-imports # Looks for packages that are imported two or more times
|
||||||
|
- name: early-return # Spots if-then-else statements that can be refactored to simplify code reading
|
||||||
|
- name: empty-block # Warns on empty code blocks
|
||||||
|
- name: empty-lines # Warns when there are heading or trailing newlines in a block
|
||||||
|
- name: errorf # Should replace `errors.New(fmt.Sprintf())` with `fmt.Errorf()`
|
||||||
|
- name: error-naming # Naming of error variables.
|
||||||
|
- name: error-return # The error return parameter should be last.
|
||||||
|
- name: error-strings # Conventions around error strings.
|
||||||
|
- name: exported # Naming and commenting conventions on exported symbols.
|
||||||
|
arguments: ['sayRepetitiveInsteadOfStutters']
|
||||||
|
#- name: file-header # Header which each file should have.
|
||||||
|
# Useless without config, have no config for it
|
||||||
|
- name: flag-parameter # Warns on boolean parameters that create a control coupling
|
||||||
|
#- name: function-length # Warns on functions exceeding the statements or lines max
|
||||||
|
# There is a dedicated linter for this
|
||||||
|
#- name: function-result-limit # Specifies the maximum number of results a function can return
|
||||||
|
# Opinion: Don't need this
|
||||||
|
- name: get-return # Warns on getters that do not yield any result
|
||||||
|
- name: identical-branches # Spots if-then-else statements with identical `then` and `else` branches
|
||||||
|
- name: if-return # Redundant if when returning an error.
|
||||||
|
#- name: imports-blacklist # Disallows importing the specified packages
|
||||||
|
# Useless without config, have no config for it
|
||||||
|
- name: import-shadowing # Spots identifiers that shadow an import
|
||||||
|
- name: increment-decrement # Use `i++` and `i--` instead of `i += 1` and `i -= 1`.
|
||||||
|
- name: indent-error-flow # Prevents redundant else statements.
|
||||||
|
#- name: line-length-limit # Specifies the maximum number of characters in a lined
|
||||||
|
# There is a dedicated linter for this
|
||||||
|
#- name: max-public-structs # The maximum number of public structs in a file.
|
||||||
|
# Opinion: Don't need this
|
||||||
|
- name: modifies-parameter # Warns on assignments to function parameters
|
||||||
|
- name: modifies-value-receiver # Warns on assignments to value-passed method receivers
|
||||||
|
#- name: nested-structs # Warns on structs within structs
|
||||||
|
# Opinion: Don't need this
|
||||||
|
- name: optimize-operands-order # Checks inefficient conditional expressions
|
||||||
|
#- name: package-comments # Package commenting conventions.
|
||||||
|
# Opinion: Don't need this
|
||||||
|
- name: range # Prevents redundant variables when iterating over a collection.
|
||||||
|
- name: range-val-address # Warns if address of range value is used dangerously
|
||||||
|
- name: range-val-in-closure # Warns if range value is used in a closure dispatched as goroutine
|
||||||
|
- name: receiver-naming # Conventions around the naming of receivers.
|
||||||
|
- name: redefines-builtin-id # Warns on redefinitions of builtin identifiers
|
||||||
|
#- name: string-format # Warns on specific string literals that fail one or more user-configured regular expressions
|
||||||
|
# Useless without config, have no config for it
|
||||||
|
- name: string-of-int # Warns on suspicious casts from int to string
|
||||||
|
- name: struct-tag # Checks common struct tags like `json`,`xml`,`yaml`
|
||||||
|
- name: superfluous-else # Prevents redundant else statements (extends indent-error-flow)
|
||||||
|
- name: time-equal # Suggests to use `time.Time.Equal` instead of `==` and `!=` for equality check time.
|
||||||
|
- name: time-naming # Conventions around the naming of time variables.
|
||||||
|
- name: unconditional-recursion # Warns on function calls that will lead to (direct) infinite recursion
|
||||||
|
- name: unexported-naming # Warns on wrongly named un-exported symbols
|
||||||
|
- name: unexported-return # Warns when a public return is from unexported type.
|
||||||
|
- name: unhandled-error # Warns on unhandled errors returned by funcion calls
|
||||||
|
arguments:
|
||||||
|
- "fmt.(Fp|P)rint(f|ln|)"
|
||||||
|
- name: unnecessary-stmt # Suggests removing or simplifying unnecessary statements
|
||||||
|
- name: unreachable-code # Warns on unreachable code
|
||||||
|
- name: unused-parameter # Suggests to rename or remove unused function parameters
|
||||||
|
- name: unused-receiver # Suggests to rename or remove unused method receivers
|
||||||
|
#- name: use-any # Proposes to replace `interface{}` with its alias `any`
|
||||||
|
# Is not (yet) available?
|
||||||
|
- name: useless-break # Warns on useless `break` statements in case clauses
|
||||||
|
- name: var-declaration # Reduces redundancies around variable declaration.
|
||||||
|
- name: var-naming # Naming rules.
|
||||||
|
- name: waitgroup-by-value # Warns on functions taking sync.WaitGroup as a by-value parameter
|
||||||
|
|
||||||
|
...
|
|
@ -1,10 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
image: "reporunner/golang-alpine"
|
|
||||||
checkout_dir: /go/src/github.com/Luzifer/rconfig
|
|
||||||
|
|
||||||
commands:
|
|
||||||
- apk --no-cache add build-base
|
|
||||||
- go test -v -race -cover ./...
|
|
||||||
|
|
||||||
...
|
|
|
@ -2,6 +2,8 @@ package rconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeriveEnvVarName(t *testing.T) {
|
func TestDeriveEnvVarName(t *testing.T) {
|
||||||
|
@ -17,13 +19,11 @@ func TestDeriveEnvVarName(t *testing.T) {
|
||||||
"CamelCase": "CAMEL_CASE",
|
"CamelCase": "CAMEL_CASE",
|
||||||
"_foobar": "FOOBAR",
|
"_foobar": "FOOBAR",
|
||||||
"ILoveGoAndJSONSoMuch": "I_LOVE_GO_AND_JSON_SO_MUCH",
|
"ILoveGoAndJSONSoMuch": "I_LOVE_GO_AND_JSON_SO_MUCH",
|
||||||
"mrT": "MR_T",
|
"mrT": "MR_T",
|
||||||
"my_case1": "MY_CASE1",
|
"my_case1": "MY_CASE1",
|
||||||
"MyFieldName": "MY_FIELD_NAME",
|
"MyFieldName": "MY_FIELD_NAME",
|
||||||
"SmallCASE": "SMALL_CASE",
|
"SmallCASE": "SMALL_CASE",
|
||||||
} {
|
} {
|
||||||
if d := deriveEnvVarName(test); d != expect {
|
assert.Equal(t, expect, deriveEnvVarName(test))
|
||||||
t.Errorf("Derived variable %q did not match expectation %q", d, expect)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
87
config.go
87
config.go
|
@ -49,21 +49,20 @@ func init() {
|
||||||
// For your configuration struct you can use the following struct-tags to control
|
// For your configuration struct you can use the following struct-tags to control
|
||||||
// the behavior of rconfig:
|
// the behavior of rconfig:
|
||||||
//
|
//
|
||||||
// default: Set a default value
|
// default: Set a default value
|
||||||
// vardefault: Read the default value from the variable defaults
|
// vardefault: Read the default value from the variable defaults
|
||||||
// env: Read the value from this environment variable
|
// env: Read the value from this environment variable
|
||||||
// flag: Flag to read in format "long,short" (for example "listen,l")
|
// flag: Flag to read in format "long,short" (for example "listen,l")
|
||||||
// description: A help text for Usage output to guide your users
|
// description: A help text for Usage output to guide your users
|
||||||
//
|
//
|
||||||
// The format you need to specify those values you can see in the example to this
|
// The format you need to specify those values you can see in the example to this
|
||||||
// function.
|
// function.
|
||||||
//
|
|
||||||
func Parse(config interface{}) error {
|
func Parse(config interface{}) error {
|
||||||
return parse(config, nil)
|
return parse(config, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAndValidate works exactly like Parse but implements an additional run of
|
// ParseAndValidate works exactly like Parse but implements an additional run of
|
||||||
// the go-validator package on the configuration struct. Therefore additonal struct
|
// the go-validator package on the configuration struct. Therefore additional struct
|
||||||
// tags are supported like described in the readme file of the go-validator package:
|
// tags are supported like described in the readme file of the go-validator package:
|
||||||
//
|
//
|
||||||
// https://github.com/go-validator/validator/tree/v2#usage
|
// https://github.com/go-validator/validator/tree/v2#usage
|
||||||
|
@ -103,14 +102,20 @@ func SetVariableDefaults(defaults map[string]string) {
|
||||||
variableDefaults = defaults
|
variableDefaults = defaults
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAndValidate(in interface{}, args []string) error {
|
//revive:disable-next-line:confusing-naming // The public function is only a wrapper with less args
|
||||||
if err := parse(in, args); err != nil {
|
func parseAndValidate(in interface{}, args []string) (err error) {
|
||||||
|
if err = parse(in, args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return validator.Validate(in)
|
if err = validator.Validate(in); err != nil {
|
||||||
|
return fmt.Errorf("validating values: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//revive:disable-next-line:confusing-naming // The public function is only a wrapper with less args
|
||||||
func parse(in interface{}, args []string) error {
|
func parse(in interface{}, args []string) error {
|
||||||
if args == nil {
|
if args == nil {
|
||||||
args = os.Args
|
args = os.Args
|
||||||
|
@ -123,27 +128,26 @@ func parse(in interface{}, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := fs.Parse(args); err != nil {
|
||||||
return err
|
return fmt.Errorf("parsing flag-set: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if afterFuncs != nil {
|
for _, f := range afterFuncs {
|
||||||
for _, f := range afterFuncs {
|
if err := f(); err != nil {
|
||||||
if err := f(); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:funlen,gocognit,gocyclo // Hard to split
|
||||||
func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
||||||
if reflect.TypeOf(in).Kind() != reflect.Ptr {
|
if reflect.TypeOf(in).Kind() != reflect.Ptr {
|
||||||
return nil, errors.New("Calling parser with non-pointer")
|
return nil, errors.New("calling parser with non-pointer")
|
||||||
}
|
}
|
||||||
|
|
||||||
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
|
if reflect.ValueOf(in).Elem().Kind() != reflect.Struct {
|
||||||
return nil, errors.New("Calling parser with pointer to non-struct")
|
return nil, errors.New("calling parser with pointer to non-struct")
|
||||||
}
|
}
|
||||||
|
|
||||||
afterFuncs := []afterFunc{}
|
afterFuncs := []afterFunc{}
|
||||||
|
@ -166,11 +170,10 @@ func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
||||||
case reflect.TypeOf(time.Duration(0)):
|
case reflect.TypeOf(time.Duration(0)):
|
||||||
v, err := time.ParseDuration(value)
|
v, err := time.ParseDuration(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if value == "" {
|
if value != "" {
|
||||||
v = time.Duration(0)
|
return nil, fmt.Errorf("parsing time.Duration: %w", err)
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
v = time.Duration(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if typeField.Tag.Get("flag") != "" {
|
if typeField.Tag.Get("flag") != "" {
|
||||||
|
@ -215,14 +218,13 @@ func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
||||||
matched := false
|
matched := false
|
||||||
for _, tf := range timeParserFormats {
|
for _, tf := range timeParserFormats {
|
||||||
if t, err := time.Parse(tf, *sVar); err == nil {
|
if t, err := time.Parse(tf, *sVar); err == nil {
|
||||||
matched = true
|
|
||||||
valField.Set(reflect.ValueOf(t))
|
valField.Set(reflect.ValueOf(t))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !matched {
|
if !matched {
|
||||||
return fmt.Errorf("Value %q did not match expected time formats", *sVar)
|
return fmt.Errorf("value %q did not match expected time formats", *sVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -259,11 +261,10 @@ func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
||||||
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
|
||||||
vt, err := strconv.ParseInt(value, 10, 64)
|
vt, err := strconv.ParseInt(value, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if value == "" {
|
if value != "" {
|
||||||
vt = 0
|
return nil, fmt.Errorf("parsing int: %w", err)
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
vt = 0
|
||||||
}
|
}
|
||||||
if typeField.Tag.Get("flag") != "" {
|
if typeField.Tag.Get("flag") != "" {
|
||||||
registerFlagInt(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
registerFlagInt(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
@ -274,11 +275,10 @@ func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
vt, err := strconv.ParseUint(value, 10, 64)
|
vt, err := strconv.ParseUint(value, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if value == "" {
|
if value != "" {
|
||||||
vt = 0
|
return nil, fmt.Errorf("parsing uint: %w", err)
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
vt = 0
|
||||||
}
|
}
|
||||||
if typeField.Tag.Get("flag") != "" {
|
if typeField.Tag.Get("flag") != "" {
|
||||||
registerFlagUint(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
registerFlagUint(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
@ -289,11 +289,10 @@ func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
vt, err := strconv.ParseFloat(value, 64)
|
vt, err := strconv.ParseFloat(value, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if value == "" {
|
if value != "" {
|
||||||
vt = 0.0
|
return nil, fmt.Errorf("parsing float: %w", err)
|
||||||
} else {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
vt = 0.0
|
||||||
}
|
}
|
||||||
if typeField.Tag.Get("flag") != "" {
|
if typeField.Tag.Get("flag") != "" {
|
||||||
registerFlagFloat(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
registerFlagFloat(typeField.Type.Kind(), fs, valField.Addr().Interface(), parts, vt, typeField.Tag.Get("description"))
|
||||||
|
@ -315,7 +314,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
||||||
for _, v := range strings.Split(value, ",") {
|
for _, v := range strings.Split(value, ",") {
|
||||||
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
|
it, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("parsing int: %w", err)
|
||||||
}
|
}
|
||||||
def = append(def, int(it))
|
def = append(def, int(it))
|
||||||
}
|
}
|
||||||
|
@ -329,7 +328,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) ([]afterFunc, error) {
|
||||||
if len(del) == 0 {
|
if len(del) == 0 {
|
||||||
del = ","
|
del = ","
|
||||||
}
|
}
|
||||||
var def = []string{}
|
def := []string{}
|
||||||
if value != "" {
|
if value != "" {
|
||||||
def = strings.Split(value, del)
|
def = strings.Split(value, del)
|
||||||
}
|
}
|
||||||
|
@ -355,9 +354,9 @@ func registerFlagFloat(t reflect.Kind, fs *pflag.FlagSet, field interface{}, par
|
||||||
}
|
}
|
||||||
case reflect.Float64:
|
case reflect.Float64:
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
fs.Float64Var(field.(*float64), parts[0], float64(vt), desc)
|
fs.Float64Var(field.(*float64), parts[0], vt, desc)
|
||||||
} else {
|
} else {
|
||||||
fs.Float64VarP(field.(*float64), parts[0], parts[1], float64(vt), desc)
|
fs.Float64VarP(field.(*float64), parts[0], parts[1], vt, desc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -384,9 +383,9 @@ func registerFlagInt(t reflect.Kind, fs *pflag.FlagSet, field interface{}, parts
|
||||||
}
|
}
|
||||||
case reflect.Int64:
|
case reflect.Int64:
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
fs.Int64Var(field.(*int64), parts[0], int64(vt), desc)
|
fs.Int64Var(field.(*int64), parts[0], vt, desc)
|
||||||
} else {
|
} else {
|
||||||
fs.Int64VarP(field.(*int64), parts[0], parts[1], int64(vt), desc)
|
fs.Int64VarP(field.(*int64), parts[0], parts[1], vt, desc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,9 +418,9 @@ func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, part
|
||||||
}
|
}
|
||||||
case reflect.Uint64:
|
case reflect.Uint64:
|
||||||
if len(parts) == 1 {
|
if len(parts) == 1 {
|
||||||
fs.Uint64Var(field.(*uint64), parts[0], uint64(vt), desc)
|
fs.Uint64Var(field.(*uint64), parts[0], vt, desc)
|
||||||
} else {
|
} else {
|
||||||
fs.Uint64VarP(field.(*uint64), parts[0], parts[1], uint64(vt), desc)
|
fs.Uint64VarP(field.(*uint64), parts[0], parts[1], vt, desc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@ package rconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestErrors(t *testing.T) {
|
func TestErrors(t *testing.T) {
|
||||||
for test, parsable := range map[string]interface{}{
|
for test, parsable := range map[string]interface{}{
|
||||||
"use string as default to int": struct {
|
"use string as default to int": struct {
|
||||||
A int `default:"a"`
|
A int `default:"a"` //revive:disable-line:struct-tag // Intentional error for testing
|
||||||
}{},
|
}{},
|
||||||
"use string as default to float": struct {
|
"use string as default to float": struct {
|
||||||
A float32 `default:"a"`
|
A float32 `default:"a"`
|
||||||
|
@ -24,18 +26,12 @@ func TestErrors(t *testing.T) {
|
||||||
A []int `default:"a,b"`
|
A []int `default:"a,b"`
|
||||||
}{},
|
}{},
|
||||||
} {
|
} {
|
||||||
if err := parse(&parsable, nil); err == nil {
|
assert.Error(t, parse(&parsable, nil), test) //#nosec:G601 // Fine for this test
|
||||||
t.Errorf("Expected error but got none. Test: %s", test)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := parse(struct {
|
assert.Error(t, parse(struct {
|
||||||
A string `default:"a"`
|
A string `default:"a"`
|
||||||
}{}, nil); err == nil {
|
}{}, nil), "feeding non-pointer to parse")
|
||||||
t.Errorf("Expected error when feeding non-pointer struct to parse")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parse("test", nil); err == nil {
|
assert.Error(t, parse("test", nil), "feeding non-pointer string to parse")
|
||||||
t.Errorf("Expected error when feeding non-pointer string to parse")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package rconfig
|
package rconfig_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/Luzifer/rconfig/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ExampleParse() {
|
func ExampleParse() {
|
||||||
|
@ -23,14 +25,16 @@ func ExampleParse() {
|
||||||
"--user=Luzifer",
|
"--user=Luzifer",
|
||||||
}
|
}
|
||||||
|
|
||||||
Parse(&config)
|
if err := rconfig.Parse(&config); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
|
fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
|
||||||
config.Username,
|
config.Username,
|
||||||
config.Details.Age)
|
config.Details.Age)
|
||||||
|
|
||||||
// You can also show an usage message for your user
|
// You can also show an usage message for your user
|
||||||
Usage()
|
rconfig.Usage()
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// Hello Luzifer, happy birthday for your 25th birthday.
|
// Hello Luzifer, happy birthday for your 25th birthday.
|
||||||
|
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGeneralExecution(t *testing.T) {
|
func TestGeneralExecution(t *testing.T) {
|
||||||
|
@ -20,14 +23,10 @@ func TestGeneralExecution(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
exec := func(desc string, tests [][2]interface{}) {
|
exec := func(desc string, tests [][2]interface{}) {
|
||||||
if err := parse(&cfg, args); err != nil {
|
require.NoError(t, parse(&cfg, args))
|
||||||
t.Fatalf("Parsing options caused error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
if !reflect.DeepEqual(reflect.ValueOf(test[0]).Elem().Interface(), test[1]) {
|
assert.Equal(t, test[1], reflect.ValueOf(test[0]).Elem().Interface(), desc)
|
||||||
t.Errorf("%q expected value does not match: %#v != %#v", desc, test[0], test[1])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,11 +50,11 @@ func TestGeneralExecution(t *testing.T) {
|
||||||
|
|
||||||
cfg = test{}
|
cfg = test{}
|
||||||
args = []string{}
|
args = []string{}
|
||||||
os.Setenv("shell", "test546")
|
require.NoError(t, os.Setenv("shell", "test546"))
|
||||||
exec("no arguments and set env", [][2]interface{}{
|
exec("no arguments and set env", [][2]interface{}{
|
||||||
{&cfg.Test, "test546"},
|
{&cfg.Test, "test546"},
|
||||||
})
|
})
|
||||||
os.Unsetenv("shell")
|
require.NoError(t, os.Unsetenv("shell"))
|
||||||
|
|
||||||
cfg = test{}
|
cfg = test{}
|
||||||
args = []string{
|
args = []string{
|
||||||
|
@ -70,9 +69,7 @@ func TestGeneralExecution(t *testing.T) {
|
||||||
{&cfg.DefaultFlag, "goo"},
|
{&cfg.DefaultFlag, "goo"},
|
||||||
})
|
})
|
||||||
|
|
||||||
if !reflect.DeepEqual(Args(), []string{"positional1", "positional2"}) {
|
assert.Equal(t, []string{"positional1", "positional2"}, Args())
|
||||||
t.Errorf("expected positional arguments to match")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestValidationIntegration(t *testing.T) {
|
func TestValidationIntegration(t *testing.T) {
|
||||||
|
@ -85,7 +82,5 @@ func TestValidationIntegration(t *testing.T) {
|
||||||
args = []string{}
|
args = []string{}
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := parseAndValidate(&cfgValidated, args); err == nil {
|
assert.Error(t, parseAndValidate(&cfgValidated, args))
|
||||||
t.Errorf("Expected error, got none")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
12
go.mod
12
go.mod
|
@ -1,9 +1,15 @@
|
||||||
module github.com/Luzifer/rconfig/v2
|
module github.com/Luzifer/rconfig/v2
|
||||||
|
|
||||||
go 1.17
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
gopkg.in/validator.v2 v2.0.0-20210331031555-b37d688a7fb0
|
github.com/stretchr/testify v1.8.4
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/validator.v2 v2.0.1
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
18
go.sum
18
go.sum
|
@ -1,8 +1,16 @@
|
||||||
|
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/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
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/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
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/validator.v2 v2.0.0-20210331031555-b37d688a7fb0 h1:EFLtLCwd8tGN+r/ePz3cvRtdsfYNhDEdt/vp6qsT+0A=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/validator.v2 v2.0.0-20210331031555-b37d688a7fb0/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc=
|
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -3,6 +3,9 @@ package rconfig
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrecedence(t *testing.T) {
|
func TestPrecedence(t *testing.T) {
|
||||||
|
@ -11,7 +14,6 @@ func TestPrecedence(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
|
||||||
cfg testcfg
|
cfg testcfg
|
||||||
args []string
|
args []string
|
||||||
vardefaults map[string]string
|
vardefaults map[string]string
|
||||||
|
@ -20,20 +22,14 @@ func TestPrecedence(t *testing.T) {
|
||||||
exec := func(desc string, fn func() interface{}, exp interface{}) {
|
exec := func(desc string, fn func() interface{}, exp interface{}) {
|
||||||
cfg = testcfg{}
|
cfg = testcfg{}
|
||||||
SetVariableDefaults(vardefaults)
|
SetVariableDefaults(vardefaults)
|
||||||
err = parse(&cfg, args)
|
assert.NoError(t, parse(&cfg, args), desc)
|
||||||
|
|
||||||
if err != nil {
|
assert.Equal(t, exp, fn())
|
||||||
t.Errorf("%q parsing caused error: %s", desc, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if res := fn(); res != exp {
|
|
||||||
t.Errorf("%q expected value does not match: %#v != %#v", desc, res, exp)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provided: Flag, Env, Default, VarDefault
|
// Provided: Flag, Env, Default, VarDefault
|
||||||
args = []string{"-a", "5"}
|
args = []string{"-a", "5"}
|
||||||
os.Setenv("a", "8")
|
require.NoError(t, os.Setenv("a", "8"))
|
||||||
vardefaults = map[string]string{
|
vardefaults = map[string]string{
|
||||||
"a": "3",
|
"a": "3",
|
||||||
}
|
}
|
||||||
|
@ -42,7 +38,7 @@ func TestPrecedence(t *testing.T) {
|
||||||
|
|
||||||
// Provided: Env, Default, VarDefault
|
// Provided: Env, Default, VarDefault
|
||||||
args = []string{}
|
args = []string{}
|
||||||
os.Setenv("a", "8")
|
require.NoError(t, os.Setenv("a", "8"))
|
||||||
vardefaults = map[string]string{
|
vardefaults = map[string]string{
|
||||||
"a": "3",
|
"a": "3",
|
||||||
}
|
}
|
||||||
|
@ -51,7 +47,7 @@ func TestPrecedence(t *testing.T) {
|
||||||
|
|
||||||
// Provided: Default, VarDefault
|
// Provided: Default, VarDefault
|
||||||
args = []string{}
|
args = []string{}
|
||||||
os.Unsetenv("a")
|
require.NoError(t, os.Unsetenv("a"))
|
||||||
vardefaults = map[string]string{
|
vardefaults = map[string]string{
|
||||||
"a": "3",
|
"a": "3",
|
||||||
}
|
}
|
||||||
|
@ -60,7 +56,7 @@ func TestPrecedence(t *testing.T) {
|
||||||
|
|
||||||
// Provided: Default
|
// Provided: Default
|
||||||
args = []string{}
|
args = []string{}
|
||||||
os.Unsetenv("a")
|
require.NoError(t, os.Unsetenv("a"))
|
||||||
vardefaults = map[string]string{}
|
vardefaults = map[string]string{}
|
||||||
|
|
||||||
exec("Provided: Default", func() interface{} { return cfg.A }, 1)
|
exec("Provided: Default", func() interface{} { return cfg.A }, 1)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package rconfig
|
package rconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"os"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VarDefaultsFromYAMLFile reads contents of a file and calls VarDefaultsFromYAML
|
// VarDefaultsFromYAMLFile reads contents of a file and calls VarDefaultsFromYAML
|
||||||
func VarDefaultsFromYAMLFile(filename string) map[string]string {
|
func VarDefaultsFromYAMLFile(filename string) map[string]string {
|
||||||
data, err := ioutil.ReadFile(filename)
|
data, err := os.ReadFile(filename) //#nosec:G304 // Loading file from var is intended
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return make(map[string]string)
|
return make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package rconfig
|
package rconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestVardefaultParsing(t *testing.T) {
|
func TestVardefaultParsing(t *testing.T) {
|
||||||
|
@ -18,6 +20,7 @@ func TestVardefaultParsing(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
cfg test
|
cfg test
|
||||||
args = []string{}
|
args = []string{}
|
||||||
|
err error
|
||||||
vardefaults = map[string]string{
|
vardefaults = map[string]string{
|
||||||
"my_secret_value": "veryverysecretkey",
|
"my_secret_value": "veryverysecretkey",
|
||||||
"unkownkey": "hi there",
|
"unkownkey": "hi there",
|
||||||
|
@ -26,14 +29,10 @@ func TestVardefaultParsing(t *testing.T) {
|
||||||
)
|
)
|
||||||
|
|
||||||
exec := func(desc string, tests [][2]interface{}) {
|
exec := func(desc string, tests [][2]interface{}) {
|
||||||
if err := parse(&cfg, args); err != nil {
|
require.NoError(t, parse(&cfg, args))
|
||||||
t.Fatalf("Parsing options caused error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
if !reflect.DeepEqual(reflect.ValueOf(test[0]).Elem().Interface(), test[1]) {
|
assert.Equal(t, test[1], reflect.ValueOf(test[0]).Elem().Interface(), desc)
|
||||||
t.Errorf("%q expected value does not match: %#v != %#v", desc, test[0], test[1])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,13 +52,14 @@ func TestVardefaultParsing(t *testing.T) {
|
||||||
{&cfg.SomeVar, ""},
|
{&cfg.SomeVar, ""},
|
||||||
})
|
})
|
||||||
|
|
||||||
tmp, _ := ioutil.TempFile("", "")
|
tmp, _ := os.CreateTemp("", "")
|
||||||
defer func() {
|
t.Cleanup(func() {
|
||||||
tmp.Close()
|
tmp.Close() //nolint:errcheck,gosec,revive // Just cleanup, will be closed automatically
|
||||||
os.Remove(tmp.Name())
|
os.Remove(tmp.Name()) //nolint:errcheck,gosec,revive // Just cleanup of tmp-file
|
||||||
}()
|
})
|
||||||
yamlData := "---\nmy_secret_value: veryverysecretkey\nunknownkey: hi there\nint_var: 42\n"
|
yamlData := "---\nmy_secret_value: veryverysecretkey\nunknownkey: hi there\nint_var: 42\n"
|
||||||
tmp.WriteString(yamlData)
|
_, err = tmp.WriteString(yamlData)
|
||||||
|
require.NoError(t, err)
|
||||||
SetVariableDefaults(VarDefaultsFromYAMLFile(tmp.Name()))
|
SetVariableDefaults(VarDefaultsFromYAMLFile(tmp.Name()))
|
||||||
exec("defaults from YAML file", [][2]interface{}{
|
exec("defaults from YAML file", [][2]interface{}{
|
||||||
{&cfg.IntVar, int64(42)},
|
{&cfg.IntVar, int64(42)},
|
||||||
|
|
Loading…
Reference in a new issue