diff options
| author | Jeff Carr <[email protected]> | 2025-09-24 22:17:40 -0500 | 
|---|---|---|
| committer | Jeff Carr <[email protected]> | 2025-09-24 22:17:40 -0500 | 
| commit | 9f4fc6ecddd88c9d2d84bc1960f1b78ea56cc85c (patch) | |
| tree | d0371789aa93c27e550d764317661894d24ef29e | |
| parent | 8d5a5220c9b2dfc7c6c1e69d1eaed7bb41079bf5 (diff) | |
start autocomplete support
| -rw-r--r-- | complete.go | 222 | 
1 files changed, 222 insertions, 0 deletions
diff --git a/complete.go b/complete.go new file mode 100644 index 0000000..72f964d --- /dev/null +++ b/complete.go @@ -0,0 +1,222 @@ +package arg + +import ( +	"fmt" +	"io" +	"os" +	"strings" +) + +// has the variables for autocomplete +type Complete struct { +	Subcommand []string  // this will be sent from the shell autocomplete scheme +	Partial    string    // whatever the user has partially entered on the commandline +	Stderr     io.Writer // this is where Stderr +	Stdout     io.Writer // this is where Stdout +} + +// same as WriteHelpForSubcommand above, but can flip to STDERR and STDOUT +// most shell autocomplete behavior usually wants things that way +func (p *Parser) WriteHelpForAutocomplete(stderr io.Writer, stdout io.Writer, partial string, subcommand ...string) error { +	var automatch []string +	if stderr == nil { +		stderr = os.Stderr +	} +	cmd, err := p.lookupCommand(subcommand...) +	if err != nil { +		return err +	} + +	var positionals, longOptions, shortOptions, envOnlyOptions []*spec +	var hasVersionOption bool +	for _, spec := range cmd.specs { +		switch { +		case spec.positional: +			positionals = append(positionals, spec) +		case spec.long != "": +			longOptions = append(longOptions, spec) +			if spec.long == "version" { +				hasVersionOption = true +			} +		case spec.short != "": +			shortOptions = append(shortOptions, spec) +		case spec.short == "" && spec.long == "": +			envOnlyOptions = append(envOnlyOptions, spec) +		} +	} + +	// obtain a flattened list of options from all ancestors +	// also determine if any ancestor has a version option spec +	var globals []*spec +	ancestor := cmd.parent +	for ancestor != nil { +		for _, spec := range ancestor.specs { +			if spec.long == "version" { +				hasVersionOption = true +				break +			} +		} +		globals = append(globals, ancestor.specs...) +		ancestor = ancestor.parent +	} + +	if p.description != "" { +		fmt.Fprintln(stderr, p.description) +	} + +	if !hasVersionOption && p.version != "" { +		fmt.Fprintln(stderr, p.version) +	} + +	p.WriteUsageForSubcommand(stderr, subcommand...) + +	// write the list of positionals +	if len(positionals) > 0 { +		fmt.Fprint(stderr, "\nPositional arguments:\n") +		for _, spec := range positionals { +			print(stderr, spec.placeholder, spec.help, withDefault(spec.defaultString), withEnv(spec.env)) +		} +	} + +	// write the list of options with the short-only ones first to match the usage string +	if len(shortOptions)+len(longOptions) > 0 || cmd.parent == nil { +		fmt.Fprint(stderr, "\nOptions:\n") +		for _, spec := range shortOptions { +			p.printOption(stderr, spec) +		} +		for _, spec := range longOptions { +			p.printOption(stderr, spec) +			//jwc +			if strings.HasPrefix(spec.long, partial) { +				automatch = append(automatch, "--"+spec.long) +			} +		} +	} + +	// write the list of global options +	if len(globals) > 0 { +		fmt.Fprint(stderr, "\nGlobal options:\n") +		for _, spec := range globals { +			p.printOption(stderr, spec) +		} +	} + +	// write the list of built in options +	p.printOption(stderr, &spec{ +		cardinality: zero, +		long:        "help", +		short:       "h", +		help:        "display this help and exit", +	}) +	if !hasVersionOption && p.version != "" { +		p.printOption(stderr, &spec{ +			cardinality: zero, +			long:        "version", +			help:        "display version and exit", +		}) +	} + +	// write the list of environment only variables +	if len(envOnlyOptions) > 0 { +		fmt.Fprint(stderr, "\nEnvironment variables:\n") +		for _, spec := range envOnlyOptions { +			p.printEnvOnlyVar(stderr, spec) +		} +	} + +	// write the list of subcommands +	if len(cmd.subcommands) > 0 { +		fmt.Fprint(stderr, "\nCommands:\n") +		for _, subcmd := range cmd.subcommands { +			names := append([]string{subcmd.name}, subcmd.aliases...) +			print(stderr, strings.Join(names, ", "), subcmd.help) +			if strings.HasPrefix(subcmd.name, partial) { +				automatch = append(automatch, subcmd.name) +			} +		} +	} + +	if p.epilogue != "" { +		fmt.Fprintln(stderr, "\n"+p.epilogue) +	} + +	if stdout == nil { +		fmt.Fprintf(os.Stdout, "err foo") +	} else { +		// writes out the shell autocomplete matches +		if len(automatch) > 0 { +			fmt.Fprintf(stdout, "%s", strings.Join(automatch, " ")) +		} +	} +	return nil +} + +// GetUsageForSubcommand gets the commands for bash shell completetion +func (p *Parser) GetUsageForSubcommand(stdout io.Writer, stderr io.Writer, partial string, s string) (string, error) { +	var log []string +	var final []string +	cmd, err := p.lookupCommand(s) +	if err != nil { +		return "", err +	} + +	var positionals, longOptions, shortOptions []*spec +	for _, spec := range cmd.specs { +		switch { +		case spec.positional: +			positionals = append(positionals, spec) +		case spec.long != "": +			longOptions = append(longOptions, spec) +		case spec.short != "": +			shortOptions = append(shortOptions, spec) +		} +	} + +	// write the option component of the usage message +	for _, spec := range shortOptions { +		// prefix with a space +		log = append(log, fmt.Sprintf(" ")) +		if !spec.required { +			// log = append(log, fmt.Sprintf("[")) +		} +		log = append(log, synopsis(spec, "-"+spec.short)) +		if !spec.required { +			// log = append(log, fmt.Sprintf("]")) +		} +	} + +	for _, spec := range longOptions { +		// prefix with a space +		if !spec.required { +			// log = append(log, fmt.Sprintf("[")) +		} +		log = append(log, synopsis(spec, "--"+spec.long)) +		if !spec.required { +			// log = append(log, fmt.Sprintf("]")) +		} +	} + +	for _, spec := range positionals { +		if !spec.required { +			// log = append(log, fmt.Sprintf("[")) +		} +		if spec.cardinality == multiple { +			log = append(log, fmt.Sprintf("%s [%s ...]", spec.placeholder, spec.placeholder)) +		} else { +			log = append(log, spec.placeholder) +		} +	} +	for _, subcmd := range cmd.subcommands { +		names := append([]string{subcmd.name}, subcmd.aliases...) +		if strings.HasPrefix(subcmd.name, partial) { +			final = append(final, subcmd.name) +		} +		log = append(log, strings.Join(names, ", "), subcmd.help) +		if stderr != nil { +			print(stderr, strings.Join(names, ", "), subcmd.help) +		} +	} +	fmt.Fprintf(stdout, "%s", strings.Join(final, " ")) + +	return strings.Join(final, " "), nil +}  | 
