Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 32 additions & 20 deletions shtab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"directory": {"bash": "_shtab_compgen_dirs", "zsh": "_files -/", "tcsh": "d"}}
FILE = CHOICE_FUNCTIONS["file"]
DIRECTORY = DIR = CHOICE_FUNCTIONS["directory"]
DEFAULT_FUNCTIONS = {"bash": FILE["bash"], "zsh": "_default", "tcsh": FILE["tcsh"]}
FLAG_OPTION = (
_StoreConstAction,
_HelpAction,
Expand Down Expand Up @@ -134,7 +135,7 @@ def get_public_subcommands(sub):
return {k for k, v in sub.choices.items() if id(v) in public_parsers}


def get_bash_commands(root_parser, root_prefix, choice_functions=None):
def get_bash_commands(root_parser, root_prefix, default_complete, choice_functions=None):
"""
Recursive subcommand parser traversal, returning lists of information on
commands (formatted for output to the completions script).
Expand Down Expand Up @@ -179,10 +180,11 @@ def recurse(parser, prefix):
if positional.help == SUPPRESS:
continue

if hasattr(positional, "complete"):
positional_complete = getattr(positional, "complete", {"bash": default_complete})
if positional_complete:
# shtab `.complete = ...` functions
compgens.append(u"{}_pos_{}_COMPGEN={}".format(
prefix, i, complete2pattern(positional.complete, "bash", choice_type2fn)))
prefix, i, complete2pattern(positional_complete, "bash", choice_type2fn)))

if positional.choices:
# choices (including subparsers & shtab `.complete` functions)
Expand Down Expand Up @@ -287,15 +289,16 @@ def recurse(parser, prefix):


@mark_completer("bash")
def complete_bash(parser, root_prefix=None, preamble="", choice_functions=None):
def complete_bash(parser, root_prefix=None, preamble="", default_complete="",
choice_functions=None):
"""
Returns bash syntax autocompletion script.

See `complete` for arguments.
"""
root_prefix = wordify("_shtab_" + (root_prefix or parser.prog))
subparsers, option_strings, compgens, choices, nargs = get_bash_commands(
parser, root_prefix, choice_functions=choice_functions)
parser, root_prefix, default_complete, choice_functions=choice_functions)

# References:
# - https://www.gnu.org/software/bash/manual/html_node/
Expand Down Expand Up @@ -448,7 +451,8 @@ def escape_zsh(string):


@mark_completer("zsh")
def complete_zsh(parser, root_prefix=None, preamble="", choice_functions=None):
def complete_zsh(parser, root_prefix=None, preamble="", default_complete="",
choice_functions=None):
"""
Returns zsh syntax autocompletion script.

Expand Down Expand Up @@ -484,7 +488,7 @@ def format_positional(opt):
pattern=complete2pattern(opt.complete, "zsh", choice_type2fn) if hasattr(
opt, "complete") else
(choice_type2fn[opt.choices[0].type] if isinstance(opt.choices[0], Choice) else
"({})".format(" ".join(map(str, opt.choices)))) if opt.choices else "",
"({})".format(" ".join(map(str, opt.choices)))) if opt.choices else default_complete,
)

# {cmd: {"help": help, "arguments": [arguments]}}
Expand Down Expand Up @@ -634,7 +638,8 @@ def command_list(prefix, options):


@mark_completer("tcsh")
def complete_tcsh(parser, root_prefix=None, preamble="", choice_functions=None):
def complete_tcsh(parser, root_prefix=None, preamble="", default_complete="",
choice_functions=None):
"""
Return tcsh syntax autocompletion script.

Expand All @@ -657,14 +662,12 @@ def get_specials(arg, arg_type, arg_sel):
arg_sel,
choice_strs,
)
elif hasattr(arg, "complete"):
complete_fn = complete2pattern(arg.complete, 'tcsh', choice_type2fn)
if complete_fn:
yield "'{}/{}/{}/'".format(
arg_type,
arg_sel,
complete_fn,
)
else:
arg_complete = getattr(arg, "complete", default_complete)
if arg_complete:
complete_fn = complete2pattern(arg_complete, 'tcsh', choice_type2fn)
if complete_fn:
yield "'{}/{}/{}/'".format(arg_type, arg_sel, complete_fn)

def recurse_parser(cparser, positional_idx, requirements=None):
log_prefix = '| ' * positional_idx
Expand Down Expand Up @@ -737,7 +740,8 @@ def recurse_parser(cparser, positional_idx, requirements=None):


def complete(parser: ArgumentParser, shell: str = "bash", root_prefix: Opt[str] = None,
preamble: Union[str, Dict] = "", choice_functions: Opt[Any] = None) -> str:
preamble: Union[str, Dict] = "", default_complete: Union[str, Dict] = "",
choice_functions: Opt[Any] = None) -> str:
"""
parser : argparse.ArgumentParser
shell : str (bash/zsh)
Expand All @@ -746,26 +750,33 @@ def complete(parser: ArgumentParser, shell: str = "bash", root_prefix: Opt[str]
preamble : dict or str
mapping shell to text to prepend to generated script
(e.g. `{"bash": "_myprog_custom_function(){ echo hello }"}`)
default_complete : dict or str
mapping shell to text to fallback on when positional `.complete` is undefined
choice_functions : deprecated

N.B. `parser.add_argument().complete = ...` can be used to define custom
completions (e.g. filenames). See <../examples/pathcomplete.py>.
"""
if isinstance(preamble, dict):
preamble = preamble.get(shell, "")
if isinstance(default_complete, dict):
default_complete = default_complete.get(shell, "")
completer = get_completer(shell)
return completer(
parser,
root_prefix=root_prefix,
preamble=preamble,
default_complete=default_complete,
choice_functions=choice_functions,
)


def completion_action(parent=None, preamble=""):
def completion_action(parent=None, preamble="", default_complete=""):
class PrintCompletionAction(Action):
def __call__(self, parser, namespace, values, option_string=None):
print(complete(parent or parser, values, preamble=preamble))
print(
complete(parent or parser, values, preamble=preamble,
default_complete=default_complete))
parser.exit(0)

return PrintCompletionAction
Expand All @@ -777,6 +788,7 @@ def add_argument_to(
help="print shell completion script",
parent=None,
preamble="",
default_complete="",
):
"""
parser : argparse.ArgumentParser
Expand All @@ -794,7 +806,7 @@ def add_argument_to(
option_string = [option_string]
kwargs = {
"choices": SUPPORTED_SHELLS, "default": None, "help": help,
"action": completion_action(parent, preamble)}
"action": completion_action(parent, preamble, default_complete)}
if option_string[0][0] != "-": # subparser mode
kwargs.update(default=SUPPORTED_SHELLS[0], nargs="?")
assert parent is not None, "subcommand mode: parent required"
Expand Down