From efaed1116ad9ca3143c60e87a8fd82d0bffee25d Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 5 Mar 2024 00:33:27 +0300 Subject: [PATCH 01/11] [options] Fix panic when parsing unsupported option with value passed with equal sign ('=') --- CHANGELOG.md | 4 ++++ ek.go | 2 +- options/options.go | 8 +++++++- options/options_test.go | 7 +++++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 066cc8fc..c1a7242a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Changelog +### 12.103.0 + +* `[options]` Fixed panic when parsing unsupported option with value passed with equal sign (`=`) + ### 12.102.0 * `[knf/validators/network]` Added `Mail` validator diff --git a/ek.go b/ek.go index 6bc17959..d5e878e9 100644 --- a/ek.go +++ b/ek.go @@ -20,7 +20,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // // VERSION is current ek package version -const VERSION = "12.102.0" +const VERSION = "12.103.0" // ////////////////////////////////////////////////////////////////////////////////// // diff --git a/options/options.go b/options/options.go index 22a90bf4..90d4b44a 100644 --- a/options/options.go +++ b/options/options.go @@ -634,7 +634,13 @@ func (opts *Options) parseLongOption(opt string) (string, string, error) { return "", "", OptionError{"--" + optSlice[0], "", ERROR_WRONG_FORMAT} } - return optSlice[0], strings.Join(optSlice[1:], "="), nil + optName := optSlice[0] + + if opts.full[optName] == nil { + return "", "", OptionError{"--" + optName, "", ERROR_UNSUPPORTED} + } + + return optName, strings.Join(optSlice[1:], "="), nil } if opts.full[opt] != nil { diff --git a/options/options_test.go b/options/options_test.go index f59e8755..df89c2c4 100644 --- a/options/options_test.go +++ b/options/options_test.go @@ -406,6 +406,13 @@ func (s *OptUtilSuite) TestParsing(c *C) { // //////////////////////////////////////////////////////////////////////////////// // + _, errs = NewOptions().Parse([]string{"--test=abcd"}, Map{"s:string": {}}) + + c.Assert(errs, Not(HasLen), 0) + c.Assert(errs[0].Error(), Equals, "Option --test is not supported") + + // //////////////////////////////////////////////////////////////////////////////// // + fArgs, errs := NewOptions().Parse([]string{"-", "--"}, Map{"t:test": {}}) c.Assert(errs, HasLen, 0) From 5bf087df99f4514eb609728ca46891f7dbad2cda Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 5 Mar 2024 00:41:32 +0300 Subject: [PATCH 02/11] [options] Code refactoring --- CHANGELOG.md | 1 + options/options.go | 20 ++++++++------------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1a7242a..87f084f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### 12.103.0 * `[options]` Fixed panic when parsing unsupported option with value passed with equal sign (`=`) +* `[options]` Code refactoring ### 12.102.0 diff --git a/options/options.go b/options/options.go index 90d4b44a..bd51ff3a 100644 --- a/options/options.go +++ b/options/options.go @@ -628,19 +628,17 @@ func (opts *Options) parseOptions(rawOpts []string) (Arguments, []error) { func (opts *Options) parseLongOption(opt string) (string, string, error) { if strings.Contains(opt, "=") { - optSlice := strings.Split(opt, "=") + optName, optValue, ok := strings.Cut(opt, "=") - if len(optSlice) <= 1 || optSlice[1] == "" { - return "", "", OptionError{"--" + optSlice[0], "", ERROR_WRONG_FORMAT} + if ok && optValue == "" { + return "", "", OptionError{"--" + optName, "", ERROR_WRONG_FORMAT} } - optName := optSlice[0] - if opts.full[optName] == nil { return "", "", OptionError{"--" + optName, "", ERROR_UNSUPPORTED} } - return optName, strings.Join(optSlice[1:], "="), nil + return optName, optValue, nil } if opts.full[opt] != nil { @@ -652,19 +650,17 @@ func (opts *Options) parseLongOption(opt string) (string, string, error) { func (opts *Options) parseShortOption(opt string) (string, string, error) { if strings.Contains(opt, "=") { - optSlice := strings.Split(opt, "=") + optName, optValue, ok := strings.Cut(opt, "=") - if len(optSlice) <= 1 || optSlice[1] == "" { - return "", "", OptionError{"-" + optSlice[0], "", ERROR_WRONG_FORMAT} + if ok && optValue == "" { + return "", "", OptionError{"-" + optName, "", ERROR_WRONG_FORMAT} } - optName := optSlice[0] - if opts.short[optName] == "" { return "", "", OptionError{"-" + optName, "", ERROR_UNSUPPORTED} } - return opts.short[optName], strings.Join(optSlice[1:], "="), nil + return opts.short[optName], optValue, nil } if len(opt) > 2 { From e85ab2ae72a58db328f942cde4655209117366f8 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 5 Mar 2024 01:55:17 +0300 Subject: [PATCH 03/11] [knf] Code refactoring --- .scripts/packages.list | 1 + CHANGELOG.md | 1 + knf/knf.go | 87 ++------------------ knf/value/value.go | 178 ++++++++++++++++++++++++++++++++++++++++ knf/value/value_test.go | 125 ++++++++++++++++++++++++++++ 5 files changed, 312 insertions(+), 80 deletions(-) create mode 100644 knf/value/value.go create mode 100644 knf/value/value_test.go diff --git a/.scripts/packages.list b/.scripts/packages.list index 99700359..acb89ee6 100644 --- a/.scripts/packages.list +++ b/.scripts/packages.list @@ -25,6 +25,7 @@ * + knf/validators/network * + knf/validators/regexp * + knf/validators/system +* + knf/value * + lock * + log * + lscolors diff --git a/CHANGELOG.md b/CHANGELOG.md index 87f084f1..d7ad3a69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * `[options]` Fixed panic when parsing unsupported option with value passed with equal sign (`=`) * `[options]` Code refactoring +* `[knf]` Code refactoring ### 12.102.0 diff --git a/knf/knf.go b/knf/knf.go index 8764da00..a0b840f1 100644 --- a/knf/knf.go +++ b/knf/knf.go @@ -13,10 +13,11 @@ import ( "errors" "os" "path" - "strconv" "strings" "sync" "time" + + "github.com/essentialkaos/ek/v12/knf/value" ) // ////////////////////////////////////////////////////////////////////////////////// // @@ -405,32 +406,7 @@ func (c *Config) GetI64(name string, defvals ...int64) int64 { val := c.data[strings.ToLower(name)] c.mx.RUnlock() - if val == "" { - if len(defvals) == 0 { - return 0 - } - - return defvals[0] - } - - // HEX Parsing - if len(val) >= 3 && val[0:2] == "0x" { - valHex, err := strconv.ParseInt(val[2:], 16, 0) - - if err != nil { - return 0 - } - - return valHex - } - - valInt, err := strconv.ParseInt(val, 10, 0) - - if err != nil { - return 0 - } - - return valInt + return value.ParseInt64(val, defvals...) } // GetI returns configuration value as int @@ -474,21 +450,7 @@ func (c *Config) GetF(name string, defvals ...float64) float64 { val := c.data[strings.ToLower(name)] c.mx.RUnlock() - if val == "" { - if len(defvals) == 0 { - return 0.0 - } - - return defvals[0] - } - - valFl, err := strconv.ParseFloat(val, 64) - - if err != nil { - return 0.0 - } - - return valFl + return value.ParseFloat(val, defvals...) } // GetB returns configuration value as boolean @@ -505,20 +467,7 @@ func (c *Config) GetB(name string, defvals ...bool) bool { val := c.data[strings.ToLower(name)] c.mx.RUnlock() - if val == "" { - if len(defvals) == 0 { - return false - } - - return defvals[0] - } - - switch val { - case "", "0", "false", "no": - return false - default: - return true - } + return value.ParseBool(val, defvals...) } // GetM returns configuration value as file mode @@ -535,21 +484,7 @@ func (c *Config) GetM(name string, defvals ...os.FileMode) os.FileMode { val := c.data[strings.ToLower(name)] c.mx.RUnlock() - if val == "" { - if len(defvals) == 0 { - return os.FileMode(0) - } - - return defvals[0] - } - - valM, err := strconv.ParseUint(val, 8, 32) - - if err != nil { - return 0 - } - - return os.FileMode(valM) + return value.ParseMode(val, defvals...) } // GetD returns configuration value as duration @@ -566,15 +501,7 @@ func (c *Config) GetD(name string, mod DurationMod, defvals ...time.Duration) ti val := c.data[strings.ToLower(name)] c.mx.RUnlock() - if val == "" { - if len(defvals) == 0 { - return time.Duration(0) - } - - return defvals[0] - } - - return time.Duration(c.GetI64(name)) * time.Duration(mod) + return value.ParseDuration(val, time.Duration(mod), defvals...) } // Is checks if given property contains given value diff --git a/knf/value/value.go b/knf/value/value.go new file mode 100644 index 00000000..1b8ed356 --- /dev/null +++ b/knf/value/value.go @@ -0,0 +1,178 @@ +package value + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "strconv" + "strings" + "time" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +// ParseInt64 parses value as Int64 +func ParseInt64(v string, defvals ...int64) int64 { + if v == "" { + if len(defvals) == 0 { + return 0 + } + + return defvals[0] + } + + // HEX Parsing + if len(v) >= 3 && v[0:2] == "0x" { + vHex, err := strconv.ParseInt(v[2:], 16, 0) + + if err != nil { + return 0 + } + + return vHex + } + + vInt, err := strconv.ParseInt(v, 10, 0) + + if err != nil { + return 0 + } + + return vInt +} + +// ParseInt parses value as Int +func ParseInt(v string, defvals ...int) int { + if len(defvals) != 0 { + return int(ParseInt64(v, int64(defvals[0]))) + } + + return int(ParseInt64(v)) +} + +// ParseUint parses value as Uint +func ParseUint(v string, defvals ...uint) uint { + if len(defvals) != 0 { + return uint(ParseInt64(v, int64(defvals[0]))) + } + + return uint(ParseInt64(v)) +} + +// ParseUint64 parses value as Uint64 +func ParseUint64(v string, defvals ...uint64) uint64 { + if len(defvals) != 0 { + return uint64(ParseInt64(v, int64(defvals[0]))) + } + + return uint64(ParseInt64(v)) +} + +// ParseFloat parses value as float +func ParseFloat(v string, defvals ...float64) float64 { + if v == "" { + if len(defvals) == 0 { + return 0.0 + } + + return defvals[0] + } + + vF, err := strconv.ParseFloat(v, 64) + + if err != nil { + return 0.0 + } + + return vF +} + +// ParseFloat parses value as boolean +func ParseBool(v string, defvals ...bool) bool { + if v == "" { + if len(defvals) == 0 { + return false + } + + return defvals[0] + } + + switch strings.ToLower(v) { + case "", "0", "false", "no": + return false + default: + return true + } +} + +// ParseMode parses value as file mode +func ParseMode(v string, defvals ...os.FileMode) os.FileMode { + if v == "" { + if len(defvals) == 0 { + return os.FileMode(0) + } + + return defvals[0] + } + + vM, err := strconv.ParseUint(v, 8, 32) + + if err != nil { + return 0 + } + + return os.FileMode(vM) +} + +// ParseDuration parses value as duration +func ParseDuration(v string, mod time.Duration, defvals ...time.Duration) time.Duration { + if v == "" { + if len(defvals) == 0 { + return time.Duration(0) + } + + return defvals[0] + } + + return time.Duration(ParseInt64(v)) * mod +} + +// ParseTime parses value as time duration +func ParseTime(v string, defvals ...time.Duration) time.Duration { + if v == "" { + if len(defvals) == 0 { + return time.Duration(0) + } + + return defvals[0] + } + + v = strings.ToLower(v) + m := 1 + + switch v[len(v)-1:] { + case "s": + v, m = v[:len(v)-1], 1 + case "m": + v, m = v[:len(v)-1], 60 + case "h": + v, m = v[:len(v)-1], 3600 + case "d": + v, m = v[:len(v)-1], 24*3600 + case "w": + v, m = v[:len(v)-1], 7*24*3600 + } + + i, err := strconv.Atoi(v) + + if err != nil { + return time.Duration(0) + } + + return time.Duration(i*m) * time.Second +} diff --git a/knf/value/value_test.go b/knf/value/value_test.go new file mode 100644 index 00000000..64a3e392 --- /dev/null +++ b/knf/value/value_test.go @@ -0,0 +1,125 @@ +package value + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "testing" + "time" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +type ValuesSuite struct{} + +// ////////////////////////////////////////////////////////////////////////////////// // + +var _ = Suite(&ValuesSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *ValuesSuite) TestParseInt64(c *C) { + c.Assert(ParseInt64(""), Equals, int64(0)) + c.Assert(ParseInt64("", 234), Equals, int64(234)) + + c.Assert(ParseInt64("123"), Equals, int64(123)) + c.Assert(ParseInt64("0xFF"), Equals, int64(255)) + + c.Assert(ParseInt64("ABCD"), Equals, int64(0)) + c.Assert(ParseInt64("0xZZ"), Equals, int64(0)) +} + +func (s *ValuesSuite) TestParseInt(c *C) { + c.Assert(ParseInt(""), Equals, 0) + c.Assert(ParseInt("", 234), Equals, 234) + + c.Assert(ParseInt("123"), Equals, 123) + c.Assert(ParseInt("0xFF"), Equals, 255) + + c.Assert(ParseInt("ABCD"), Equals, 0) + c.Assert(ParseInt("0xZZ"), Equals, 0) +} + +func (s *ValuesSuite) TestParseUint(c *C) { + c.Assert(ParseUint(""), Equals, uint(0)) + c.Assert(ParseUint("", 234), Equals, uint(234)) + + c.Assert(ParseUint("123"), Equals, uint(123)) + c.Assert(ParseUint("0xFF"), Equals, uint(255)) + + c.Assert(ParseUint("ABCD"), Equals, uint(0)) + c.Assert(ParseUint("0xZZ"), Equals, uint(0)) +} + +func (s *ValuesSuite) TestParseUint64(c *C) { + c.Assert(ParseUint64(""), Equals, uint64(0)) + c.Assert(ParseUint64("", 234), Equals, uint64(234)) + + c.Assert(ParseUint64("123"), Equals, uint64(123)) + c.Assert(ParseUint64("0xFF"), Equals, uint64(255)) + + c.Assert(ParseUint64("ABCD"), Equals, uint64(0)) + c.Assert(ParseUint64("0xZZ"), Equals, uint64(0)) +} + +func (s *ValuesSuite) TestParseFloat(c *C) { + c.Assert(ParseFloat(""), Equals, 0.0) + c.Assert(ParseFloat("", 234.0), Equals, 234.0) + + c.Assert(ParseFloat("123"), Equals, 123.0) + + c.Assert(ParseFloat("ABCD"), Equals, 0.0) +} + +func (s *ValuesSuite) TestParseBool(c *C) { + c.Assert(ParseBool(""), Equals, false) + c.Assert(ParseBool("", true), Equals, true) + + c.Assert(ParseBool("0"), Equals, false) + c.Assert(ParseBool("No"), Equals, false) + c.Assert(ParseBool("False"), Equals, false) + + c.Assert(ParseBool("true"), Equals, true) + c.Assert(ParseBool("abcd"), Equals, true) +} + +func (s *ValuesSuite) TestParseMode(c *C) { + c.Assert(ParseMode(""), Equals, os.FileMode(0)) + c.Assert(ParseMode("", 0600), Equals, os.FileMode(0600)) + + c.Assert(ParseMode("0600"), Equals, os.FileMode(0600)) + c.Assert(ParseMode("600"), Equals, os.FileMode(0600)) + + c.Assert(ParseMode("ABCD"), Equals, os.FileMode(0)) +} + +func (s *ValuesSuite) TestParseDuration(c *C) { + c.Assert(ParseDuration("", time.Minute), Equals, time.Duration(0)) + c.Assert(ParseDuration("", time.Minute, time.Hour), Equals, time.Hour) + + c.Assert(ParseDuration("3", time.Minute, time.Hour), Equals, 3*time.Minute) + + c.Assert(ParseDuration("ABCD", time.Minute), Equals, time.Duration(0)) +} + +func (s *ValuesSuite) TestParseTime(c *C) { + c.Assert(ParseTime(""), Equals, time.Duration(0)) + c.Assert(ParseTime("", time.Hour), Equals, time.Hour) + + c.Assert(ParseTime("7s"), Equals, 7*time.Second) + c.Assert(ParseTime("6m"), Equals, 6*time.Minute) + c.Assert(ParseTime("3h"), Equals, 3*time.Hour) + c.Assert(ParseTime("2d"), Equals, 48*time.Hour) + c.Assert(ParseTime("3w"), Equals, 3*7*24*time.Hour) + + c.Assert(ParseTime("ABCD"), Equals, time.Duration(0)) +} From 95e483dae6d7b7145fa6ea245e9b7aa5df1fe6d4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Mar 2024 03:16:58 +0000 Subject: [PATCH 04/11] Bump golang.org/x/crypto from 0.20.0 to 0.21.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.20.0 to 0.21.0. - [Commits](https://github.com/golang/crypto/compare/v0.20.0...v0.21.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 95f7a2d5..64299642 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.18 require ( github.com/essentialkaos/check v1.4.0 github.com/essentialkaos/go-linenoise/v3 v3.4.0 - golang.org/x/crypto v0.20.0 - golang.org/x/sys v0.17.0 + golang.org/x/crypto v0.21.0 + golang.org/x/sys v0.18.0 ) require ( diff --git a/go.sum b/go.sum index 0525f9c8..6680cd0d 100644 --- a/go.sum +++ b/go.sum @@ -11,7 +11,7 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 95c644086728c8ea10397511b0204b0de02e4c05 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 5 Mar 2024 11:47:56 +0300 Subject: [PATCH 05/11] [knf/value] Add more parsers --- knf/value/value.go | 73 ++++++++++++++++++++++++++++++++++------- knf/value/value_test.go | 27 +++++++++++++++ 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/knf/value/value.go b/knf/value/value.go index 1b8ed356..d1e5116e 100644 --- a/knf/value/value.go +++ b/knf/value/value.go @@ -12,11 +12,13 @@ import ( "strconv" "strings" "time" + + "github.com/essentialkaos/ek/v12/strutil" ) // ////////////////////////////////////////////////////////////////////////////////// // -// ParseInt64 parses value as Int64 +// ParseInt64 parses value as 64-bit int func ParseInt64(v string, defvals ...int64) int64 { if v == "" { if len(defvals) == 0 { @@ -28,25 +30,25 @@ func ParseInt64(v string, defvals ...int64) int64 { // HEX Parsing if len(v) >= 3 && v[0:2] == "0x" { - vHex, err := strconv.ParseInt(v[2:], 16, 0) + h, err := strconv.ParseInt(v[2:], 16, 0) if err != nil { return 0 } - return vHex + return h } - vInt, err := strconv.ParseInt(v, 10, 0) + i, err := strconv.ParseInt(v, 10, 64) if err != nil { return 0 } - return vInt + return i } -// ParseInt parses value as Int +// ParseInt parses value as int func ParseInt(v string, defvals ...int) int { if len(defvals) != 0 { return int(ParseInt64(v, int64(defvals[0]))) @@ -55,7 +57,7 @@ func ParseInt(v string, defvals ...int) int { return int(ParseInt64(v)) } -// ParseUint parses value as Uint +// ParseUint parses value as uint func ParseUint(v string, defvals ...uint) uint { if len(defvals) != 0 { return uint(ParseInt64(v, int64(defvals[0]))) @@ -64,7 +66,7 @@ func ParseUint(v string, defvals ...uint) uint { return uint(ParseInt64(v)) } -// ParseUint64 parses value as Uint64 +// ParseUint64 parses value as 64-bit uint func ParseUint64(v string, defvals ...uint64) uint64 { if len(defvals) != 0 { return uint64(ParseInt64(v, int64(defvals[0]))) @@ -83,13 +85,13 @@ func ParseFloat(v string, defvals ...float64) float64 { return defvals[0] } - vF, err := strconv.ParseFloat(v, 64) + f, err := strconv.ParseFloat(v, 64) if err != nil { return 0.0 } - return vF + return f } // ParseFloat parses value as boolean @@ -120,13 +122,13 @@ func ParseMode(v string, defvals ...os.FileMode) os.FileMode { return defvals[0] } - vM, err := strconv.ParseUint(v, 8, 32) + m, err := strconv.ParseUint(v, 8, 32) if err != nil { return 0 } - return os.FileMode(vM) + return os.FileMode(m) } // ParseDuration parses value as duration @@ -176,3 +178,50 @@ func ParseTime(v string, defvals ...time.Duration) time.Duration { return time.Duration(i*m) * time.Second } + +// ParseTimestamp parses value as Unix timestamp +func ParseTimestamp(v string, defvals ...time.Time) time.Time { + if v == "" { + if len(defvals) == 0 { + return time.Time{} + } + + return defvals[0] + } + + i, err := strconv.ParseInt(v, 10, 64) + + if err != nil { + return time.Time{} + } + + return time.Unix(i, 0) +} + +// ParseTimezone parses value as timezone +func ParseTimezone(v string, defvals ...*time.Location) *time.Location { + if v == "" { + if len(defvals) == 0 { + return nil + } + + return defvals[0] + } + + l, _ := time.LoadLocation(v) + + return l +} + +// ParseList parses value as list +func ParseList(v string, defvals ...[]string) []string { + if v == "" { + if len(defvals) == 0 { + return nil + } + + return defvals[0] + } + + return strutil.Fields(v) +} diff --git a/knf/value/value_test.go b/knf/value/value_test.go index 64a3e392..06930876 100644 --- a/knf/value/value_test.go +++ b/knf/value/value_test.go @@ -123,3 +123,30 @@ func (s *ValuesSuite) TestParseTime(c *C) { c.Assert(ParseTime("ABCD"), Equals, time.Duration(0)) } + +func (s *ValuesSuite) TestParseTimestamp(c *C) { + c.Assert(ParseTimestamp("").IsZero(), Equals, true) + c.Assert(ParseTimestamp("", time.Unix(1709627257, 0)).Unix(), Equals, int64(1709627257)) + + c.Assert(ParseTimestamp("1709627257").Unix(), Equals, int64(1709627257)) + + c.Assert(ParseTimestamp("ABCD").IsZero(), Equals, true) +} + +func (s *ValuesSuite) TestParseTimezone(c *C) { + l, _ := time.LoadLocation("America/Los_Angeles") + + c.Assert(ParseTimezone(""), IsNil) + c.Assert(ParseTimezone("", l), NotNil) + + c.Assert(ParseTimezone("Europe/Vienna"), NotNil) + + c.Assert(ParseTimezone("ABCD"), IsNil) +} + +func (s *ValuesSuite) TestParseList(c *C) { + c.Assert(ParseList(""), IsNil) + c.Assert(ParseList("", []string{"A", "B"}), DeepEquals, []string{"A", "B"}) + + c.Assert(ParseList("A, B"), DeepEquals, []string{"A", "B"}) +} From 83a9fd5c835d54b3e50af6243648981bd5e1b53b Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 5 Mar 2024 13:27:33 +0300 Subject: [PATCH 06/11] [knf] Add more getters --- CHANGELOG.md | 4 + knf/example_test.go | 228 +++++++++++++++++++++++++++++++++++++++++--- knf/knf.go | 120 +++++++++++++++++++++++ knf/knf_test.go | 65 ++++++++++++- 4 files changed, 401 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7ad3a69..7a27a0b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ### 12.103.0 +* `[knf]` Added method `GetTD` +* `[knf]` Added method `GetTS` +* `[knf]` Added method `GetTZ` +* `[knf]` Added method `GetL` * `[options]` Fixed panic when parsing unsupported option with value passed with equal sign (`=`) * `[options]` Code refactoring * `[knf]` Code refactoring diff --git a/knf/example_test.go b/knf/example_test.go index 76b78e85..33ca319a 100644 --- a/knf/example_test.go +++ b/knf/example_test.go @@ -43,6 +43,18 @@ func ExampleGlobal() { // Read duration as minutes GetD("section:duration", Minute) + // Read time duration + GetTD("section:time-duration") + + // Read timestamp + GetTS("section:timestamp") + + // Read timezone + GetTZ("section:timezone") + + // Read list + GetL("section:list") + // Check section if HasSection("section") { // Section exist @@ -207,6 +219,50 @@ func ExampleGetD() { fmt.Printf("Value from config: %v\n", GetD("user:timeout", Minute)) } +func ExampleGetTD() { + err := Global("/path/to/your/config.knf") + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Value from config: %v\n", GetTD("user:timeout")) +} + +func ExampleGetTS() { + err := Global("/path/to/your/config.knf") + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Value from config: %v\n", GetTS("user:created")) +} + +func ExampleGetTZ() { + err := Global("/path/to/your/config.knf") + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Value from config: %v\n", GetTZ("service:timezone")) +} + +func ExampleGetL() { + err := Global("/path/to/your/config.knf") + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Value from config: %v\n", GetL("issue:labels")) +} + func ExampleIs() { err := Global("/path/to/your/config.knf") @@ -325,7 +381,10 @@ func ExampleConfig_GetS() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -347,7 +406,10 @@ func ExampleConfig_GetB() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -369,7 +431,10 @@ func ExampleConfig_GetI() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -391,7 +456,10 @@ func ExampleConfig_GetI64() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -413,7 +481,10 @@ func ExampleConfig_GetU() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -435,7 +506,10 @@ func ExampleConfig_GetU64() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -457,7 +531,10 @@ func ExampleConfig_GetF() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -479,7 +556,10 @@ func ExampleConfig_GetM() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -501,7 +581,10 @@ func ExampleConfig_GetD() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3 + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -512,7 +595,104 @@ func ExampleConfig_GetD() { fmt.Printf("Value from config: %v\n", cfg.GetD("user:timeout", Minute)) // Output: - // Value from config: 5m0s + // Value from config: 3m0s +} + +func ExampleConfig_GetTD() { + cfg, err := Parse([]byte(` +[user] + name: john + uid: 512 + is-admin: true + priority: 3.7 + default-mode: 0644 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin +`)) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Value from config: %v\n", cfg.GetTD("user:timeout")) + + // Output: + // Value from config: 3m0s +} + +func ExampleConfig_GetTS() { + cfg, err := Parse([]byte(` +[user] + name: john + uid: 512 + is-admin: true + priority: 3.7 + default-mode: 0644 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin +`)) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Value from config: %v\n", cfg.GetTS("user:created")) +} + +func ExampleConfig_GetTZ() { + cfg, err := Parse([]byte(` +[user] + name: john + uid: 512 + is-admin: true + priority: 3.7 + default-mode: 0644 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin +`)) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Value from config: %s\n", cfg.GetTZ("user:timezone")) + + // Output: + // Value from config: Europe/Madrid +} + +func ExampleConfig_GetL() { + cfg, err := Parse([]byte(` +[user] + name: john + uid: 512 + is-admin: true + priority: 3.7 + default-mode: 0644 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin +`)) + + if err != nil { + fmt.Printf("Error: %v\n", err) + return + } + + fmt.Printf("Value from config: %s\n", cfg.GetL("user:labels")) + + // Output: + // Value from config: [system admin] } func ExampleConfig_Is() { @@ -523,7 +703,10 @@ func ExampleConfig_Is() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -547,7 +730,10 @@ func ExampleConfig_HasSection() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -571,7 +757,10 @@ func ExampleConfig_HasProp() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -595,7 +784,10 @@ func ExampleConfig_Sections() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin [log] file: /var/log/app/app.log @@ -623,7 +815,10 @@ func ExampleConfig_Props() { is-admin: true priority: 3.7 default-mode: 0644 - timeout: 5 + timeout: 3m + created: 1654424130 + timezone: Europe/Madrid + labels: system admin `)) if err != nil { @@ -642,6 +837,9 @@ func ExampleConfig_Props() { // 4: priority // 5: default-mode // 6: timeout + // 7: created + // 8: timezone + // 9: labels } func ExampleConfig_File() { diff --git a/knf/knf.go b/knf/knf.go index a0b840f1..735986a7 100644 --- a/knf/knf.go +++ b/knf/knf.go @@ -237,6 +237,58 @@ func GetD(name string, mod DurationMod, defvals ...time.Duration) time.Duration return global.GetD(name, mod, defvals...) } +// GetTD returns configuration value as time duration +func GetTD(name string, defvals ...time.Duration) time.Duration { + if global == nil { + if len(defvals) == 0 { + return time.Duration(0) + } + + return defvals[0] + } + + return global.GetTD(name, defvals...) +} + +// GetTS returns configuration timestamp value as time +func GetTS(name string, defvals ...time.Time) time.Time { + if global == nil { + if len(defvals) == 0 { + return time.Time{} + } + + return defvals[0] + } + + return global.GetTS(name, defvals...) +} + +// GetTS returns configuration value as timezone +func GetTZ(name string, defvals ...*time.Location) *time.Location { + if global == nil { + if len(defvals) == 0 { + return nil + } + + return defvals[0] + } + + return global.GetTZ(name, defvals...) +} + +// GetL returns configuration value as list +func GetL(name string, defvals ...[]string) []string { + if global == nil { + if len(defvals) == 0 { + return nil + } + + return defvals[0] + } + + return global.GetL(name, defvals...) +} + // Is checks if given property contains given value func Is(name string, value any) bool { if global == nil { @@ -504,6 +556,74 @@ func (c *Config) GetD(name string, mod DurationMod, defvals ...time.Duration) ti return value.ParseDuration(val, time.Duration(mod), defvals...) } +// GetTD returns configuration value as time duration +func (c *Config) GetTD(name string, defvals ...time.Duration) time.Duration { + if c == nil || c.mx == nil { + if len(defvals) == 0 { + return time.Duration(0) + } + + return defvals[0] + } + + c.mx.RLock() + val := c.data[strings.ToLower(name)] + c.mx.RUnlock() + + return value.ParseTime(val, defvals...) +} + +// GetTS returns configuration timestamp value as time +func (c *Config) GetTS(name string, defvals ...time.Time) time.Time { + if c == nil || c.mx == nil { + if len(defvals) == 0 { + return time.Time{} + } + + return defvals[0] + } + + c.mx.RLock() + val := c.data[strings.ToLower(name)] + c.mx.RUnlock() + + return value.ParseTimestamp(val, defvals...) +} + +// GetTS returns configuration value as timezone +func (c *Config) GetTZ(name string, defvals ...*time.Location) *time.Location { + if c == nil || c.mx == nil { + if len(defvals) == 0 { + return nil + } + + return defvals[0] + } + + c.mx.RLock() + val := c.data[strings.ToLower(name)] + c.mx.RUnlock() + + return value.ParseTimezone(val, defvals...) +} + +// GetL returns configuration value as list +func (c *Config) GetL(name string, defvals ...[]string) []string { + if c == nil || c.mx == nil { + if len(defvals) == 0 { + return nil + } + + return defvals[0] + } + + c.mx.RLock() + val := c.data[strings.ToLower(name)] + c.mx.RUnlock() + + return value.ParseList(val, defvals...) +} + // Is checks if given property contains given value func (c *Config) Is(name string, value any) bool { if c == nil || c.mx == nil { diff --git a/knf/knf_test.go b/knf/knf_test.go index a6e0d177..3a7b9979 100644 --- a/knf/knf_test.go +++ b/knf/knf_test.go @@ -69,6 +69,26 @@ test1: 1 test3: ABC test4: true +[time-duration] + test1: 0 + test2: 15 + test3: 45s + test4: 12M + test5: ABCD + +[timestamp] + test1: 0 + test2: 1709629048 + test3: ABCD + +[timezone] + test1: Europe/Zurich + test2: ABCD + +[list] + test1: + test2: Test1, Test2 + [comment] test1: 100 # test2: 100 @@ -196,6 +216,10 @@ func (s *KNFSuite) TestErrors(c *check.C) { c.Assert(GetB("test"), check.Equals, false) c.Assert(GetM("test"), check.Equals, os.FileMode(0)) c.Assert(GetD("test", Second), check.Equals, time.Duration(0)) + c.Assert(GetTD("test"), check.Equals, time.Duration(0)) + c.Assert(GetTS("test").IsZero(), check.Equals, true) + c.Assert(GetTZ("test"), check.IsNil) + c.Assert(GetL("test"), check.IsNil) c.Assert(Is("test", ""), check.Equals, false) c.Assert(HasSection("test"), check.Equals, false) c.Assert(HasProp("test"), check.Equals, false) @@ -212,6 +236,10 @@ func (s *KNFSuite) TestErrors(c *check.C) { c.Assert(config.GetB("test"), check.Equals, false) c.Assert(config.GetM("test"), check.Equals, os.FileMode(0)) c.Assert(config.GetD("test", Second), check.Equals, time.Duration(0)) + c.Assert(config.GetTD("test"), check.Equals, time.Duration(0)) + c.Assert(config.GetTS("test").IsZero(), check.Equals, true) + c.Assert(config.GetTZ("test"), check.IsNil) + c.Assert(config.GetL("test"), check.IsNil) c.Assert(config.Is("test", ""), check.Equals, true) c.Assert(config.HasSection("test"), check.Equals, false) c.Assert(config.HasProp("test"), check.Equals, false) @@ -289,7 +317,7 @@ func (s *KNFSuite) TestSections(c *check.C) { sections := Sections() - c.Assert(sections, check.HasLen, 9) + c.Assert(sections, check.HasLen, 13) c.Assert( sections, check.DeepEquals, @@ -300,6 +328,10 @@ func (s *KNFSuite) TestSections(c *check.C) { "integer", "file-mode", "duration", + "time-duration", + "timestamp", + "timezone", + "list", "comment", "macro", "k", @@ -423,6 +455,19 @@ func (s *KNFSuite) TestDuration(c *check.C) { c.Assert(GetD("duration:test4", Second), check.Equals, time.Duration(0)) } +func (s *KNFSuite) TestTimeDuration(c *check.C) { + err := Global(s.ConfigPath) + + c.Assert(global, check.NotNil) + c.Assert(err, check.IsNil) + + c.Assert(GetTD("time-duration:test1"), check.Equals, time.Duration(0)) + c.Assert(GetTD("time-duration:test2"), check.Equals, 15*time.Second) + c.Assert(GetTD("time-duration:test3"), check.Equals, 45*time.Second) + c.Assert(GetTD("time-duration:test4"), check.Equals, 12*time.Minute) + c.Assert(GetTD("time-duration:test5"), check.Equals, time.Duration(0)) +} + func (s *KNFSuite) TestIs(c *check.C) { err := Global(s.ConfigPath) @@ -478,6 +523,10 @@ func (s *KNFSuite) TestNil(c *check.C) { c.Assert(nilConf.GetB("formatting:test1"), check.Equals, false) c.Assert(nilConf.GetM("formatting:test1"), check.Equals, os.FileMode(0)) c.Assert(nilConf.GetD("formatting:test1", Second), check.Equals, time.Duration(0)) + c.Assert(nilConf.GetTD("formatting:test1"), check.Equals, time.Duration(0)) + c.Assert(nilConf.GetTS("formatting:test1").IsZero(), check.Equals, true) + c.Assert(nilConf.GetTZ("formatting:test1"), check.IsNil) + c.Assert(nilConf.GetL("formatting:test1"), check.IsNil) c.Assert(nilConf.Is("formatting:test1", ""), check.Equals, false) c.Assert(nilConf.HasSection("formatting"), check.Equals, false) c.Assert(nilConf.HasProp("formatting:test1"), check.Equals, false) @@ -499,6 +548,8 @@ func (s *KNFSuite) TestNil(c *check.C) { func (s *KNFSuite) TestDefault(c *check.C) { global = nil + l, _ := time.LoadLocation("Asia/Yerevan") + c.Assert(GetS("string:test100", "fail"), check.Equals, "fail") c.Assert(GetB("boolean:test100", true), check.Equals, true) c.Assert(GetI("integer:test100", 9999), check.Equals, 9999) @@ -508,6 +559,10 @@ func (s *KNFSuite) TestDefault(c *check.C) { c.Assert(GetF("integer:test100", 123.45), check.Equals, 123.45) c.Assert(GetM("file-mode:test100", 0755), check.Equals, os.FileMode(0755)) c.Assert(GetD("duration:test100", Second, time.Minute), check.Equals, time.Minute) + c.Assert(GetTD("duration:test100", time.Minute), check.Equals, time.Minute) + c.Assert(GetTS("duration:test100", time.Now()).IsZero(), check.Equals, false) + c.Assert(GetTZ("duration:test100", l), check.Equals, l) + c.Assert(GetL("duration:test100", []string{"A", "B"}), check.DeepEquals, []string{"A", "B"}) c.Assert(GetS("string:test6", "fail"), check.Equals, "fail") err := Global(s.ConfigPath) @@ -524,6 +579,10 @@ func (s *KNFSuite) TestDefault(c *check.C) { c.Assert(GetF("integer:test100", 123.45), check.Equals, 123.45) c.Assert(GetM("file-mode:test100", 0755), check.Equals, os.FileMode(0755)) c.Assert(GetD("duration:test100", Second, time.Minute), check.Equals, time.Minute) + c.Assert(GetTD("duration:test100", time.Minute), check.Equals, time.Minute) + c.Assert(GetTS("duration:test100", time.Now()).IsZero(), check.Equals, false) + c.Assert(GetTZ("duration:test100", l), check.Equals, l) + c.Assert(GetL("duration:test100", []string{"A", "B"}), check.DeepEquals, []string{"A", "B"}) c.Assert(GetS("string:test6", "fail"), check.Equals, "fail") var nc *Config @@ -537,6 +596,10 @@ func (s *KNFSuite) TestDefault(c *check.C) { c.Assert(nc.GetF("integer:test100", 123.45), check.Equals, 123.45) c.Assert(nc.GetM("file-mode:test100", 0755), check.Equals, os.FileMode(0755)) c.Assert(nc.GetD("duration:test100", Second, time.Minute), check.Equals, time.Minute) + c.Assert(nc.GetTD("duration:test100", time.Minute), check.Equals, time.Minute) + c.Assert(nc.GetTS("duration:test100", time.Now()).IsZero(), check.Equals, false) + c.Assert(nc.GetTZ("duration:test100", l), check.Equals, l) + c.Assert(nc.GetL("duration:test100", []string{"A", "B"}), check.DeepEquals, []string{"A", "B"}) c.Assert(nc.GetS("string:test6", "fail"), check.Equals, "fail") } From 9d107096387fca57959ca2e415c72be14a92d346 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 5 Mar 2024 13:52:45 +0300 Subject: [PATCH 07/11] [knf/value] Code refactoring --- knf/value/value.go | 72 +++++++++++++++++++++++++++++++++-------- knf/value/value_test.go | 8 +++++ 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/knf/value/value.go b/knf/value/value.go index d1e5116e..7f624e89 100644 --- a/knf/value/value.go +++ b/knf/value/value.go @@ -8,6 +8,7 @@ package value // ////////////////////////////////////////////////////////////////////////////////// // import ( + "math" "os" "strconv" "strings" @@ -18,6 +19,10 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // +var maxCheckFail = false + +// ////////////////////////////////////////////////////////////////////////////////// // + // ParseInt64 parses value as 64-bit int func ParseInt64(v string, defvals ...int64) int64 { if v == "" { @@ -28,8 +33,7 @@ func ParseInt64(v string, defvals ...int64) int64 { return defvals[0] } - // HEX Parsing - if len(v) >= 3 && v[0:2] == "0x" { + if isHex(v) { h, err := strconv.ParseInt(v[2:], 16, 0) if err != nil { @@ -50,29 +54,65 @@ func ParseInt64(v string, defvals ...int64) int64 { // ParseInt parses value as int func ParseInt(v string, defvals ...int) int { + var i int64 + if len(defvals) != 0 { - return int(ParseInt64(v, int64(defvals[0]))) + i = ParseInt64(v, int64(defvals[0])) + } else { + i = ParseInt64(v) } - return int(ParseInt64(v)) -} - -// ParseUint parses value as uint -func ParseUint(v string, defvals ...uint) uint { - if len(defvals) != 0 { - return uint(ParseInt64(v, int64(defvals[0]))) + if maxCheckFail || i > math.MaxInt { + return 0 } - return uint(ParseInt64(v)) + return int(i) } // ParseUint64 parses value as 64-bit uint func ParseUint64(v string, defvals ...uint64) uint64 { + if v == "" { + if len(defvals) == 0 { + return 0 + } + + return defvals[0] + } + + if isHex(v) { + h, err := strconv.ParseUint(v[2:], 16, 0) + + if err != nil { + return 0 + } + + return h + } + + i, err := strconv.ParseUint(v, 10, 64) + + if err != nil { + return 0 + } + + return i +} + +// ParseUint parses value as uint +func ParseUint(v string, defvals ...uint) uint { + var u uint64 + if len(defvals) != 0 { - return uint64(ParseInt64(v, int64(defvals[0]))) + u = ParseUint64(v, uint64(defvals[0])) + } else { + u = ParseUint64(v) } - return uint64(ParseInt64(v)) + if maxCheckFail || u > math.MaxUint { + return 0 + } + + return uint(u) } // ParseFloat parses value as float @@ -225,3 +265,9 @@ func ParseList(v string, defvals ...[]string) []string { return strutil.Fields(v) } + +// ////////////////////////////////////////////////////////////////////////////////// // + +func isHex(v string) bool { + return len(v) >= 3 && v[0:2] == "0x" +} diff --git a/knf/value/value_test.go b/knf/value/value_test.go index 06930876..fdc8651e 100644 --- a/knf/value/value_test.go +++ b/knf/value/value_test.go @@ -47,6 +47,10 @@ func (s *ValuesSuite) TestParseInt(c *C) { c.Assert(ParseInt("ABCD"), Equals, 0) c.Assert(ParseInt("0xZZ"), Equals, 0) + + maxCheckFail = true + c.Assert(ParseInt("9999"), Equals, 0) + maxCheckFail = false } func (s *ValuesSuite) TestParseUint(c *C) { @@ -58,6 +62,10 @@ func (s *ValuesSuite) TestParseUint(c *C) { c.Assert(ParseUint("ABCD"), Equals, uint(0)) c.Assert(ParseUint("0xZZ"), Equals, uint(0)) + + maxCheckFail = true + c.Assert(ParseUint("9999"), Equals, uint(0)) + maxCheckFail = false } func (s *ValuesSuite) TestParseUint64(c *C) { From 8c7846f4c581abf7192f249574645d88f4ec5a0f Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Tue, 5 Mar 2024 14:00:57 +0300 Subject: [PATCH 08/11] Improve CI workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ba43cfa..5f078e65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -113,7 +113,7 @@ jobs: run: .scripts/nix.sh 12 - name: Send coverage data - uses: essentialkaos/goveralls-action@v1 + uses: essentialkaos/goveralls-action@v2 env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From adac3f9541148b5faec7d4066f1428e7a5e6735d Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Wed, 6 Mar 2024 00:09:34 +0300 Subject: [PATCH 09/11] [knf] Code refactoring --- knf/knf.go | 52 ++++++++++++++++++++++++++++++++++++------------- knf/knf_test.go | 3 +++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/knf/knf.go b/knf/knf.go index 735986a7..36f90c66 100644 --- a/knf/knf.go +++ b/knf/knf.go @@ -444,8 +444,8 @@ func (c *Config) GetS(name string, defvals ...string) string { return val } -// GetI64 returns configuration value as int64 -func (c *Config) GetI64(name string, defvals ...int64) int64 { +// GetI returns configuration value as int +func (c *Config) GetI(name string, defvals ...int) int { if c == nil || c.mx == nil { if len(defvals) == 0 { return 0 @@ -458,34 +458,58 @@ func (c *Config) GetI64(name string, defvals ...int64) int64 { val := c.data[strings.ToLower(name)] c.mx.RUnlock() - return value.ParseInt64(val, defvals...) + return value.ParseInt(val, defvals...) } -// GetI returns configuration value as int -func (c *Config) GetI(name string, defvals ...int) int { - if len(defvals) != 0 { - return int(c.GetI64(name, int64(defvals[0]))) +// GetI64 returns configuration value as int64 +func (c *Config) GetI64(name string, defvals ...int64) int64 { + if c == nil || c.mx == nil { + if len(defvals) == 0 { + return 0 + } + + return defvals[0] } - return int(c.GetI64(name)) + c.mx.RLock() + val := c.data[strings.ToLower(name)] + c.mx.RUnlock() + + return value.ParseInt64(val, defvals...) } // GetU returns configuration value as uint func (c *Config) GetU(name string, defvals ...uint) uint { - if len(defvals) != 0 { - return uint(c.GetI64(name, int64(defvals[0]))) + if c == nil || c.mx == nil { + if len(defvals) == 0 { + return 0 + } + + return defvals[0] } - return uint(c.GetI64(name)) + c.mx.RLock() + val := c.data[strings.ToLower(name)] + c.mx.RUnlock() + + return value.ParseUint(val, defvals...) } // GetU64 returns configuration value as uint64 func (c *Config) GetU64(name string, defvals ...uint64) uint64 { - if len(defvals) != 0 { - return uint64(c.GetI64(name, int64(defvals[0]))) + if c == nil || c.mx == nil { + if len(defvals) == 0 { + return 0 + } + + return defvals[0] } - return uint64(c.GetI64(name)) + c.mx.RLock() + val := c.data[strings.ToLower(name)] + c.mx.RUnlock() + + return value.ParseUint64(val, defvals...) } // GetF returns configuration value as floating number diff --git a/knf/knf_test.go b/knf/knf_test.go index 3a7b9979..5894c464 100644 --- a/knf/knf_test.go +++ b/knf/knf_test.go @@ -519,6 +519,9 @@ func (s *KNFSuite) TestNil(c *check.C) { c.Assert(nilConf.GetS("formatting:test1"), check.Equals, "") c.Assert(nilConf.GetI("formatting:test1"), check.Equals, 0) + c.Assert(nilConf.GetI64("formatting:test1"), check.Equals, int64(0)) + c.Assert(nilConf.GetU("formatting:test1"), check.Equals, uint(0)) + c.Assert(nilConf.GetU64("formatting:test1"), check.Equals, uint64(0)) c.Assert(nilConf.GetF("formatting:test1"), check.Equals, 0.0) c.Assert(nilConf.GetB("formatting:test1"), check.Equals, false) c.Assert(nilConf.GetM("formatting:test1"), check.Equals, os.FileMode(0)) From a000df35200c82bdd4541e2508b3c84957fa296e Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Wed, 6 Mar 2024 00:22:34 +0300 Subject: [PATCH 10/11] [knf] Code refactoring --- knf/knf.go | 2 +- knf/value/value.go | 4 ++-- knf/value/value_test.go | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/knf/knf.go b/knf/knf.go index 36f90c66..2c402662 100644 --- a/knf/knf.go +++ b/knf/knf.go @@ -594,7 +594,7 @@ func (c *Config) GetTD(name string, defvals ...time.Duration) time.Duration { val := c.data[strings.ToLower(name)] c.mx.RUnlock() - return value.ParseTime(val, defvals...) + return value.ParseTimeDuration(val, defvals...) } // GetTS returns configuration timestamp value as time diff --git a/knf/value/value.go b/knf/value/value.go index 7f624e89..7280a415 100644 --- a/knf/value/value.go +++ b/knf/value/value.go @@ -184,8 +184,8 @@ func ParseDuration(v string, mod time.Duration, defvals ...time.Duration) time.D return time.Duration(ParseInt64(v)) * mod } -// ParseTime parses value as time duration -func ParseTime(v string, defvals ...time.Duration) time.Duration { +// ParseTimeDuration parses value as time duration +func ParseTimeDuration(v string, defvals ...time.Duration) time.Duration { if v == "" { if len(defvals) == 0 { return time.Duration(0) diff --git a/knf/value/value_test.go b/knf/value/value_test.go index fdc8651e..e043aa72 100644 --- a/knf/value/value_test.go +++ b/knf/value/value_test.go @@ -119,17 +119,17 @@ func (s *ValuesSuite) TestParseDuration(c *C) { c.Assert(ParseDuration("ABCD", time.Minute), Equals, time.Duration(0)) } -func (s *ValuesSuite) TestParseTime(c *C) { - c.Assert(ParseTime(""), Equals, time.Duration(0)) - c.Assert(ParseTime("", time.Hour), Equals, time.Hour) +func (s *ValuesSuite) TestParseTimeDuration(c *C) { + c.Assert(ParseTimeDuration(""), Equals, time.Duration(0)) + c.Assert(ParseTimeDuration("", time.Hour), Equals, time.Hour) - c.Assert(ParseTime("7s"), Equals, 7*time.Second) - c.Assert(ParseTime("6m"), Equals, 6*time.Minute) - c.Assert(ParseTime("3h"), Equals, 3*time.Hour) - c.Assert(ParseTime("2d"), Equals, 48*time.Hour) - c.Assert(ParseTime("3w"), Equals, 3*7*24*time.Hour) + c.Assert(ParseTimeDuration("7s"), Equals, 7*time.Second) + c.Assert(ParseTimeDuration("6m"), Equals, 6*time.Minute) + c.Assert(ParseTimeDuration("3h"), Equals, 3*time.Hour) + c.Assert(ParseTimeDuration("2d"), Equals, 48*time.Hour) + c.Assert(ParseTimeDuration("3w"), Equals, 3*7*24*time.Hour) - c.Assert(ParseTime("ABCD"), Equals, time.Duration(0)) + c.Assert(ParseTimeDuration("ABCD"), Equals, time.Duration(0)) } func (s *ValuesSuite) TestParseTimestamp(c *C) { From e8b3c8c089fa622d05acab4e48bb190485ac3753 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Wed, 6 Mar 2024 01:48:58 +0300 Subject: [PATCH 11/11] [knf/united] Add new sub-package --- .scripts/packages.list | 1 + README.md | 1 + knf/united/united.go | 213 ++++++++++++++++++++++++++++++++++++ knf/united/united_test.go | 220 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 435 insertions(+) create mode 100644 knf/united/united.go create mode 100644 knf/united/united_test.go diff --git a/.scripts/packages.list b/.scripts/packages.list index acb89ee6..44036c37 100644 --- a/.scripts/packages.list +++ b/.scripts/packages.list @@ -20,6 +20,7 @@ * ! initsystem * + jsonutil * + knf +* + knf/united * + knf/validators * + knf/validators/fs * + knf/validators/network diff --git a/README.md b/README.md index 61227418..d8140b2c 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ go get -u github.com/essentialkaos/ek/v12 * [`initsystem`](https://kaos.sh/g/ek.v12/initsystem) — Package provides methods for working with different init systems (sysv, upstart, systemd) * [`jsonutil`](https://kaos.sh/g/ek.v12/jsonutil) — Package provides methods for working with JSON data * [`knf`](https://kaos.sh/g/ek.v12/knf) — Package provides methods for working with configuration files in [KNF format](https://kaos.sh/knf-spec) +* [`knf/united`](https://kaos.sh/g/ek.v12/knf/united) — Package provides united configuration (_knf + options + environment variables_) * [`log`](https://kaos.sh/g/ek.v12/log) — Package with an improved logger * [`lock`](https://kaos.sh/g/ek.v12/lock) — Package provides methods for working with lock files * [`lscolors`](https://kaos.sh/g/ek.v12/lscolors) — Package provides methods for colorizing file names based on colors from dircolors diff --git a/knf/united/united.go b/knf/united/united.go new file mode 100644 index 00000000..be8a51cb --- /dev/null +++ b/knf/united/united.go @@ -0,0 +1,213 @@ +// Package united provides KNF configuration extended by enviroment variables and options +package united + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "time" + + "github.com/essentialkaos/ek/v12/knf" + "github.com/essentialkaos/ek/v12/knf/value" + "github.com/essentialkaos/ek/v12/options" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Config is extended configuration +type Config struct { + mappings map[string]Mapping +} + +// Mapping contains mapping [knf property] → [option] → [envvar] +type Mapping struct { + Property string // Property from KNF configuration file + Option string // Command-line option + Variable string // Environment variable +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// DurationMod is type for duration modificator +type DurationMod = knf.DurationMod + +// ////////////////////////////////////////////////////////////////////////////////// // + +var global *Config + +var optionHasFunc = options.Has +var optionGetFunc = options.GetS + +// ////////////////////////////////////////////////////////////////////////////////// // + +// Combine applies mappings to combine knf properties, options, and environment +// variables +func Combine(mappings ...Mapping) error { + config := &Config{ + mappings: make(map[string]Mapping), + } + + for _, m := range mappings { + config.mappings[m.Property] = m + } + + global = config + + return nil +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// GetS returns configuration value as string +func GetS(name string, defvals ...string) string { + if global == nil { + return knf.GetS(name, defvals...) + } + + val := global.getProp(name) + + if val == "" && len(defvals) != 0 { + return defvals[0] + } + + return val +} + +// GetI returns configuration value as int +func GetI(name string, defvals ...int) int { + if global == nil { + return knf.GetI(name, defvals...) + } + + return value.ParseInt(global.getProp(name), defvals...) +} + +// GetI64 returns configuration value as int64 +func GetI64(name string, defvals ...int64) int64 { + if global == nil { + return knf.GetI64(name, defvals...) + } + + return value.ParseInt64(global.getProp(name), defvals...) +} + +// GetU returns configuration value as uint +func GetU(name string, defvals ...uint) uint { + if global == nil { + return knf.GetU(name, defvals...) + } + + return value.ParseUint(global.getProp(name), defvals...) +} + +// GetU64 returns configuration value as uint64 +func GetU64(name string, defvals ...uint64) uint64 { + if global == nil { + return knf.GetU64(name, defvals...) + } + + return value.ParseUint64(global.getProp(name), defvals...) +} + +// GetF returns configuration value as floating number +func GetF(name string, defvals ...float64) float64 { + if global == nil { + return knf.GetF(name, defvals...) + } + + return value.ParseFloat(global.getProp(name), defvals...) +} + +// GetB returns configuration value as boolean +func GetB(name string, defvals ...bool) bool { + if global == nil { + return knf.GetB(name, defvals...) + } + + return value.ParseBool(global.getProp(name), defvals...) +} + +// GetM returns configuration value as file mode +func GetM(name string, defvals ...os.FileMode) os.FileMode { + if global == nil { + return knf.GetM(name, defvals...) + } + + return value.ParseMode(global.getProp(name), defvals...) +} + +// GetD returns configuration values as duration +func GetD(name string, mod DurationMod, defvals ...time.Duration) time.Duration { + if global == nil { + return knf.GetD(name, mod, defvals...) + } + + return value.ParseDuration(global.getProp(name), time.Duration(mod), defvals...) +} + +// GetTD returns configuration value as time duration +func GetTD(name string, defvals ...time.Duration) time.Duration { + if global == nil { + return knf.GetTD(name, defvals...) + } + + return value.ParseTimeDuration(global.getProp(name), defvals...) +} + +// GetTS returns configuration timestamp value as time +func GetTS(name string, defvals ...time.Time) time.Time { + if global == nil { + return knf.GetTS(name, defvals...) + } + + return value.ParseTimestamp(global.getProp(name), defvals...) +} + +// GetTS returns configuration value as timezone +func GetTZ(name string, defvals ...*time.Location) *time.Location { + if global == nil { + return knf.GetTZ(name, defvals...) + } + + return value.ParseTimezone(global.getProp(name), defvals...) +} + +// GetL returns configuration value as list +func GetL(name string, defvals ...[]string) []string { + if global == nil { + return knf.GetL(name, defvals...) + } + + return value.ParseList(global.getProp(name), defvals...) +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +// IsEmpty returns true if mapping is empty +func (m Mapping) IsEmpty() bool { + return m.Option == "" && m.Property == "" && m.Variable == "" +} + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (c *Config) getProp(name string) string { + m := c.mappings[name] + + if m.IsEmpty() { + return knf.GetS(name) + } + + switch { + case m.Option != "" && optionHasFunc(m.Option): + return optionGetFunc(m.Option) + case m.Variable != "" && os.Getenv(m.Variable) != "": + return os.Getenv(m.Variable) + default: + return knf.GetS(name) + } +} diff --git a/knf/united/united_test.go b/knf/united/united_test.go new file mode 100644 index 00000000..f5e463e1 --- /dev/null +++ b/knf/united/united_test.go @@ -0,0 +1,220 @@ +package united + +// ////////////////////////////////////////////////////////////////////////////////// // +// // +// Copyright (c) 2024 ESSENTIAL KAOS // +// Apache License, Version 2.0 // +// // +// ////////////////////////////////////////////////////////////////////////////////// // + +import ( + "os" + "testing" + "time" + + "github.com/essentialkaos/ek/v12/knf" + "github.com/essentialkaos/ek/v12/options" + + . "github.com/essentialkaos/check" +) + +// ////////////////////////////////////////////////////////////////////////////////// // + +const _CONFIG_DATA = ` +[test] + string: Test + integer: 123 + float: 234.5 + boolean: true + file-mode: 0644 + duration: 24 + time-duration: 5m + timestamp: 1709629048 + timezone: Europe/Zurich + list: Test1, Test2 +` + +// ////////////////////////////////////////////////////////////////////////////////// // + +func Test(t *testing.T) { TestingT(t) } + +type UnitedSuite struct{} + +// ////////////////////////////////////////////////////////////////////////////////// // + +var _ = Suite(&UnitedSuite{}) + +// ////////////////////////////////////////////////////////////////////////////////// // + +func (s *UnitedSuite) SetUpSuite(c *C) { + configFile := c.MkDir() + "/config.knf" + err := os.WriteFile(configFile, []byte(_CONFIG_DATA), 0644) + + if err != nil { + c.Fatal(err.Error()) + } + + err = knf.Global(configFile) + + if err != nil { + c.Fatal(err.Error()) + } +} + +func (s *UnitedSuite) TestKNFOnly(c *C) { + global = nil + + c.Assert(GetS("test:string"), Equals, "Test") + c.Assert(GetI("test:integer"), Equals, 123) + c.Assert(GetI64("test:integer"), Equals, int64(123)) + c.Assert(GetU("test:integer"), Equals, uint(123)) + c.Assert(GetU64("test:integer"), Equals, uint64(123)) + c.Assert(GetF("test:float"), Equals, 234.5) + c.Assert(GetB("test:boolean"), Equals, true) + c.Assert(GetM("test:file-mode"), Equals, os.FileMode(0644)) + c.Assert(GetD("test:duration", knf.Minute), Equals, 24*time.Minute) + c.Assert(GetTD("test:time-duration"), Equals, 5*time.Minute) + c.Assert(GetTS("test:timestamp").Unix(), Equals, int64(1709629048)) + c.Assert(GetTZ("test:timezone").String(), Equals, "Europe/Zurich") + c.Assert(GetL("test:list"), DeepEquals, []string{"Test1", "Test2"}) + + err := Combine( + Mapping{"test:string", "test-string", "TEST_STRING"}, + Mapping{"test:integer", "test-integer", "TEST_INTEGER"}, + Mapping{"test:float", "test-float", "TEST_FLOAT"}, + Mapping{"test:boolean", "test-boolean", "TEST_BOOLEAN"}, + Mapping{"test:file-mode", "test-file-mode", "TEST_FILE_MODE"}, + Mapping{"test:duration", "test-duration", "TEST_DURATION"}, + Mapping{"test:time-duration", "test-time-duration", "TEST_TIME_DURATION"}, + Mapping{"test:timestamp", "test-timestamp", "TEST_TIMESTAMP"}, + Mapping{"test:timezone", "test-timezone", "TEST_TIMEZONE"}, + Mapping{"test:list", "test-list", "TEST_LIST"}, + ) + + c.Assert(err, IsNil) + + c.Assert(GetS("test:string"), Equals, "Test") + c.Assert(GetI("test:integer"), Equals, 123) + c.Assert(GetI64("test:integer"), Equals, int64(123)) + c.Assert(GetU("test:integer"), Equals, uint(123)) + c.Assert(GetU64("test:integer"), Equals, uint64(123)) + c.Assert(GetF("test:float"), Equals, 234.5) + c.Assert(GetB("test:boolean"), Equals, true) + c.Assert(GetM("test:file-mode"), Equals, os.FileMode(0644)) + c.Assert(GetD("test:duration", knf.Minute), Equals, 24*time.Minute) + c.Assert(GetTD("test:time-duration"), Equals, 5*time.Minute) + c.Assert(GetTS("test:timestamp").Unix(), Equals, int64(1709629048)) + c.Assert(GetTZ("test:timezone").String(), Equals, "Europe/Zurich") + c.Assert(GetL("test:list"), DeepEquals, []string{"Test1", "Test2"}) + c.Assert(GetS("test:unknown"), Equals, "") + c.Assert(GetS("test:unknown", "TestABCD"), Equals, "TestABCD") +} + +func (s *UnitedSuite) TestWithOptions(c *C) { + opts := options.NewOptions() + + _, errs := opts.Parse( + []string{ + "--test-string", "TestOpt", + "--test-integer", "456", + "--test-float", "567.8", + "--test-boolean", "false", + "--test-file-mode", "0640", + "--test-duration", "35", + "--test-time-duration", "3h", + "--test-timestamp", "1704067200", + "--test-timezone", "Europe/Prague", + "--test-list", "Test1Opt,Test2Opt", + }, options.Map{ + "test-string": {}, + "test-integer": {Type: options.INT}, + "test-float": {Type: options.FLOAT}, + "test-boolean": {Type: options.MIXED}, + "test-file-mode": {}, + "test-duration": {Type: options.INT}, + "test-time-duration": {}, + "test-timestamp": {}, + "test-timezone": {}, + "test-list": {}, + }, + ) + + c.Assert(errs, HasLen, 0) + + err := Combine( + Mapping{"test:string", "test-string", "TEST_STRING"}, + Mapping{"test:integer", "test-integer", "TEST_INTEGER"}, + Mapping{"test:float", "test-float", "TEST_FLOAT"}, + Mapping{"test:boolean", "test-boolean", "TEST_BOOLEAN"}, + Mapping{"test:file-mode", "test-file-mode", "TEST_FILE_MODE"}, + Mapping{"test:duration", "test-duration", "TEST_DURATION"}, + Mapping{"test:time-duration", "test-time-duration", "TEST_TIME_DURATION"}, + Mapping{"test:timestamp", "test-timestamp", "TEST_TIMESTAMP"}, + Mapping{"test:timezone", "test-timezone", "TEST_TIMEZONE"}, + Mapping{"test:list", "test-list", "TEST_LIST"}, + ) + + c.Assert(err, IsNil) + + optionHasFunc = opts.Has + optionGetFunc = opts.GetS + + c.Assert(GetS("test:string"), Equals, "TestOpt") + c.Assert(GetI("test:integer"), Equals, 456) + c.Assert(GetI64("test:integer"), Equals, int64(456)) + c.Assert(GetU("test:integer"), Equals, uint(456)) + c.Assert(GetU64("test:integer"), Equals, uint64(456)) + c.Assert(GetF("test:float"), Equals, 567.8) + c.Assert(GetB("test:boolean"), Equals, false) + c.Assert(GetM("test:file-mode"), Equals, os.FileMode(0640)) + c.Assert(GetD("test:duration", knf.Hour), Equals, 35*time.Hour) + c.Assert(GetTD("test:time-duration"), Equals, 3*time.Hour) + c.Assert(GetTS("test:timestamp").Unix(), Equals, int64(1704067200)) + c.Assert(GetTZ("test:timezone").String(), Equals, "Europe/Prague") + c.Assert(GetL("test:list"), DeepEquals, []string{"Test1Opt", "Test2Opt"}) + + optionHasFunc = options.Has + optionGetFunc = options.GetS +} + +func (s *UnitedSuite) TestWithEnv(c *C) { + os.Setenv("TEST_STRING", "TestEnv") + os.Setenv("TEST_INTEGER", "789") + os.Setenv("TEST_FLOAT", "678.9") + os.Setenv("TEST_BOOLEAN", "no") + os.Setenv("TEST_FILE_MODE", "0600") + os.Setenv("TEST_DURATION", "17") + os.Setenv("TEST_TIME_DURATION", "19m") + os.Setenv("TEST_TIMESTAMP", "1591014600") + os.Setenv("TEST_TIMEZONE", "Europe/Berlin") + os.Setenv("TEST_LIST", "Test1Env,Test2Env") + + err := Combine( + Mapping{"test:string", "test-string", "TEST_STRING"}, + Mapping{"test:integer", "test-integer", "TEST_INTEGER"}, + Mapping{"test:float", "test-float", "TEST_FLOAT"}, + Mapping{"test:boolean", "test-boolean", "TEST_BOOLEAN"}, + Mapping{"test:file-mode", "test-file-mode", "TEST_FILE_MODE"}, + Mapping{"test:duration", "test-duration", "TEST_DURATION"}, + Mapping{"test:time-duration", "test-time-duration", "TEST_TIME_DURATION"}, + Mapping{"test:timestamp", "test-timestamp", "TEST_TIMESTAMP"}, + Mapping{"test:timezone", "test-timezone", "TEST_TIMEZONE"}, + Mapping{"test:list", "test-list", "TEST_LIST"}, + ) + + c.Assert(err, IsNil) + + c.Assert(GetS("test:string"), Equals, "TestEnv") + c.Assert(GetI("test:integer"), Equals, 789) + c.Assert(GetI64("test:integer"), Equals, int64(789)) + c.Assert(GetU("test:integer"), Equals, uint(789)) + c.Assert(GetU64("test:integer"), Equals, uint64(789)) + c.Assert(GetF("test:float"), Equals, 678.9) + c.Assert(GetB("test:boolean"), Equals, false) + c.Assert(GetM("test:file-mode"), Equals, os.FileMode(0600)) + c.Assert(GetD("test:duration", knf.Hour), Equals, 17*time.Hour) + c.Assert(GetTD("test:time-duration"), Equals, 19*time.Minute) + c.Assert(GetTS("test:timestamp").Unix(), Equals, int64(1591014600)) + c.Assert(GetTZ("test:timezone").String(), Equals, "Europe/Berlin") + c.Assert(GetL("test:list"), DeepEquals, []string{"Test1Env", "Test2Env"}) +}