From 5ae5368eff4ff5a639eed8bbb6577af9b2f5de73 Mon Sep 17 00:00:00 2001 From: Yancharuk Alexander Date: Sat, 12 Apr 2025 22:16:59 +0300 Subject: [PATCH] fix: Remove short options from conf.String() output fixes #48 --- conf.go | 8 +++- conf_test.go | 129 +++++++++++++++++++++++++++++++++++---------------- go.mod | 1 - go.sum | 2 - sources.go | 5 ++ usage.go | 14 +++--- 6 files changed, 106 insertions(+), 53 deletions(-) diff --git a/conf.go b/conf.go index cbf216b..6760a95 100644 --- a/conf.go +++ b/conf.go @@ -6,6 +6,7 @@ import ( "net/url" "os" "reflect" + "sort" "strings" ) @@ -75,13 +76,16 @@ func String(v interface{}) (string, error) { return "", err } + sf := sortedFields{fields: fields} + sort.Sort(&sf) + var s strings.Builder - for i, fld := range fields { + for i, fld := range sf.fields { if fld.Options.Noprint { continue } - s.WriteString(flagUsage(fld)) + s.WriteString(longOptInfo(fld)) s.WriteString("=") v := fmt.Sprintf("%v", fld.Field.Interface()) diff --git a/conf_test.go b/conf_test.go index 616b671..b3bfc98 100644 --- a/conf_test.go +++ b/conf_test.go @@ -580,6 +580,7 @@ func TestUsage(t *testing.T) { t.Log(diff) t.Log("GOT:\n", got) t.Log("EXP:\n", tt.want) + return } t.Logf("\t%s\tShould match byte for byte the output.", success) } @@ -605,6 +606,7 @@ func TestUsage(t *testing.T) { t.Log(diff) t.Log("GOT:\n", got) t.Log("EXP:\n", tt.options) + return } t.Logf("\t%s\tShould match byte for byte the output.", success) } @@ -615,55 +617,98 @@ func TestUsage(t *testing.T) { } } +var expectedStringOutput = ` --a-string=B + --an-int=1 + --bool=true + --custom=@hello@ + --debug-host=http://xxxxxx:xxxxxx@0.0.0.0:4000 + --e-dur=1m0s + --immutable=mydefaultvalue + --ip-endpoints=[127.0.0.1:200 127.0.0.1:829] + --ip-ip=127.0.0.0 + --ip-name=localhost + --name=andy + --password=xxxxxx` + func TestExampleString(t *testing.T) { - tt := struct { - envs map[string]string + tests := []struct { + name string + namespace string + want string + envs map[string]string }{ - envs: map[string]string{ - "TEST_AN_INT": "1", - "TEST_S": "s", - "TEST_BOOL": "TRUE", - "TEST_SKIP": "SKIP", - "TEST_IP_NAME": "local", - "TEST_NAME": "andy", - "TEST_DURATION": "1m", + { + name: "with-namespace", + namespace: "TEST", + envs: map[string]string{ + "TEST_AN_INT": "1", + "TEST_S": "s", + "TEST_BOOL": "TRUE", + "TEST_SKIP": "SKIP", + "TEST_IP_NAME": "local", + "TEST_NAME": "andy", + "TEST_DURATION": "1m", + }, + want: expectedStringOutput, + }, + { + name: "without-namespace", + namespace: "", + envs: map[string]string{ + "AN_INT": "1", + "S": "s", + "BOOL": "TRUE", + "SKIP": "SKIP", + "IP_NAME": "local", + "NAME": "andy", + "DURATION": "1m", + }, + want: expectedStringOutput, }, } - os.Clearenv() - for k, v := range tt.envs { - os.Setenv(k, v) - } + t.Log("Given the need validate conf output.") + { + for testID, tt := range tests { + f := func(t *testing.T) { + t.Logf("\tTest: %d\tWhen testing %s", testID, tt.name) + { + os.Clearenv() + for k, v := range tt.envs { + os.Setenv(k, v) + } - os.Args = []string{"conf.test"} + os.Args = []string{"conf.test"} - var cfg config - if _, err := conf.Parse("TEST", &cfg); err != nil { - fmt.Print(err) - return - } + var cfg config + if _, err := conf.Parse(tt.namespace, &cfg); err != nil { + t.Log(err) + return + } - out, err := conf.String(&cfg) - if err != nil { - fmt.Print(err) - return - } + got, err := conf.String(&cfg) + if err != nil { + t.Log(err) + return + } + + got = strings.TrimRight(got, "\n") + gotS := strings.Split(got, "\n") + wantS := strings.Split(tt.want, "\n") + if diff := cmp.Diff(gotS, wantS); diff != "" { + t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed) + t.Log(diff) + t.Log("GOT:\n", got) + t.Log("EXP:\n", tt.want) + return + } + t.Logf("\t%s\tShould match byte for byte the output.", success) + } + } - fmt.Print(out) - - // Output: - // --an-int=1 - // --a-string/-s=B - // --bool=true - // --ip-name=localhost - // --ip-ip=127.0.0.0 - // --ip-endpoints=[127.0.0.1:200 127.0.0.1:829] - // --debug-host=http://xxxxxx:xxxxxx@0.0.0.0:4000 - // --password=xxxxxx - // --immutable=mydefaultvalue - // --custom=@hello@ - // --name=andy - // --e-dur/-d=1m0s + t.Run(tt.name, f) + } + } } type ConfExplicit struct { @@ -758,10 +803,11 @@ func TestVersionExplicit(t *testing.T) { f := func(t *testing.T) { os.Args = tt.args if help, err := conf.Parse("APP", &tt.config); err != nil { - if err == conf.ErrHelpWanted { + if err != conf.ErrHelpWanted { if diff := cmp.Diff(tt.want, help); diff != "" { t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed) t.Log(diff) + return } t.Logf("\t%s\tShould match byte for byte the output.", success) } @@ -860,6 +906,7 @@ func TestVersionImplicit(t *testing.T) { if diff := cmp.Diff(tt.want, help); diff != "" { t.Errorf("\t%s\tShould match the output byte for byte. See diff:", failed) t.Log(diff) + return } t.Logf("\t%s\tShould match byte for byte the output.", success) } diff --git a/go.mod b/go.mod index 9c24a0b..c525621 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,5 @@ go 1.23.0 require ( github.com/google/go-cmp v0.3.1 - golang.org/x/text v0.23.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 7878027..3daa9e8 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/sources.go b/sources.go index c71979c..611e13b 100644 --- a/sources.go +++ b/sources.go @@ -211,6 +211,11 @@ func flagUsage(fld Field) string { return usage } +// longOptInfo constructs a long option description string. +func longOptInfo(fld Field) string { + return " --" + strings.ToLower(strings.Join(fld.FlagKey, `-`)) +} + /* Portions Copyright (c) 2009 The Go Authors. All rights reserved. diff --git a/usage.go b/usage.go index d2af031..954024f 100644 --- a/usage.go +++ b/usage.go @@ -5,11 +5,9 @@ import ( "os" "path" "reflect" + "sort" "strings" "text/tabwriter" - - "golang.org/x/text/collate" - "golang.org/x/text/language" ) const ( @@ -31,8 +29,11 @@ func (sf sortedFields) Swap(i, j int) { sf.fields[i], sf.fields[j] = sf.fields[j], sf.fields[i] } -func (sf sortedFields) Bytes(i int) []byte { - return []byte(strings.ToLower(strings.Join(sf.fields[i].FlagKey, `-`))) +func (sf sortedFields) Less(i, j int) bool { + s1 := strings.ToLower(strings.Join(sf.fields[i].FlagKey, `-`)) + s2 := strings.ToLower(strings.Join(sf.fields[j].FlagKey, `-`)) + + return s1 < s2 } func containsField(fields []Field, name string) bool { @@ -70,8 +71,7 @@ func fmtUsage(namespace string, fields []Field) string { } sf := sortedFields{fields: fields} - cc := collate.New(language.English) - cc.Sort(sf) + sort.Sort(&sf) _, file := path.Split(os.Args[0]) fmt.Fprintf(&sb, "Usage: %s [options...] [arguments...]\n\n", file)