cli/mtool/tool.go

281 lines
4.5 KiB
Go

package mtool
import (
"flag"
"fmt"
"io"
"os"
"sort"
"strings"
"text/tabwriter"
)
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, customArgs ...any) {
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),
envNameMap: make(map[string] []string),
customArgs: customArgs,
}
out := flags.Output()
flags.Usage = func() {
nflags := 0
flags.VisitAll(func(f *flag.Flag){
nflags++
f.Usage = FormatInCode(f.Usage, false)
varNames, ok := flags.envNameMap[f.Name]
if !ok || len(varNames) == 0 {
return
}
f.Usage += "\n("
for i, name := range varNames {
f.Usage += "$"+name
if i < len(varNames) - 1 {
f.Usage += ", "
}
}
f.Usage += ")"
})
hasOptions := nflags != 0
// Name
if usageTool.desc != "" {
fmt.Fprintf(
out, "Name:\n %s - %s\n\n",
usageTool.FullName(), FormatInCode(t.desc, true),
)
}
// 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,
"\nDescription:\n %s\n",
FormatInCode(usageTool.ldesc, true),
)
}
// Options
if hasOptions {
fmt.Fprintln(out, "\nOptions:")
fmt.Fprintln(out, " -- option terminator")
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 || func() bool {
toolName := args[0]
_, ok := t.subs[toolName]
if ok {
return false
}
if toolName != "" && toolName[0]=='-' {
return true
}
return false
}() {
if t.desc != "" {
fmt.Fprintf(
out, "Name:\n %s - %s\n\n",
t.FullName(), FormatInCode(t.desc, true),
)
}
fmt.Fprintf(out, "Usage:\n"+
" %s <command>\n", t.FullName())
if t.ldesc != "" {
fmt.Fprintf(
out,
"\nDescription:\n %s\n",
FormatInCode(t.ldesc, true),
)
}
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.Fprintf(
out,
"%s: No such util %q'\n" +
"use ' %s ' to see available commands\n",
t.FullName(), toolName,
t.FullName(),
)
os.Exit(1)
}
sub := t.subs[toolName]
usageTool = sub
sub.Run(args, customArgs...)
}
// Returns the built-in
// string in a more printable format.
// Is used in printing descriptions.
func FormatInCode(
desc string,
addSpaces bool,
) string {
if desc == "" {
return desc
}
desc = strings.ReplaceAll(desc, "\t", "")
if desc[0] == '\n' {
desc = desc[1:]
}
if desc[len(desc)-1] == '\n' {
desc = desc[:len(desc)-1]
}
if addSpaces {
desc = strings.ReplaceAll(desc, "\n", "\n ")
}
return desc
}