Skip to content
Snippets Groups Projects
Verified Commit 63c472c9 authored by Volker Schukai's avatar Volker Schukai :alien:
Browse files

feat improve the help output

parent 0a96e1dd
No related branches found
No related tags found
No related merge requests found
/**
* Copyright (c) 2009 The Go Authors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**/
package xflags
// this file contain adapted standard function from flag.go
import (
"flag"
"fmt"
"reflect"
"strings"
)
func getFlagTable(f *flag.FlagSet) []string {
var isZeroValueErrs []error
result := []string{}
f.VisitAll(func(f *flag.Flag) {
var b strings.Builder
fmt.Fprintf(&b, " -%s", f.Name) // Two spaces before -; see next two comments.
name, usage := flag.UnquoteUsage(f)
if len(name) > 0 {
b.WriteString(" ")
b.WriteString(name)
}
// Boolean flags of one ASCII letter are so common we
// treat them specially, putting their usage on the same line.
if b.Len() <= 4 { // space, space, '-', 'x'.
b.WriteString("\t")
} else {
// Four spaces before the tab triggers good alignment
// for both 4- and 8-space tab stops.
b.WriteString("\n \t")
}
b.WriteString(strings.ReplaceAll(usage, "\n", "\n \t"))
// Print the default value only if it differs to the zero value
// for this flag type.
if isZero, err := isZeroValue(f, f.DefValue); err != nil {
isZeroValueErrs = append(isZeroValueErrs, err)
} else if !isZero {
if _, ok := f.Value.(*stringValue); ok {
// put quotes on the value
fmt.Fprintf(&b, " (default %q)", f.DefValue)
} else {
fmt.Fprintf(&b, " (default %v)", f.DefValue)
}
}
result = append(result, b.String())
})
// If calling String on any zero flag.Values triggered a panic, print
// the messages after the full set of defaults so that the programmer
// knows to fix the panic.
if errs := isZeroValueErrs; len(errs) > 0 {
fmt.Fprintln(f.Output())
for _, err := range errs {
result = append(result, err.Error())
}
}
return result
}
// isZeroValue determines whether the string represents the zero
// value for a flag.
func isZeroValue(f *flag.Flag, value string) (ok bool, err error) {
// Build a zero value of the flag's Value type, and see if the
// result of calling its String method equals the value passed in.
// This works unless the Value type is itself an interface type.
typ := reflect.TypeOf(f.Value)
var z reflect.Value
if typ.Kind() == reflect.Pointer {
z = reflect.New(typ.Elem())
} else {
z = reflect.Zero(typ)
}
// Catch panics calling the String method, which shouldn't prevent the
// usage message from being printed, but that we should report to the
// user so that they know to fix their code.
defer func() {
if e := recover(); e != nil {
if typ.Kind() == reflect.Pointer {
typ = typ.Elem()
}
err = fmt.Errorf("panic calling String method on zero %v for flag %s: %v", typ, f.Name, e)
}
}()
return value == z.Interface().(flag.Value).String(), nil
}
// -- string Value
type stringValue string
func newStringValue(val string, p *string) *stringValue {
*p = val
return (*stringValue)(p)
}
func (s *stringValue) Set(val string) error {
*s = stringValue(val)
return nil
}
func (s *stringValue) Get() any { return string(*s) }
func (s *stringValue) String() string { return string(*s) }
help.go 0 → 100644
package xflags
import (
"strings"
)
func (c *cmd[C]) getCommandLevel() (*cmd[C], []string) {
result := c
path := []string{}
path = append(path, c.name)
for _, c := range c.commands {
if c.flagSet.Parsed() {
var p []string
result, p = c.getCommandLevel()
path = append(path, p...)
break
}
}
return result, path
}
// Help returns the help text for the command
func (s *Settings[C]) Help() string {
cmd, path := s.command.getCommandLevel()
h := strings.Join(path, " ")
var help string
help = "Usage: " + h + " "
g := getFlagTable(cmd.settings.command.flagSet)
if len(g) > 0 {
help += "[global options] "
}
if len(cmd.commands) > 0 {
help += "[command] "
}
help += "[arguments]"
help += "\n"
if len(g) > 0 {
help += "\nGlobal Options:\n"
help += strings.Join(g, "\n") + "\n"
}
if len(cmd.commands) > 0 {
for _, c := range cmd.commands {
help += "\nCommand: " + c.name + "\n"
//help += fmt.Sprintf(" %s\t%s", c.name, c.tagMapping[tagDescription])
s := getFlagTable(c.flagSet)
if len(s) > 0 {
help += "\nOptions:\n"
help += strings.Join(s, "\n") + "\n"
}
}
}
return help
}
package xflags
import (
"github.com/stretchr/testify/assert"
"testing"
)
type CmdTestHelp1 struct {
A bool `short:"a" description:"Message A"`
Sub1 struct {
B bool `short:"b" description:"Message B"`
Sub2 struct {
C bool `short:"c" description:"Message C"`
Sub3 struct {
D bool `short:"d" description:"Message D"`
} `command:"sub3"`
Sub4 struct {
E bool `short:"e" description:"Message E"`
} `command:"sub4"`
} `command:"sub2"`
} `command:"sub1"`
aa int `short:"x" description:"Message X"`
}
func TestGetHelp(t *testing.T) {
tables := []struct {
name string
args []string
substring string
}{
{"test1", []string{"sub1", "--help"}, "Usage: test1 sub1 [global options] [command] [arguments]"},
{"test1", []string{"xsd", "help"}, "Usage: test1 [global options] [command] [arguments]"},
{"test2", []string{"sub1", "sub2"}, "Usage: test2 sub1 sub2 [global options] [command] [arguments]"},
}
for _, table := range tables {
t.Run(table.name, func(t *testing.T) {
s := New(table.name, CmdTestHelp1{})
s.Parse(table.args)
help := s.Help()
assert.NotEmpty(t, help)
assert.Contains(t, help, table.substring)
})
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment