Skip to content

Commit 4fac7e1

Browse files
committed
Determine whether to add a subparser to completion by its presence in _get_subactions()
This is the logic argparse uses for showing a subparser in the list of options, so shtab now matches that behavior. This is particularly important for subparsers which pass `add_help=False` in order to provide their *own* help implementation.
1 parent 76c5ead commit 4fac7e1

File tree

2 files changed

+43
-4
lines changed

2 files changed

+43
-4
lines changed

shtab/__init__.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ def wordify(string):
141141
return string.replace("-", "_").replace(".", " ").replace(" ", "_")
142142

143143

144+
def get_public_subcommands(sub):
145+
"""Get all the publicly-visible subcommands for a given subparser."""
146+
# NOTE: public subcommands have their primary name listed in the result of
147+
# `_get_subactions()`. We use this to get the parser for each subcommand and
148+
# compare all the choices (including aliases!) to the set of known-public
149+
# parsers.
150+
public_parsers = {id(sub.choices[i.dest]) for i in sub._get_subactions()}
151+
return {k for k, v in sub.choices.items() if id(v) in public_parsers}
152+
153+
144154
def get_bash_commands(root_parser, root_prefix, choice_functions=None):
145155
"""
146156
Recursive subcommand parser traversal, returning lists of information on
@@ -204,6 +214,9 @@ def recurse(parser, prefix):
204214
# choices (including subparsers & shtab `.complete` functions)
205215
log.debug("choices:{}:{}".format(prefix, sorted(positional.choices)))
206216

217+
if isinstance(positional.choices, dict):
218+
public_cmds = get_public_subcommands(positional)
219+
207220
this_positional_choices = []
208221
for choice in positional.choices:
209222
if isinstance(choice, Choice):
@@ -222,7 +235,7 @@ def recurse(parser, prefix):
222235
elif isinstance(positional.choices, dict):
223236
# subparser, so append to list of subparsers & recurse
224237
log.debug("subcommand:%s", choice)
225-
if positional.choices[choice].add_help:
238+
if choice in public_cmds:
226239
discovered_subparsers.append(str(choice))
227240
this_positional_choices.append(str(choice))
228241
(
@@ -577,8 +590,9 @@ def format_positional(opt):
577590
root_arguments.append(format_positional(opt))
578591
else: # subparser
579592
log.debug("choices:{}:{}".format(root_prefix, sorted(sub.choices)))
593+
public_cmds = get_public_subcommands(sub)
580594
for cmd, subparser in sub.choices.items():
581-
if not subparser.add_help:
595+
if cmd not in public_cmds:
582596
log.debug("skip:subcommand:%s", cmd)
583597
continue
584598
log.debug("subcommand:%s", cmd)

tests/test_shtab.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def test_custom_complete(shell, caplog):
177177
def test_subparser_custom_complete(shell, caplog):
178178
parser = ArgumentParser(prog="test")
179179
subparsers = parser.add_subparsers()
180-
sub = subparsers.add_parser("sub")
180+
sub = subparsers.add_parser("sub", help="help message")
181181
sub.add_argument("posA").complete = {"bash": "_shtab_test_some_func"}
182182
preamble = {"bash": "_shtab_test_some_func() { compgen -W 'one two' -- $1 ;}"}
183183
with caplog.at_level(logging.INFO):
@@ -194,6 +194,31 @@ def test_subparser_custom_complete(shell, caplog):
194194
assert not caplog.record_tuples
195195

196196

197+
@fix_shell
198+
def test_subparser_aliases(shell, caplog):
199+
parser = ArgumentParser(prog="test")
200+
subparsers = parser.add_subparsers()
201+
sub = subparsers.add_parser("sub", aliases=["xsub", "ysub"], help="help message")
202+
sub.add_argument("posA").complete = {"bash": "_shtab_test_some_func"}
203+
preamble = {"bash": "_shtab_test_some_func() { compgen -W 'one two' -- $1 ;}"}
204+
with caplog.at_level(logging.INFO):
205+
completion = shtab.complete(parser, shell=shell, preamble=preamble)
206+
print(completion)
207+
208+
if shell == "bash":
209+
shell = Bash(completion)
210+
shell.compgen('-W "${_shtab_test_subparsers[*]}"', "s", "sub")
211+
shell.compgen('-W "$_shtab_test_pos_0_choices"', "s", "sub")
212+
shell.compgen('-W "${_shtab_test_subparsers[*]}"', "x", "xsub")
213+
shell.compgen('-W "$_shtab_test_pos_0_choices"', "x", "xsub")
214+
shell.compgen('-W "${_shtab_test_subparsers[*]}"', "y", "ysub")
215+
shell.compgen('-W "$_shtab_test_pos_0_choices"', "y", "ysub")
216+
shell.test('"$($_shtab_test_sub_pos_0_COMPGEN o)" = "one"')
217+
shell.test('-z "$_shtab_test_COMPGEN"')
218+
219+
assert not caplog.record_tuples
220+
221+
197222
@fix_shell
198223
def test_add_argument_to_optional(shell, caplog):
199224
parser = ArgumentParser(prog="test")
@@ -213,7 +238,7 @@ def test_add_argument_to_optional(shell, caplog):
213238
def test_add_argument_to_positional(shell, caplog, capsys):
214239
parser = ArgumentParser(prog="test")
215240
subparsers = parser.add_subparsers()
216-
sub = subparsers.add_parser("completion")
241+
sub = subparsers.add_parser("completion", help="help message")
217242
shtab.add_argument_to(sub, "shell", parent=parser)
218243
from argparse import Namespace
219244

0 commit comments

Comments
 (0)