feat: implemented non-breaking way to define environment variable to flag values.

This commit is contained in:
Andrey Parhomenko 2024-04-07 18:34:29 +05:00
parent 5d79542278
commit cf9db63da6
4 changed files with 345 additions and 222 deletions

View file

@ -5,15 +5,29 @@ import (
"strconv" "strconv"
"fmt" "fmt"
"os" "os"
"strings"
) )
var ( var (
root = mtool.T("test").Subs( root = mtool.T("test").Subs(
mtool.T("echo").Func(func(flags *mtool.Flags) { mtool.T("echo").Func(func(flags *mtool.Flags) {
var b bool var (
flags.BoolVar(&b, "b", false, "the check flag") b bool
n int
)
flags.BoolVar(&b, "u", false, "convert to uppercase", "UPPERCASE")
flags.IntVar(&n, "n", 1, "amount of times to print", "NUMPRINT", "NUMPRINT1")
args := flags.Parse() args := flags.Parse()
if b {
for i, arg := range args {
args[i] = strings.ToUpper(arg)
}
}
for i:=0 ; i<n ; i++ {
fmt.Println(args) fmt.Println(args)
}
}).Desc( }).Desc(
"print string array to standard output", "print string array to standard output",
).Usage( ).Usage(

10
mtool/handler.go Normal file
View file

@ -0,0 +1,10 @@
package mtool
type Handler interface {
Handle(*Flags)
}
type HandlerFunc func(*Flags)
func (fn HandlerFunc) Handle(flags *Flags) {
fn(flags)
}

View file

@ -4,13 +4,9 @@ package mtool
// to make multitool CLI applications. // to make multitool CLI applications.
import ( import (
"fmt"
"os"
//path "path/filepath"
"flag" "flag"
"sort" "os"
"text/tabwriter" "time"
"io"
) )
@ -19,225 +15,113 @@ type Flags struct {
tool *Tool tool *Tool
args []string args []string
parsedArgs []string parsedArgs []string
} envMap map[string]string
type Handler interface {
Handle(*Flags)
}
type HandlerFunc func(*Flags)
func (fn HandlerFunc) Handle(flags *Flags) {
fn(flags)
}
type Tool struct {
name string
handler Handler
desc, ldesc, usage string
subs ToolMap
parent *Tool
}
// Returns new empty tool with specified name.
func T(name string) *Tool {
ret := &Tool{}
ret.name = name
return ret
}
func (t *Tool) Handler(h Handler) *Tool {
t.handler = h
return t
}
func (t *Tool) Func(fn HandlerFunc) *Tool {
t.handler = fn
return t
}
func (t *Tool) Desc(d string) *Tool {
t.desc = d
return t
}
func (t *Tool) Ldesc(d string) *Tool {
t.ldesc = d
return t
}
func (t *Tool) Usage(u string) *Tool {
t.usage = u
return t
}
func (t *Tool) Subs(tools ...*Tool) *Tool {
if t.subs == nil {
t.subs = ToolMap{}
}
for _, tool := range tools {
tool.parent = t
t.subs[tool.name] = tool
}
return t
}
func (t *Tool) Name() string {
return t.name
}
func (t *Tool) FullName() string {
ret := ""
for t != nil {
ret = t.name + func() string {
if ret != "" {
return " "
}
return ""
}() + ret
t = t.parent
}
return ret
}
func (t *Tool) IsRoot() bool {
return t.parent == nil
}
func (t *Tool) ProgName() string {
for t.parent != nil {
t = t.parent
}
return t.name
}
func (t *Tool) PrintSubs(out io.Writer) {
w := new(tabwriter.Writer)
w.Init(out, 0, 0, 1, ' ', 0)
defer w.Flush()
keys := make([]string, len(t.subs))
i := 0
for k, _ := range t.subs {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
tool := t.subs[k]
fmt.Fprintf(w, " %s\t%s\n", k, tool.desc)
}
}
func (t *Tool) Run(args []string) {
var(
usageTool *Tool
)
// Should implement the shit later
//binBase := path.Base(arg0) ;
//binBase = binBase[:len(binBase)-len(path.Ext(binBase))]
flagSet := flag.NewFlagSet(t.FullName(), flag.ExitOnError)
flags := &Flags{
FlagSet : flagSet,
}
out := flags.Output()
flags.Usage = func() {
n := 0
flags.VisitAll(func(f *flag.Flag){
n++
})
hasOptions := n != 0
// Name
if usageTool.desc != "" {
fmt.Fprintf(
out, "Name:\n %s - %s\n\n",
usageTool.FullName(), t.desc,
)
}
// Usage
fmt.Fprintf(
out, "Usage:\n %s",
usageTool.FullName(),
)
if hasOptions {
fmt.Fprintf(out, " [options]")
}
if usageTool.usage != "" {
fmt.Fprintf(
out,
" %s",
usageTool.usage,
)
}
fmt.Fprintln(out, "")
if usageTool.ldesc != "" {
fmt.Fprintf(out, "Description:\n %s", usageTool.ldesc)
}
// Options
if hasOptions {
fmt.Fprintln(out, "\nOptions:")
flags.PrintDefaults()
}
}
flags.args = args
// If the tool has its own handler run it.
if t.handler != nil {
usageTool = t
t.handler.Handle(flags)
return
}
// Print available sub commands if
// got no arguments.
if len(args) == 0 {
if t.desc != "" {
fmt.Fprintf(
out, "Name:\n %s - %s\n\n",
t.FullName(), t.desc,
)
}
fmt.Fprintf(out, "Usage:\n"+
" %s <command>\n", t.FullName())
if t.ldesc != "" {
fmt.Fprintf(out, "\nDescription:\n %s\n", t.ldesc)
}
if len(t.subs) > 0 {
fmt.Fprint(out, "\nCommands:\n")
t.PrintSubs(out)
}
os.Exit(1)
}
toolName := args[0]
args = args[1:]
if _, ok := t.subs[toolName] ; !ok {
fmt.Printf("%s: No such util %q'\n", t.ProgName(), toolName)
os.Exit(1)
}
sub := t.subs[toolName]
usageTool = sub
sub.Run(args)
} }
type ToolMap map[string] *Tool type ToolMap map[string] *Tool
func (flags *Flags) wasPassed(name string) bool {
found := false
flags.Visit(func(f *flag.Flag){
if f.Name == name {
found = true
}
})
return found
}
func (flags *Flags) setEnv(
name string,
env []string,
) bool {
for _, k := range env {
value, has := os.LookupEnv(k)
if !has {
continue
}
flags.envMap[name] = value
return true
}
return false
}
func (flags *Flags) StringVar(
p *string,
name string,
value string,
usage string,
env ...string,
) {
flags.FlagSet.StringVar(p, name, value, usage)
flags.setEnv(name, env)
}
func (flags *Flags) IntVar(
p *int,
name string,
value int,
usage string,
env ...string,
) {
flags.FlagSet.IntVar(p, name, value, usage)
flags.setEnv(name, env)
}
func (flags *Flags) Int64Var(
p *int64,
name string,
value int64,
usage string,
env ...string,
) {
flags.FlagSet.Int64Var(p, name, value, usage)
flags.setEnv(name, env)
}
func (flags *Flags) BoolVar(
p *bool,
name string,
value bool,
usage string,
env ...string,
) {
flags.FlagSet.BoolVar(p, name, value, usage)
flags.setEnv(name, env)
}
func (flags *Flags) Float64Var(
p *float64,
name string,
value float64,
usage string,
env ...string,
) {
flags.FlagSet.Float64Var(p, name, value, usage)
flags.setEnv(name, env)
}
func (flags *Flags) DurationVar(
p *time.Duration,
name string,
value time.Duration,
usage string,
env ...string,
) {
flags.FlagSet.DurationVar(p, name, value, usage)
flags.setEnv(name, env)
}
func (flags *Flags) Parse() []string { func (flags *Flags) Parse() []string {
flags.FlagSet.Parse(flags.args) flags.FlagSet.Parse(flags.args)
for name, v := range flags.envMap {
if !flags.wasPassed(name) {
flags.FlagSet.Set(name, v)
}
}
flags.parsedArgs = flags.FlagSet.Args() flags.parsedArgs = flags.FlagSet.Args()
return flags.parsedArgs return flags.parsedArgs
} }

215
mtool/tool.go Normal file
View file

@ -0,0 +1,215 @@
package mtool
import (
"text/tabwriter"
"flag"
"sort"
"fmt"
"io"
"os"
)
type Tool struct {
name string
handler Handler
desc, ldesc, usage string
subs ToolMap
parent *Tool
}
// Returns new empty tool with specified name.
func T(name string) *Tool {
ret := &Tool{}
ret.name = name
return ret
}
func (t *Tool) Handler(h Handler) *Tool {
t.handler = h
return t
}
func (t *Tool) Func(fn HandlerFunc) *Tool {
t.handler = fn
return t
}
func (t *Tool) Desc(d string) *Tool {
t.desc = d
return t
}
func (t *Tool) Ldesc(d string) *Tool {
t.ldesc = d
return t
}
func (t *Tool) Usage(u string) *Tool {
t.usage = u
return t
}
func (t *Tool) Subs(tools ...*Tool) *Tool {
if t.subs == nil {
t.subs = ToolMap{}
}
for _, tool := range tools {
tool.parent = t
t.subs[tool.name] = tool
}
return t
}
func (t *Tool) Name() string {
return t.name
}
func (t *Tool) FullName() string {
ret := ""
for t != nil {
ret = t.name + func() string {
if ret != "" {
return " "
}
return ""
}() + ret
t = t.parent
}
return ret
}
func (t *Tool) IsRoot() bool {
return t.parent == nil
}
func (t *Tool) ProgName() string {
for t.parent != nil {
t = t.parent
}
return t.name
}
func (t *Tool) PrintSubs(out io.Writer) {
w := new(tabwriter.Writer)
w.Init(out, 0, 0, 1, ' ', 0)
defer w.Flush()
keys := make([]string, len(t.subs))
i := 0
for k, _ := range t.subs {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
tool := t.subs[k]
fmt.Fprintf(w, " %s\t%s\n", k, tool.desc)
}
}
func (t *Tool) Run(args []string) {
var(
usageTool *Tool
)
// Should implement the shit later
//binBase := path.Base(arg0) ;
//binBase = binBase[:len(binBase)-len(path.Ext(binBase))]
flagSet := flag.NewFlagSet(t.FullName(), flag.ExitOnError)
flags := &Flags{
FlagSet : flagSet,
envMap: make(map[string]string),
}
out := flags.Output()
flags.Usage = func() {
n := 0
flags.VisitAll(func(f *flag.Flag){
n++
})
hasOptions := n != 0
// Name
if usageTool.desc != "" {
fmt.Fprintf(
out, "Name:\n %s - %s\n\n",
usageTool.FullName(), t.desc,
)
}
// Usage
fmt.Fprintf(
out, "Usage:\n %s",
usageTool.FullName(),
)
if hasOptions {
fmt.Fprintf(out, " [options]")
}
if usageTool.usage != "" {
fmt.Fprintf(
out,
" %s",
usageTool.usage,
)
}
fmt.Fprintln(out, "")
if usageTool.ldesc != "" {
fmt.Fprintf(out, "Description:\n %s", usageTool.ldesc)
}
// Options
if hasOptions {
fmt.Fprintln(out, "\nOptions:")
flags.PrintDefaults()
}
}
flags.args = args
// If the tool has its own handler run it.
if t.handler != nil {
usageTool = t
t.handler.Handle(flags)
return
}
// Print available sub commands if
// got no arguments.
if len(args) == 0 {
if t.desc != "" {
fmt.Fprintf(
out, "Name:\n %s - %s\n\n",
t.FullName(), t.desc,
)
}
fmt.Fprintf(out, "Usage:\n"+
" %s <command>\n", t.FullName())
if t.ldesc != "" {
fmt.Fprintf(out, "\nDescription:\n %s\n", t.ldesc)
}
if len(t.subs) > 0 {
fmt.Fprint(out, "\nCommands:\n")
t.PrintSubs(out)
}
os.Exit(1)
}
toolName := args[0]
args = args[1:]
if _, ok := t.subs[toolName] ; !ok {
fmt.Printf("%s: No such util %q'\n", t.ProgName(), toolName)
os.Exit(1)
}
sub := t.subs[toolName]
usageTool = sub
sub.Run(args)
}