Add base classes for multi-commands.

Review Request #11882 — Created Nov. 24, 2021 and submitted — Latest diff uploaded

Information

RBTools
master

Reviewers

We'll soon be adding two new commands, rbt api and rbt review, which
both want to implement additional subcommands (rbt api get,
rbt review publish, etc). This change adds the framework to do so,
consisting of two new base classes. SubCommand should be inherited for
each subcommand offered, and MultiCommand aggregates them together.
Arguments from the MultiCommand are added in to the subcommand
parsers.

The one tricky thing here was getting --help to do the right thing. In
the case of MultiCommands, we need to actually parse the arguments in
order to get the right subparser, but doing so was enforcing any usage
requirements. It turns out that the correct fix here was to allow
add_help to default to True in the parser initialization. This
allows us to call parse_args and have it not fail.

I did also have to move the logging setup out of the initialize
method, so that we wouldn't attach multiple log handlers.

Used this extensively with the upcoming review command.

Changes between revision 2 and 3

orig
1
2
3
4

Commits

Summary ID Author
Add base classes for multi-commands.
We'll soon be adding two new commands, `rbt api` and `rbt review`, which both want to implement additional subcommands (`rbt api get`, `rbt review publish`, etc). This change adds the framework to do so, consisting of two new base classes. `SubCommand` should be inherited for each subcommand offered, and `MultiCommand` aggregates them together. Arguments from the `MultiCommand` are added in to the subcommand parsers. The one tricky thing here was getting `--help` to do the right thing. In the case of `MultiCommand`s, we need to actually parse the arguments in order to get the right subparser, but doing so was enforcing any usage requirements. It turns out that the correct fix here was to allow `add_help` to default to `True` in the parser initialization. This allows us to call `parse_args` and have it not fail. I did also have to move the logging setup out of the `initialize` method, so that we wouldn't attach multiple log handlers. Testing Done: Used this extensively with the upcoming review command.
18a029ec319184099954b19bb70f435decd7cefc David Trowbridge
Add base classes for multi-commands.
We'll soon be adding two new commands, `rbt api` and `rbt review`, which both want to implement additional subcommands (`rbt api get`, `rbt review publish`, etc). This change adds the framework to do so, consisting of two new base classes. `SubCommand` should be inherited for each subcommand offered, and `MultiCommand` aggregates them together. Arguments from the `MultiCommand` are added in to the subcommand parsers. The one tricky thing here was getting `--help` to do the right thing. In the case of `MultiCommand`s, we need to actually parse the arguments in order to get the right subparser, but doing so was enforcing any usage requirements. It turns out that the correct fix here was to allow `add_help` to default to `True` in the parser initialization. This allows us to call `parse_args` and have it not fail. I did also have to move the logging setup out of the `initialize` method, so that we wouldn't attach multiple log handlers. Testing Done: Used this extensively with the upcoming review command.
1b235b2d7d82590fa3b80f309ab4dff59df1408a David Trowbridge

Files

rbtools/commands/__init__.py
rbtools/commands/__init__.py
Diff Revision 2 Diff Revision 3
266 lines
def add_to(self, parser, config={}, argv=[]):
267

    
   
267

   
268
            argv (list, deprecated):
268
            argv (list, deprecated):
269
                Unused legacy argument.
269
                Unused legacy argument.
270
        """
270
        """
271
        # First look for an existing group with the same name. This allows
271
        # First look for an existing group with the same name. This allows
272
        # a SubCommand to merge review groups with its parent MultiCommand.
272
        # a BaseSubCommand to merge review groups with its parent

    
   
273
        # BaseMultiCommand.
273
        for group in parser._action_groups:
274
        for group in parser._action_groups:
274
            if group.title == self.name:
275
            if group.title == self.name:
275
                break
276
                break
276
        else:
277
        else:
277
            group = parser.add_argument_group(self.name, self.description)
278
            group = parser.add_argument_group(self.name, self.description)
1177 lines
def __init__(self, options, config, *args, **kwargs):
1455
            The parsed options.
1456
            The parsed options.
1456

    
   
1457

   
1457
        config (dict):
1458
        config (dict):
1458
            The loaded RBTools configuration.
1459
            The loaded RBTools configuration.
1459
        """
1460
        """
1460
        super(SubCommand, self).__init__(*args, **kwargs)
1461
        super(BaseSubCommand, self).__init__(*args, **kwargs)
1461
        self.options = options
1462
        self.options = options
1462
        self.config = config
1463
        self.config = config
1463

    
   
1464

   
1464

    
   
1465

   
1465
class BaseMultiCommand(Command):
1466
class BaseMultiCommand(Command):
1466
    """Abstract base class for commands which offer subcommands.
1467
    """Abstract base class for commands which offer subcommands.
1467

    
   
1468

   
1468
    Some commands (such as :command:`rbt review`) want to offer many
1469
    Some commands (such as :command:`rbt review`) want to offer many
1469
    subcommands.
1470
    subcommands.
1470
    """
1471
    """
1471

    
   
1472

   
1472
    #: The available subcommands.
1473
    #: The available subcommands.
1473
    #:
1474
    #:
1474
    #: This is a list of SubCommand sub classes.
1475
    #: This is a list of BaseSubCommand sub classes.
1475
    #:
1476
    #:
1476
    #: Type:
1477
    #: Type:
1477
    #:     list
1478
    #:     list
1478
    subcommands = {}
1479
    subcommands = {}
1479

    
   
1480

   
73 lines
def create_parser(self, config, argv=[]):
1553

    
   
1554

   
1554
        return parser
1555
        return parser
1555

    
   
1556

   
1556
    def initialize(self):
1557
    def initialize(self):
1557
        """Initialize the command."""
1558
        """Initialize the command."""
1558
        super(MultiCommand, self).initialize()
1559
        super(BaseMultiCommand, self).initialize()
1559

    
   
1560

   
1560
        command = self.options.command_cls(options=self.options,
1561
        command = self.options.command_cls(options=self.options,
1561
                                           config=self.config,
1562
                                           config=self.config,
1562
                                           transport_cls=self.transport_cls)
1563
                                           transport_cls=self.transport_cls)
1563
        command.stdout = self.stdout
1564
        command.stdout = self.stdout
40 lines
Loading...