This commit is contained in:
21
app.go
21
app.go
@@ -28,7 +28,7 @@ func WithInput(in config.BindProvider) func(*App) {
|
||||
|
||||
// WithSkipArgs sets how many arguments are passed. For example, you don't need to pass the name of a single command.
|
||||
func WithSkipArgs(l int) func(*App) {
|
||||
return WithInput(chain.New(arg.New(arg.WithSkip(l)), &memory.Default{}))
|
||||
return WithInput(chain.New(arg.New(arg.WithArgs(os.Args[resolveSkip(l):])), &memory.Default{}))
|
||||
}
|
||||
|
||||
// WithExit sets exit callback by default os.Exit.
|
||||
@@ -43,7 +43,7 @@ func New(opts ...func(*App)) *App {
|
||||
app := &App{
|
||||
out: output.Stdout(),
|
||||
exit: os.Exit,
|
||||
in: chain.New(arg.New(arg.WithSkip(0)), &memory.Default{}),
|
||||
in: chain.New(arg.New(arg.WithArgs(os.Args[resolveSkip(0):])), &memory.Default{}),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
@@ -125,3 +125,20 @@ func (a *App) list(ctx context.Context) error {
|
||||
func (a *App) printError(ctx context.Context, err error) {
|
||||
ansi(ctx, a.in, a.out).Println(ctx, "<error>\n\n ", err, "\n</error>")
|
||||
}
|
||||
|
||||
func resolveSkip(in int) int {
|
||||
res := 2
|
||||
|
||||
switch {
|
||||
case in > 0 && len(os.Args) > in:
|
||||
res = in
|
||||
case in > 0:
|
||||
res = len(os.Args)
|
||||
case len(os.Args) == 1:
|
||||
res = 1
|
||||
case len(os.Args) > 1 && os.Args[1][0] == '-':
|
||||
res = 1
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func ExampleNew_help() {
|
||||
// --group-test-string[=GROUP-TEST-STRING] test group string [default:group string default value]
|
||||
// --log-{service}-level[=LOG-{SERVICE}-LEVEL] service level [default:debug]
|
||||
// --bool test bool option
|
||||
// --duration[=DURATION] test duration with default [default: 1s]
|
||||
// --duration[=DURATION] test duration with default
|
||||
// --ansi Do not ask any interactive question
|
||||
// -V, --version Display this application version
|
||||
// -h, --help Display this help message
|
||||
|
||||
2
go.mod
2
go.mod
@@ -2,7 +2,7 @@ module gitoa.ru/go-4devs/console
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require gitoa.ru/go-4devs/config v0.0.7
|
||||
require gitoa.ru/go-4devs/config v0.0.8
|
||||
|
||||
require (
|
||||
golang.org/x/mod v0.31.0 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@@ -1,12 +1,10 @@
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
gitoa.ru/go-4devs/config v0.0.7 h1:8q6axRNLgXE5dYQd8Jbh9j+STqevbibVyvwrtsuHpZk=
|
||||
gitoa.ru/go-4devs/config v0.0.7/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=
|
||||
gitoa.ru/go-4devs/config v0.0.8 h1:o4p8I9jWJMfiFVVKr50IqCGj1fF+8kmSPDkH0deRvn4=
|
||||
gitoa.ru/go-4devs/config v0.0.8/go.mod h1:jHKqVafFVW400LC0M4i1ifPapiI9sqpX/QTh+VMadKw=
|
||||
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
|
||||
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
|
||||
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
|
||||
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
|
||||
|
||||
3
help.go
3
help.go
@@ -9,6 +9,7 @@ import (
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
@@ -70,7 +71,7 @@ To display the list of available commands, please use the <info>list</info> comm
|
||||
Name: cmd.Name,
|
||||
Description: cmd.Description,
|
||||
Help: cmd.Help,
|
||||
Definition: descriptor.NewDefinition(config.NewVars(def.Options()...).Variables()),
|
||||
Options: def.With(param.New(descriptor.TxtStyle())),
|
||||
})
|
||||
if derr != nil {
|
||||
return fmt.Errorf("descriptor help:%w", derr)
|
||||
|
||||
3
list.go
3
list.go
@@ -8,6 +8,7 @@ import (
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/config/validator"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
@@ -72,7 +73,7 @@ func executeList(ctx context.Context, in config.Provider, out output.Output) err
|
||||
cmds := Commands()
|
||||
commands := descriptor.Commands{
|
||||
Namespace: ns,
|
||||
Definition: descriptor.NewDefinition(config.NewVars(definition.New(Default()...).Options()...).Variables()),
|
||||
Options: definition.New(Default()...).With(param.New(descriptor.TxtStyle())),
|
||||
}
|
||||
groups := make(map[string]*descriptor.NSCommand)
|
||||
namespaces := make([]string, 0, len(cmds))
|
||||
|
||||
@@ -3,13 +3,9 @@ package descriptor
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
@@ -24,16 +20,18 @@ var (
|
||||
)
|
||||
|
||||
type Command struct {
|
||||
config.Options
|
||||
|
||||
Bin string
|
||||
Name string
|
||||
Description string
|
||||
Help string
|
||||
Definition Definition
|
||||
}
|
||||
|
||||
type Commands struct {
|
||||
config.Options
|
||||
|
||||
Namespace string
|
||||
Definition Definition
|
||||
Commands []NSCommand
|
||||
}
|
||||
|
||||
@@ -46,65 +44,6 @@ func (n *NSCommand) Append(name, desc string) {
|
||||
n.Commands = append(n.Commands, ShortCommand{Name: name, Description: desc})
|
||||
}
|
||||
|
||||
func NewDefinition(opts []config.Variable) Definition {
|
||||
type data struct {
|
||||
name string
|
||||
pos uint64
|
||||
opt config.Variable
|
||||
}
|
||||
|
||||
posArgs := make([]data, 0, len(opts))
|
||||
|
||||
posOpt := make([]data, 0, len(opts))
|
||||
for _, opt := range opts {
|
||||
pos, ok := arg.ParamArgument(opt)
|
||||
if !ok {
|
||||
pos, _ = option.DataPosition(opt)
|
||||
posOpt = append(posOpt, data{pos: pos, opt: opt})
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
posArgs = append(posArgs, data{name: strings.Join(opt.Key(), "."), pos: pos, opt: opt})
|
||||
}
|
||||
|
||||
sort.Slice(posArgs, func(i, j int) bool {
|
||||
return posArgs[i].pos > posArgs[j].pos && posArgs[i].name > posArgs[j].name
|
||||
})
|
||||
|
||||
sort.Slice(posOpt, func(i, j int) bool {
|
||||
return posOpt[i].pos < posOpt[j].pos
|
||||
})
|
||||
|
||||
args := make([]config.Variable, len(posArgs))
|
||||
for idx := range posArgs {
|
||||
args[idx] = posArgs[idx].opt
|
||||
}
|
||||
|
||||
options := make([]config.Variable, len(posOpt))
|
||||
for idx := range posOpt {
|
||||
options[idx] = posOpt[idx].opt
|
||||
}
|
||||
|
||||
return Definition{
|
||||
options: options,
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
|
||||
type Definition struct {
|
||||
args []config.Variable
|
||||
options []config.Variable
|
||||
}
|
||||
|
||||
func (d Definition) Arguments() []config.Variable {
|
||||
return d.args
|
||||
}
|
||||
|
||||
func (d Definition) Options() []config.Variable {
|
||||
return d.options
|
||||
}
|
||||
|
||||
type ShortCommand struct {
|
||||
Name string
|
||||
Description string
|
||||
|
||||
@@ -4,21 +4,18 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"gitoa.ru/go-4devs/config"
|
||||
"gitoa.ru/go-4devs/config/definition/option"
|
||||
"gitoa.ru/go-4devs/config/param"
|
||||
"gitoa.ru/go-4devs/config/value"
|
||||
"gitoa.ru/go-4devs/config/provider/arg"
|
||||
"gitoa.ru/go-4devs/console/output"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSpace = 2
|
||||
infoLen = 13
|
||||
dashDelimiter = "-"
|
||||
)
|
||||
|
||||
@@ -40,8 +37,8 @@ var (
|
||||
|
||||
{{ end -}}
|
||||
<comment>Usage:</comment>
|
||||
{{ .Name }} {{ synopsis .Definition }}
|
||||
{{- definition .Definition }}
|
||||
{{ .Name }} {{ synopsis .Options }}
|
||||
{{ definition .Options }}
|
||||
{{- help . }}
|
||||
`))
|
||||
|
||||
@@ -49,11 +46,24 @@ var (
|
||||
Funcs(txtFunc).
|
||||
Parse(`<comment>Usage:</comment>
|
||||
command [options] [arguments]
|
||||
{{- definition .Definition }}
|
||||
{{ definition .Options }}
|
||||
{{- commands .Commands -}}
|
||||
`))
|
||||
)
|
||||
|
||||
func TxtStyle() param.Option {
|
||||
return arg.WithStyle(
|
||||
arg.Style{
|
||||
Start: "<comment>",
|
||||
End: "</comment>",
|
||||
},
|
||||
arg.Style{
|
||||
Start: "<info>",
|
||||
End: "</info>",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type txt struct{}
|
||||
|
||||
func (t *txt) Command(ctx context.Context, out output.Output, cmd Command) error {
|
||||
@@ -82,54 +92,6 @@ func (t *txt) Commands(ctx context.Context, out output.Output, cmds Commands) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func txtDefaultArray(val config.Value) string {
|
||||
var st any
|
||||
|
||||
err := val.Unmarshal(&st)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%v", st)
|
||||
}
|
||||
|
||||
//nolint:cyclop
|
||||
func txtDefault(val config.Value, vr config.Variable) []byte {
|
||||
var buf bytes.Buffer
|
||||
|
||||
buf.WriteString("<comment> [default: ")
|
||||
|
||||
dataType := param.Type(vr)
|
||||
if option.IsSlice(vr) {
|
||||
buf.WriteString(txtDefaultArray(val))
|
||||
} else {
|
||||
switch dataType.(type) {
|
||||
case int:
|
||||
buf.WriteString(strconv.Itoa(val.Int()))
|
||||
case int64:
|
||||
buf.WriteString(strconv.FormatInt(val.Int64(), 10))
|
||||
case uint:
|
||||
buf.WriteString(strconv.FormatUint(uint64(val.Uint()), 10))
|
||||
case uint64:
|
||||
buf.WriteString(strconv.FormatUint(val.Uint64(), 10))
|
||||
case float64:
|
||||
buf.WriteString(strconv.FormatFloat(val.Float64(), 'g', -1, 64))
|
||||
case time.Duration:
|
||||
buf.WriteString(val.Duration().String())
|
||||
case time.Time:
|
||||
buf.WriteString(val.Time().Format(time.RFC3339))
|
||||
case string:
|
||||
buf.WriteString(val.String())
|
||||
default:
|
||||
buf.WriteString(fmt.Sprint(val.Any()))
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString("]</comment>")
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func txtCommands(cmds []NSCommand) string {
|
||||
width := commandsTotalWidth(cmds)
|
||||
showNS := len(cmds) > 1
|
||||
@@ -181,95 +143,20 @@ func txtHelp(cmd Command) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func txtDefinitionOption(maxLen int, opts ...config.Variable) string {
|
||||
buf := bytes.Buffer{}
|
||||
buf.WriteString("\n\n<comment>Options:</comment>\n")
|
||||
|
||||
for _, opt := range opts {
|
||||
if option.IsHidden(opt) {
|
||||
continue
|
||||
}
|
||||
|
||||
var op bytes.Buffer
|
||||
|
||||
op.WriteString(" <info>")
|
||||
|
||||
if short, ok := option.ParamShort(opt); ok {
|
||||
op.WriteString("-")
|
||||
op.WriteString(short)
|
||||
op.WriteString(", ")
|
||||
} else {
|
||||
op.WriteString(" ")
|
||||
}
|
||||
|
||||
op.WriteString("--")
|
||||
op.WriteString(strings.Join(opt.Key(), dashDelimiter))
|
||||
|
||||
if !option.IsBool(opt) {
|
||||
if !option.IsRequired(opt) {
|
||||
op.WriteString("[")
|
||||
}
|
||||
|
||||
op.WriteString("=")
|
||||
op.WriteString(strings.ToUpper(strings.Join(opt.Key(), dashDelimiter)))
|
||||
|
||||
if !option.IsRequired(opt) {
|
||||
op.WriteString("]")
|
||||
}
|
||||
}
|
||||
|
||||
op.WriteString("</info>")
|
||||
buf.Write(op.Bytes())
|
||||
buf.WriteString(strings.Repeat(" ", maxLen+17-op.Len()))
|
||||
buf.WriteString(option.DataDescription(opt))
|
||||
|
||||
if data, ok := option.DataDefaut(opt); ok {
|
||||
buf.Write(txtDefault(value.New(data), opt))
|
||||
}
|
||||
|
||||
if option.IsSlice(opt) {
|
||||
buf.WriteString("<comment> (multiple values allowed)</comment>")
|
||||
}
|
||||
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func txtDefinition(def Definition) string {
|
||||
width := totalWidth(def)
|
||||
|
||||
func txtDefinition(options config.Options) string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if args := def.Arguments(); len(args) > 0 {
|
||||
buf.WriteString("\n\n<comment>Arguments:</comment>\n")
|
||||
|
||||
for _, arg := range args {
|
||||
var ab bytes.Buffer
|
||||
|
||||
ab.WriteString(" <info>")
|
||||
ab.WriteString(strings.Join(arg.Key(), dashDelimiter))
|
||||
ab.WriteString("</info>")
|
||||
ab.WriteString(strings.Repeat(" ", width+infoLen+defaultSpace-ab.Len()))
|
||||
|
||||
buf.Write(ab.Bytes())
|
||||
buf.WriteString(option.DataDescription(arg))
|
||||
|
||||
if data, ok := option.DataDefaut(arg); ok {
|
||||
buf.Write(txtDefault(value.New(data), arg))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts := def.Options(); len(opts) > 0 {
|
||||
buf.WriteString(txtDefinitionOption(width, opts...))
|
||||
err := arg.NewDump().Reference(&buf, options)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func txtSynopsis(def Definition) string {
|
||||
func txtSynopsis(options config.Options) string {
|
||||
def := arg.NewViews(options, nil)
|
||||
|
||||
var buf bytes.Buffer
|
||||
|
||||
if len(def.Options()) > 0 {
|
||||
@@ -294,7 +181,7 @@ func txtSynopsis(def Definition) string {
|
||||
}
|
||||
|
||||
buf.WriteString("<")
|
||||
buf.WriteString(strings.Join(arg.Key(), dashDelimiter))
|
||||
buf.WriteString(arg.Name(dashDelimiter))
|
||||
buf.WriteString(">")
|
||||
|
||||
if option.IsSlice(arg) {
|
||||
@@ -320,32 +207,3 @@ func commandsTotalWidth(cmds []NSCommand) int {
|
||||
|
||||
return width
|
||||
}
|
||||
|
||||
//nolint:mnd
|
||||
func totalWidth(def Definition) int {
|
||||
var width int
|
||||
|
||||
for _, arg := range def.Arguments() {
|
||||
if l := len(strings.Join(arg.Key(), dashDelimiter)); l > width {
|
||||
width = l
|
||||
}
|
||||
}
|
||||
|
||||
for _, opt := range def.Options() {
|
||||
current := len(strings.Join(opt.Key(), dashDelimiter)) + 6
|
||||
|
||||
if !option.IsBool(opt) {
|
||||
current = current*2 + 1
|
||||
}
|
||||
|
||||
if _, ok := option.DataDefaut(opt); ok {
|
||||
current += 2
|
||||
}
|
||||
|
||||
if current > width {
|
||||
width = current
|
||||
}
|
||||
}
|
||||
|
||||
return width
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user