diff --git a/app.go b/app.go index 0a1a3f5..99904b2 100644 --- a/app.go +++ b/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, "\n\n ", err, "\n") } + +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 +} diff --git a/app_test.go b/app_test.go index 4b8e2d5..04f841b 100644 --- a/app_test.go +++ b/app_test.go @@ -30,21 +30,21 @@ func ExampleNew_help() { // test:command [options] [--] [] // // Arguments: - // test_argument test argument + // test_argument test argument // // Options: - // --string[=STRING] array string (multiple values allowed) - // --group-bool bool - // --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] - // --ansi Do not ask any interactive question - // -V, --version Display this application version - // -h, --help Display this help message - // -v, --verbose Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace (multiple values allowed) - // -q, --quiet Do not output any message - // --no-ansi Disable ANSI output + // --string[=STRING] array string (multiple values allowed) + // --group-bool bool + // --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 + // --ansi Do not ask any interactive question + // -V, --version Display this application version + // -h, --help Display this help message + // -v, --verbose Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace (multiple values allowed) + // -q, --quiet Do not output any message + // --no-ansi Disable ANSI output } func ExampleNew_list() { @@ -81,7 +81,7 @@ func ExampleNew_list() { // --ansi Do not ask any interactive question // -V, --version Display this application version // -h, --help Display this help message - // -v, --verbose Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace (multiple values allowed) + // -v, --verbose Increase the verbosity of messages: -v for info output, -vv for debug and -vvv for trace (multiple values allowed) // -q, --quiet Do not output any message // // Available commands: diff --git a/go.mod b/go.mod index d4dee3a..4108a0b 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 8514eac..28f9a61 100644 --- a/go.sum +++ b/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= diff --git a/help.go b/help.go index 4eaea7f..172874a 100644 --- a/help.go +++ b/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 list 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) diff --git a/list.go b/list.go index 56b14c7..8a6b6ac 100644 --- a/list.go +++ b/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" @@ -71,8 +72,8 @@ 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()), + Namespace: ns, + Options: definition.New(Default()...).With(param.New(descriptor.TxtStyle())), } groups := make(map[string]*descriptor.NSCommand) namespaces := make([]string, 0, len(cmds)) diff --git a/output/descriptor/descriptor.go b/output/descriptor/descriptor.go index 174623e..405afb1 100644 --- a/output/descriptor/descriptor.go +++ b/output/descriptor/descriptor.go @@ -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,17 +20,19 @@ var ( ) type Command struct { + config.Options + Bin string Name string Description string Help string - Definition Definition } type Commands struct { - Namespace string - Definition Definition - Commands []NSCommand + config.Options + + Namespace string + Commands []NSCommand } type NSCommand struct { @@ -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 diff --git a/output/descriptor/txt.go b/output/descriptor/txt.go index d09396b..c6b4dc4 100644 --- a/output/descriptor/txt.go +++ b/output/descriptor/txt.go @@ -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 -}} Usage: - {{ .Name }} {{ synopsis .Definition }} -{{- definition .Definition }} + {{ .Name }} {{ synopsis .Options }} +{{ definition .Options }} {{- help . }} `)) @@ -49,11 +46,24 @@ var ( Funcs(txtFunc). Parse(`Usage: command [options] [arguments] -{{- definition .Definition }} +{{ definition .Options }} {{- commands .Commands -}} `)) ) +func TxtStyle() param.Option { + return arg.WithStyle( + arg.Style{ + Start: "", + End: "", + }, + arg.Style{ + Start: "", + End: "", + }, + ) +} + 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(" [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("]") - - 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\nOptions:\n") - - for _, opt := range opts { - if option.IsHidden(opt) { - continue - } - - var op bytes.Buffer - - op.WriteString(" ") - - 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("") - 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(" (multiple values allowed)") - } - - 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\nArguments:\n") - - for _, arg := range args { - var ab bytes.Buffer - - ab.WriteString(" ") - ab.WriteString(strings.Join(arg.Key(), dashDelimiter)) - ab.WriteString("") - 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 -}