|
25 | 25 | )
|
26 | 26 | from .command_definition import CommandSet
|
27 | 27 | from .table_creator import Column, SimpleTable
|
28 |
| -from .utils import CompletionError, basic_complete |
| 28 | +from .utils import CompletionError, basic_complete, get_defining_class |
29 | 29 |
|
30 | 30 | # If no descriptive header is supplied, then this will be used instead
|
31 | 31 | DEFAULT_DESCRIPTIVE_HEADER = 'Description'
|
@@ -569,12 +569,43 @@ def _complete_for_arg(self, arg_action: argparse.Action,
|
569 | 569 | kwargs = {}
|
570 | 570 | if isinstance(arg_choices, ChoicesCallable):
|
571 | 571 | if arg_choices.is_method:
|
572 |
| - cmd_set = getattr(self._parser, constants.PARSER_ATTR_COMMANDSET, cmd_set) |
573 |
| - if cmd_set is not None: |
574 |
| - if isinstance(cmd_set, CommandSet): |
575 |
| - # If command is part of a CommandSet, `self` should be the CommandSet and Cmd will be next |
576 |
| - if cmd_set is not None: |
577 |
| - args.append(cmd_set) |
| 572 | + # figure out what class the completer was defined in |
| 573 | + completer_class = get_defining_class(arg_choices.to_call) |
| 574 | + |
| 575 | + # Was there a defining class identified? If so, is it a sub-class of CommandSet? |
| 576 | + if completer_class is not None and issubclass(completer_class, CommandSet): |
| 577 | + # Since the completer function is provided as an unbound function, we need to locate the instance |
| 578 | + # of the CommandSet to pass in as `self` to emulate a bound method call. |
| 579 | + # We're searching for candidates that match the completer function's parent type in this order: |
| 580 | + # 1. Does the CommandSet registered with the command's argparser match as a subclass? |
| 581 | + # 2. Do any of the registered CommandSets in the Cmd2 application exactly match the type? |
| 582 | + # 3. Is there a registered CommandSet that is is the only matching subclass? |
| 583 | + |
| 584 | + # Now get the CommandSet associated with the current command/subcommand argparser |
| 585 | + parser_cmd_set = getattr(self._parser, constants.PARSER_ATTR_COMMANDSET, cmd_set) |
| 586 | + if isinstance(parser_cmd_set, completer_class): |
| 587 | + # Case 1: Parser's CommandSet is a sub-class of the completer function's CommandSet |
| 588 | + cmd_set = parser_cmd_set |
| 589 | + else: |
| 590 | + # Search all registered CommandSets |
| 591 | + cmd_set = None |
| 592 | + candidate_sets = [] # type: List[CommandSet] |
| 593 | + for installed_cmd_set in self._cmd2_app._installed_command_sets: |
| 594 | + if type(installed_cmd_set) == completer_class: |
| 595 | + # Case 2: CommandSet is an exact type match for the completer's CommandSet |
| 596 | + cmd_set = installed_cmd_set |
| 597 | + break |
| 598 | + |
| 599 | + # Add candidate for Case 3: |
| 600 | + if isinstance(installed_cmd_set, completer_class): |
| 601 | + candidate_sets.append(installed_cmd_set) |
| 602 | + if cmd_set is None and len(candidate_sets) == 1: |
| 603 | + # Case 3: There exists exactly 1 CommandSet that is a subclass of the completer's CommandSet |
| 604 | + cmd_set = candidate_sets[0] |
| 605 | + if cmd_set is None: |
| 606 | + # No cases matched, raise an error |
| 607 | + raise CompletionError('Could not find CommandSet instance matching defining type for completer') |
| 608 | + args.append(cmd_set) |
578 | 609 | args.append(self._cmd2_app)
|
579 | 610 |
|
580 | 611 | # Check if arg_choices.to_call expects arg_tokens
|
|
0 commit comments