Implemented more unified way for usage, description and options.

This commit is contained in:
Andrey Parhomenko 2023-11-09 23:44:57 +03:00
parent 7a1e2d6a35
commit 6f7bea24d0
4 changed files with 258 additions and 156 deletions

View file

@ -1,40 +1,54 @@
package main package main
import ( import (
"github.com/omnipunk/cli" "github.com/omnipunk/cli/mtool"
"strconv" "strconv"
"fmt" "fmt"
"os"
) )
var ( var (
tools = mtool.Tools{ root = mtool.T("test").Subs(
"echo": mtool.Tool{ mtool.T("echo").Func(func(flags *mtool.Flags) {
func(flags *mtool.Flags) {
var b bool var b bool
flags.BoolVar(&b, "b", false, "the check flag") flags.BoolVar(&b, "b", false, "the check flag")
flags.Parse() args := flags.Parse()
args := flags.Args()
fmt.Println(args) fmt.Println(args)
}, }).Desc(
"print string to standard output string", "print string to standard output string",
).Usage(
"[str1 str2 ... strN]", "[str1 str2 ... strN]",
}, ),
"sum": mtool.Tool{ mtool.T("sum").Func(func(flags *mtool.Flags) {
func(flags *mtool.Flags) {
flags.Parse() flags.Parse()
args := flags.Args() args := flags.Args()
one, _ := strconv.Atoi(args[1]) one, _ := strconv.Atoi(args[1])
two, _ := strconv.Atoi(args[2]) two, _ := strconv.Atoi(args[2])
fmt.Println(one + two) fmt.Println(one + two)
}, }).Desc(
"add one value to another", "add one value to another",
).Usage(
"<int1> <int2>", "<int1> <int2>",
}, ),
} mtool.T("sub").Subs(
mtool.T("first").Func(func(flags *mtool.Flags) {
fmt.Println("called the first", flags.Parse())
}).Desc(
"description",
).Usage(
"[nothing here]",
),
mtool.T("second").Func(func(flags *mtool.Flags){
fmt.Println("called the second", flags.Parse())
}).Desc(
"description",
).Usage(
"[nothing here]",
),
),
)
) )
func main() { func main() {
mtool.Main("test", tools) root.Run(os.Args[1:])
} }

136
main.go
View file

@ -1,136 +0,0 @@
package mtool
import(
"fmt"
"os"
path "path/filepath"
"flag"
"sort"
)
type Flags struct {
*flag.FlagSet
progName string
utilName string
args []string
parsedArgs []string
}
type Handler func(*Flags)
type Tool struct {
Handler Handler
Desc, Usage string
}
type Tools map[string] Tool
func (flags *Flags) Parse() []string {
flags.FlagSet.Parse(flags.args)
flags.parsedArgs = flags.FlagSet.Args()
return flags.parsedArgs
}
func (flags *Flags) AllArgs() []string {
return flags.args
}
func (flags *Flags) Args() []string {
return flags.parsedArgs
}
func (flags *Flags) ProgName() string {
return flags.progName
}
func (flags *Flags) UtilName() string {
return flags.utilName
}
func Main(name string, m Tools) {
var(
utilName string
args []string
)
arg0 := os.Args[0]
binBase := path.Base(arg0) ;
binBase = binBase[:len(binBase)-len(path.Ext(binBase))]
if binBase != name {
utilName = binBase
args = os.Args
} else {
if len(os.Args)<2 {
keys := make([]string, len(m))
i := 0
for k, _ := range m {
keys[i] = k
i++
}
sort.Strings(keys)
for _, k := range keys {
tool := m[k]
fmt.Printf("%s: %s\n", k, tool.Desc)
}
os.Exit(0)
}
utilName = os.Args[1]
args = os.Args[1:]
}
if _, ok := m[utilName] ; !ok {
fmt.Printf("%s: No such uitl as '%s'.\n", arg0, utilName )
os.Exit(1)
}
util := m[utilName]
flagSet := flag.NewFlagSet(utilName, flag.ExitOnError)
flags := &Flags{
FlagSet : flagSet,
}
flags.Usage = func() {
out := flags.Output()
n := 0
flags.VisitAll(func(f *flag.Flag){
n++
})
hasOptions := n != 0
fmt.Fprintf(
out,
"Usage of %s:\n\t%s",
utilName, utilName,
)
if hasOptions {
fmt.Fprintf(out, " [options]")
}
if util.Usage != "" {
fmt.Fprintf(
out,
" %s",
util.Usage,
)
}
fmt.Fprintln(out, "")
if hasOptions {
fmt.Fprintln(out, "Options:")
flags.PrintDefaults()
}
os.Exit(1)
}
flags.progName = name
flags.utilName = utilName
flags.args = args[1:]
util.Handler(flags)
}

217
mtool/main.go Normal file
View file

@ -0,0 +1,217 @@
package mtool
// The package implements fast way
// to make multitool CLI applications.
import (
"fmt"
"os"
//path "path/filepath"
"flag"
"sort"
)
type Flags struct {
*flag.FlagSet
tool *Tool
args []string
parsedArgs []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, 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) 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) ProgName() string {
for t.parent != nil {
t = t.parent
}
return t.name
}
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
// We can visit the flags since the
// function will be called after
// parsing.
flags.VisitAll(func(f *flag.Flag){
n++
})
hasOptions := n != 0
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 hasOptions {
fmt.Fprintln(out, "\nOptions:")
flags.PrintDefaults()
}
os.Exit(1)
}
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 {
keys := make([]string, len(t.subs))
i := 0
for k, _ := range t.subs {
keys[i] = k
i++
}
sort.Strings(keys)
fmt.Fprintf(out, "Usage:\n"+
" %s <command> [options] [arguments]\n\n" +
"Commands:\n", t.FullName())
for i, k := range keys {
tool := t.subs[k]
fmt.Fprintf(out, " %s\t%s\n", k, tool.desc)
if i != len(keys) - 1 {
fmt.Fprintln(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
func (flags *Flags) Parse() []string {
flags.FlagSet.Parse(flags.args)
flags.parsedArgs = flags.FlagSet.Args()
return flags.parsedArgs
}
func (flags *Flags) AllArgs() []string {
return flags.args
}
func (flags *Flags) Args() []string {
return flags.parsedArgs
}
func (flags *Flags) Tool() *Tool {
return flags.tool
}

7
taskfile.yml Normal file
View file

@ -0,0 +1,7 @@
version: '3'
tasks:
btest:
cmds:
- go build ./cmd/test