feat(bootstrap): add command alias expansion
This commit is contained in:
parent
1b603f552c
commit
8c4f88ea93
2 changed files with 139 additions and 1 deletions
|
|
@ -47,6 +47,7 @@ type Options struct {
|
||||||
BinaryName string
|
BinaryName string
|
||||||
Description string
|
Description string
|
||||||
Version string
|
Version string
|
||||||
|
Aliases map[string][]string
|
||||||
Args []string
|
Args []string
|
||||||
Stdin io.Reader
|
Stdin io.Reader
|
||||||
Stdout io.Writer
|
Stdout io.Writer
|
||||||
|
|
@ -113,7 +114,7 @@ func Run(ctx context.Context, opts Options) error {
|
||||||
return ErrBinaryNameRequired
|
return ErrBinaryNameRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
command, commandArgs, showHelp := parseArgs(normalized.Args)
|
command, commandArgs, showHelp := parseArgs(expandAliases(normalized.Args, normalized.Aliases))
|
||||||
if showHelp {
|
if showHelp {
|
||||||
return printHelp(normalized, command, commandArgs)
|
return printHelp(normalized, command, commandArgs)
|
||||||
}
|
}
|
||||||
|
|
@ -168,6 +169,7 @@ func normalize(opts Options) Options {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseArgs(args []string) (command string, commandArgs []string, showHelp bool) {
|
func parseArgs(args []string) (command string, commandArgs []string, showHelp bool) {
|
||||||
|
args = trimArgs(args)
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return "", nil, false
|
return "", nil, false
|
||||||
}
|
}
|
||||||
|
|
@ -193,6 +195,63 @@ func parseArgs(args []string) (command string, commandArgs []string, showHelp bo
|
||||||
return command, commandArgs, false
|
return command, commandArgs, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expandAliases(args []string, aliases map[string][]string) []string {
|
||||||
|
args = trimArgs(args)
|
||||||
|
if len(args) == 0 || len(aliases) == 0 {
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
if args[0] == "help" && len(args) > 1 {
|
||||||
|
expanded, ok := resolveAlias(aliases, args[1])
|
||||||
|
if !ok {
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
withHelp := make([]string, 0, 1+len(expanded)+len(args[2:]))
|
||||||
|
withHelp = append(withHelp, "help")
|
||||||
|
withHelp = append(withHelp, expanded...)
|
||||||
|
withHelp = append(withHelp, args[2:]...)
|
||||||
|
return withHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
expanded, ok := resolveAlias(aliases, args[0])
|
||||||
|
if !ok {
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
withCommand := make([]string, 0, len(expanded)+len(args[1:]))
|
||||||
|
withCommand = append(withCommand, expanded...)
|
||||||
|
withCommand = append(withCommand, args[1:]...)
|
||||||
|
|
||||||
|
if len(withCommand) == 0 {
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
last := strings.TrimSpace(withCommand[len(withCommand)-1])
|
||||||
|
if last != "-h" && last != "--help" {
|
||||||
|
return withCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
helpArgs := make([]string, 0, len(withCommand))
|
||||||
|
helpArgs = append(helpArgs, "help")
|
||||||
|
helpArgs = append(helpArgs, withCommand[:len(withCommand)-1]...)
|
||||||
|
return helpArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveAlias(aliases map[string][]string, command string) ([]string, bool) {
|
||||||
|
target, ok := aliases[strings.TrimSpace(command)]
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
expanded := trimArgs(target)
|
||||||
|
if len(expanded) == 0 {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return expanded, true
|
||||||
|
}
|
||||||
|
|
||||||
func trimArgs(args []string) []string {
|
func trimArgs(args []string) []string {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -319,3 +319,82 @@ func TestRunConfigReturnsUnknownSubcommand(t *testing.T) {
|
||||||
t.Fatalf("Run error = %v, want ErrUnknownSubcommand", err)
|
t.Fatalf("Run error = %v, want ErrUnknownSubcommand", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunRoutesAliasToConfigSubcommandHook(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
var got Invocation
|
||||||
|
|
||||||
|
err := Run(context.Background(), Options{
|
||||||
|
BinaryName: "my-mcp",
|
||||||
|
Aliases: map[string][]string{
|
||||||
|
"doctor": {CommandConfig, ConfigSubcommandTest},
|
||||||
|
},
|
||||||
|
Args: []string{"doctor", "--profile", "work"},
|
||||||
|
Stdout: &stdout,
|
||||||
|
Stderr: &stderr,
|
||||||
|
Hooks: Hooks{
|
||||||
|
ConfigTest: func(_ context.Context, inv Invocation) error {
|
||||||
|
got = inv
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got.Command != "config test" {
|
||||||
|
t.Fatalf("invocation command = %q, want %q", got.Command, "config test")
|
||||||
|
}
|
||||||
|
wantArgs := []string{"--profile", "work"}
|
||||||
|
if !slices.Equal(got.Args, wantArgs) {
|
||||||
|
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunPrintsAliasHelpFromHelpCommand(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
err := Run(context.Background(), Options{
|
||||||
|
BinaryName: "my-mcp",
|
||||||
|
Aliases: map[string][]string{
|
||||||
|
"doctor": {CommandConfig, ConfigSubcommandTest},
|
||||||
|
},
|
||||||
|
Args: []string{"help", "doctor"},
|
||||||
|
Stdout: &stdout,
|
||||||
|
Stderr: &stderr,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := stdout.String()
|
||||||
|
if !strings.Contains(text, "my-mcp config test [args]") {
|
||||||
|
t.Fatalf("command help output = %q", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunPrintsAliasHelpFromTrailingHelpFlag(t *testing.T) {
|
||||||
|
var stdout bytes.Buffer
|
||||||
|
var stderr bytes.Buffer
|
||||||
|
|
||||||
|
err := Run(context.Background(), Options{
|
||||||
|
BinaryName: "my-mcp",
|
||||||
|
Aliases: map[string][]string{
|
||||||
|
"doctor": {CommandConfig, ConfigSubcommandTest},
|
||||||
|
},
|
||||||
|
Args: []string{"doctor", "--help"},
|
||||||
|
Stdout: &stdout,
|
||||||
|
Stderr: &stderr,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Run error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := stdout.String()
|
||||||
|
if !strings.Contains(text, "my-mcp config test [args]") {
|
||||||
|
t.Fatalf("command help output = %q", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue