Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve args passing and DynamicSuggestionsFunc() #6

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

jippi
Copy link

@jippi jippi commented Feb 25, 2024

Args passing

Rather than manipulating os.Args (which has a lot of caveats) we configure Cobra to use a specific set of arguments for its parsing of the command being executed.

This should work with both existing apps, as well as apps that "wraps" Cobra so its more testable, and thus, calls root.SetArgs as part of their bootstrap.

I saw behavior where existing implementation when having this library as a prompt sub-command never running any of the commands from root despite discovering them and being able to "complete" them in the TUI.

Example

main.go

Only used for go run and go build

package main

func main() {
	ctx := tui.NewContext(context.Background(), os.Stdout, os.Stderr)

	_, err := cmd.RunCommand(ctx, os.Args[1:], os.Stdout, os.Stderr)
	if err != nil {
		os.Exit(1)
	}
}

root.go

Used in testing as well as the regular build (Called from main.go)

func RunCommand(ctx context.Context, args []string, stdout io.Writer, stderr io.Writer) (*cobra.Command, error) {
	root := &cobra.Command{
		Use:           "dottie",
		Short:         "Simplify working with .env files",
		SilenceErrors: true,
		SilenceUsage:  true,
		Version:       buildVersion().String(),
	}

	root.SetVersionTemplate(`{{ .Version }}`)

	root.SetArgs(args)
	root.SetContext(ctx)
	root.SetErr(stderr)
	root.SetOut(stdout)

        // root.AddCommend() x 10

        command, err := root.ExecuteContextC(ctx)
	if err != nil {
		stderr.Info().Printfln("Run '%v --help' for usage.", command.CommandPath())
	}

	return command, err
}

DynamicSuggestionsFunc()

Passing in the *cobra.Command to DynamicSuggestionsFunc() allow you to utilize the (*cobra.Command).ValidArgsFunction() function to generate the completions.

This allow you to provide a generic auto-completion logic based off-of ValidArgsFunction() like this

DynamicSuggestionsFunc: func(cmd *cobra.Command, _ string, _ *prompt.Document) []prompt.Suggest {
	suggestions := []prompt.Suggest{}

	if cmd.ValidArgsFunction == nil {
		return suggestions
	}

	// We do not have access to the "args" being completed, so we pass in an empty slice
	// which in many cases will trigger the "full" list of suggestions
	arguments, _ := cmd.ValidArgsFunction(cmd, []string{}, "")
	for _, name := range arguments {
		// ValidArgsFunction() returns "name\tdescription" pairs
		// so we split by "\t" to get the individual parts
		parts := strings.SplitN(name, "\t", 2)

		// Build the default suggest
		suggestion := prompt.Suggest{
			Text: parts[0],
		}

		// If the autocomplete has a description, add it to the suggest
		if len(parts) == 2 {
			suggestion.Description = parts[1]
		}

		suggestions = append(suggestions, suggestion)
	}

	return suggestions
}

Tests

Exciting tests were failing, so I made those pass as well.

Proper shell/args support using shellquote.Split

strings.Fields does not account for shell quoting/spacing so in some cases it would provide bad Args() to Cobra - like if you have a CLI argument that contains a space (e.g. get -n "John Oliver" food apple) it would instead only see "John as the CLI flag name, and Oliver" as a part of the argument list.

@jippi jippi changed the title Fix args passing Improve args passing and DynamicSuggestionsFunc() Feb 25, 2024
@jippi jippi marked this pull request as draft February 25, 2024 22:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant