Skip to content

Commit

Permalink
✨ feat(flag): add some new ext flag value implements
Browse files Browse the repository at this point in the history
- flag option help support render repeatable mark
- add method ops.VarOpt2 for quick add var flag
  • Loading branch information
inhere committed Mar 14, 2023
1 parent fc4b8b8 commit a3fda82
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 153 deletions.
27 changes: 5 additions & 22 deletions gcli.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,40 +244,23 @@ func IsDebugMode() bool { return gOpts.Verbose >= VerbDebug }
*************************************************************************/

// Ints The int flag list, implemented flag.Value interface
// Deprecated
type Ints = cflag.Ints

// Strings The string flag list, implemented flag.Value interface
// Deprecated
type Strings = cflag.Strings

// Booleans The bool flag list, implemented flag.Value interface
// Deprecated
type Booleans = cflag.Booleans

// EnumString The string flag list, implemented flag.Value interface
// Deprecated
type EnumString = cflag.EnumString

// KVString The key-value string flag, repeatable.
type KVString = cflag.KVString

// ConfString The config-string flag, INI format, like nginx-config
type ConfString = cflag.ConfString

// String type, a special string
//
// Usage:
//
// // case 1:
// var names gcli.String
// c.VarOpt(&names, "names", "", "multi name by comma split")
//
// --names "tom,john,joy"
// names.Split(",") -> []string{"tom","john","joy"}
//
// // case 2:
// var ids gcli.String
// c.VarOpt(&ids, "ids", "", "multi id by comma split")
//
// --names "23,34,56"
// names.Ints(",") -> []int{23,34,56}
// Deprecated
type String = cflag.String

/*************************************************************************
Expand Down
6 changes: 6 additions & 0 deletions gflag/gflag.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ type OptCategory struct {
// Ints The int flag list, implemented flag.Value interface
type Ints = cflag.Ints

// IntsString implemented flag.Value interface
type IntsString = cflag.IntsString

// String The special string flag, implemented flag.Value interface
type String = cflag.String

// Strings The string flag list, implemented flag.Value interface
type Strings = cflag.Strings

Expand Down
146 changes: 146 additions & 0 deletions gflag/help.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package gflag

import (
"bytes"
"flag"
"fmt"
"strings"

"github.com/gookit/color"
"github.com/gookit/goutil/cflag"
"github.com/gookit/goutil/strutil"
)

/***********************************************************************
* Flags:
* - render help message
***********************************************************************/

// SetHelpRender set the raw *flag.FlagSet.Usage
func (p *Parser) SetHelpRender(fn func()) {
p.fSet.Usage = fn
}

// PrintHelpPanel for all options to the gf.out
func (p *Parser) PrintHelpPanel() {
color.Fprint(p.out, p.String())
}

// String for all flag options
func (p *Parser) String() string {
return p.BuildHelp()
}

// BuildHelp string for all flag options
func (p *Parser) BuildHelp() string {
if p.buf == nil {
p.buf = new(bytes.Buffer)
}

// repeat call the method
if p.buf.Len() < 1 {
p.buf.WriteString("Options:\n")
p.buf.WriteString(p.BuildOptsHelp())
p.buf.WriteByte('\n')

if p.HasArgs() {
p.buf.WriteString("Arguments:\n")
p.buf.WriteString(p.BuildArgsHelp())
p.buf.WriteByte('\n')
}
}

return p.buf.String()
}

// BuildOptsHelp string.
func (p *Parser) BuildOptsHelp() string {
var sb strings.Builder

p.fSet.VisitAll(func(f *flag.Flag) {
line := p.formatOneFlag(f)
if line != "" {
sb.WriteString(line)
sb.WriteByte('\n')
}
})

return sb.String()
}

func (p *Parser) formatOneFlag(f *flag.Flag) (s string) {
// Skip render:
// - opt is not exists(Has ensured that it is not a short name)
// - it is hidden flag option
// - flag desc is empty
opt, has := p.opts[f.Name]
if !has || opt.Hidden {
return
}

var fullName string
name := f.Name
// eg: "-V, --version" length is: 13
nameLen := p.names[name]
// display description on new line
descNl := p.cfg.DescNewline

var nlIndent string
if descNl {
nlIndent = "\n "
} else {
nlIndent = "\n " + strings.Repeat(" ", p.optMaxLen)
}

// add prefix '-' to option
fullName = cflag.AddPrefixes2(name, opt.Shorts, true)
s = fmt.Sprintf(" <info>%s</>", fullName)

// - build flag type info
typeName, desc := flag.UnquoteUsage(f)
// typeName: option value data type: int, string, ..., bool value will return ""
if !p.cfg.WithoutType && len(typeName) > 0 {
typeLen := len(typeName) + 1
if !descNl && nameLen+typeLen > p.optMaxLen {
descNl = true
} else {
nameLen += typeLen
}

s += fmt.Sprintf(" <magenta>%s</>", typeName)
}

if descNl {
s += nlIndent
} else {
// padding space to optMaxLen width.
if padLen := p.optMaxLen - nameLen; padLen > 0 {
s += strings.Repeat(" ", padLen)
}
s += " "
}

// --- build description
if desc == "" {
desc = defaultDesc
} else {
desc = strings.Replace(strutil.UpperFirst(desc), "\n", nlIndent, -1)
}

s += getRequiredMark(opt.Required) + desc

// ---- append default value
if isZero, isStr := cflag.IsZeroValue(f, f.DefValue); !isZero {
if isStr {
s += fmt.Sprintf(" (default <magentaB>%q</>)", f.DefValue)
} else {
s += fmt.Sprintf(" (default <magentaB>%v</>)", f.DefValue)
}
}

// arrayed, repeatable
if _, ok := f.Value.(cflag.RepeatableFlag); ok {
s += " <cyan>(repeatable)</>"
}
return s
}
5 changes: 5 additions & 0 deletions gflag/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,11 @@ func (ops *CliOpts) VarOpt(v flag.Value, name, shorts, desc string) {
ops.varOpt(v, newOpt(name, desc, nil, shorts))
}

// VarOpt2 binding an int option and with config func.
func (ops *CliOpts) VarOpt2(v flag.Value, nameAndShorts, desc string, setFns ...CliOptFn) {
ops.varOpt(v, NewOpt(nameAndShorts, desc, nil, setFns...))
}

// binding option and shorts
func (ops *CliOpts) varOpt(v flag.Value, opt *CliOpt) {
name := ops.checkFlagInfo(opt)
Expand Down
152 changes: 21 additions & 131 deletions gflag/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"io"
"os"
"reflect"
"strings"
"unsafe"

"github.com/gookit/color"
Expand Down Expand Up @@ -235,6 +234,27 @@ var (
)

// FromStruct from struct tag binding options
//
// ## Named rule:
//
// // tag format: name=val0;shorts=i;required=true;desc=a message
// type UserCmdOpts struct {
// Name string `flag:"name=name;shorts=n;required=true;desc=input username"`
// Age int `flag:"name=age;shorts=a;required=true;desc=input user age"`
// }
// opt := &UserCmdOpts{}
// p.FromStruct(opt)
//
// ## Simple rule
//
// // tag format1: name;desc;required;default;shorts
// // tag format2: desc;required;default;shorts
// type UserCmdOpts struct {
// Name string `flag:"input username;true;;n"`
// Age int `flag:"age;input user age;true;;o"`
// }
// opt := &UserCmdOpts{}
// p.UseSimpleRule().FromStruct(opt)
func (p *Parser) FromStruct(ptr any) (err error) {
v := reflect.ValueOf(ptr)
if v.Kind() != reflect.Ptr {
Expand Down Expand Up @@ -362,136 +382,6 @@ func (p *Parser) Required(names ...string) {
}
}

/***********************************************************************
* Flags:
* - render help message
***********************************************************************/

// SetHelpRender set the raw *flag.FlagSet.Usage
func (p *Parser) SetHelpRender(fn func()) {
p.fSet.Usage = fn
}

// PrintHelpPanel for all options to the gf.out
func (p *Parser) PrintHelpPanel() {
color.Fprint(p.out, p.String())
}

// String for all flag options
func (p *Parser) String() string {
return p.BuildHelp()
}

// BuildHelp string for all flag options
func (p *Parser) BuildHelp() string {
if p.buf == nil {
p.buf = new(bytes.Buffer)
}

// repeat call the method
if p.buf.Len() < 1 {
p.buf.WriteString("Options:\n")
p.buf.WriteString(p.BuildOptsHelp())
p.buf.WriteByte('\n')

if p.HasArgs() {
p.buf.WriteString("Arguments:\n")
p.buf.WriteString(p.BuildArgsHelp())
p.buf.WriteByte('\n')
}
}

return p.buf.String()
}

// BuildOptsHelp string.
func (p *Parser) BuildOptsHelp() string {
var sb strings.Builder

p.fSet.VisitAll(func(f *flag.Flag) {
line := p.formatOneFlag(f)
if line != "" {
sb.WriteString(line)
sb.WriteByte('\n')
}
})

return sb.String()
}

func (p *Parser) formatOneFlag(f *flag.Flag) (s string) {
// Skip render:
// - opt is not exists(Has ensured that it is not a short name)
// - it is hidden flag option
// - flag desc is empty
opt, has := p.opts[f.Name]
if !has || opt.Hidden {
return
}

var fullName string
name := f.Name
// eg: "-V, --version" length is: 13
nameLen := p.names[name]
// display description on new line
descNl := p.cfg.DescNewline

var nlIndent string
if descNl {
nlIndent = "\n "
} else {
nlIndent = "\n " + strings.Repeat(" ", p.optMaxLen)
}

// add prefix '-' to option
fullName = cflag.AddPrefixes2(name, opt.Shorts, true)
s = fmt.Sprintf(" <info>%s</>", fullName)

// - build flag type info
typeName, desc := flag.UnquoteUsage(f)
// typeName: option value data type: int, string, ..., bool value will return ""
if !p.cfg.WithoutType && len(typeName) > 0 {
typeLen := len(typeName) + 1
if !descNl && nameLen+typeLen > p.optMaxLen {
descNl = true
} else {
nameLen += typeLen
}

s += fmt.Sprintf(" <magenta>%s</>", typeName)
}

if descNl {
s += nlIndent
} else {
// padding space to optMaxLen width.
if padLen := p.optMaxLen - nameLen; padLen > 0 {
s += strings.Repeat(" ", padLen)
}
s += " "
}

// --- build description
if desc == "" {
desc = defaultDesc
} else {
desc = strings.Replace(strutil.UpperFirst(desc), "\n", nlIndent, -1)
}

s += getRequiredMark(opt.Required) + desc

// ---- append default value
if isZero, isStr := cflag.IsZeroValue(f, f.DefValue); !isZero {
if isStr {
s += fmt.Sprintf(" (default <magentaB>%q</>)", f.DefValue)
} else {
s += fmt.Sprintf(" (default <magentaB>%v</>)", f.DefValue)
}
}

return s
}

/***********************************************************************
* Flags:
* - helper methods
Expand Down

0 comments on commit a3fda82

Please sign in to comment.