cleo icon indicating copy to clipboard operation
cleo copied to clipboard

Accept unknown options (for poetry plugin)

Open nat-n opened this issue 4 years ago • 2 comments

I require a way to make a cleo Command accept unknown options and arguments, and not inherit the application options.

The equivalent in argparse would be something like:

parser.add_argument("command_args", nargs=argparse.REMAINDER)

however I need to use cleo because I'm creating a poetry plugin for PoeThePoet which allows users to pass unknown arguments to a task e.g.

poe test <any args passed here will be forwarded to pytest>

It looks like this is currently not supported though I'd be happy to be proven wrong on that.

I'm willing to do the leg work to add support for this, since the only alternatives for integrating with poetry are either to drop key features when used as a plugin or to monkey patch Cleo at runtime 😞.

Workaround

I've found the following workaround that seems to produce the desired result (for now) by monkey patching cleo:

def monkey_patch_cleo(command: str):
    """Monkey patch cleo, e.g. while activating the poetry plugin"""
    import cleo.application

    continue_run = cleo.application.Application._run

    def _run(self, io):
        # Trick cleo to ignoring anything following the command from this plugin 
        # by injecting a '--' token at the start of the list of command line tokens
        tokens = io.input._tokens
        if tokens and tokens[0] == command:
            # update tokens list in place
            tokens.insert(0, "--")

        continue_run(self, io)

    # Apply the patch
    cleo.application.Application._run = _run


class MyCommand(Command):
    name = "my-command"
    
    def __init__(self):
        super().__init__()
        self._ignore_validation_errors = True # Don't fail given unknown arguments

    def handle(self):
        args = self.io.input._tokens[:]
        if args[0] == "--": # ignore the extra token introduced by the monkey patch
            args = tokenized_input[1:]
         ...

Proposal

A better solution would involve being able to configure the Command to have freeform arguments, e.g.

class MyCommand(Command):
    name = "my-command"
    parse_options = False

Which carries over to resulting Definition object which in turn causes the ArgvInput instance to skip parsing of options. Parsing of positional arguments could still work the same I think.

nat-n avatar Dec 23 '21 19:12 nat-n

Hi @Secrus, nice to see this is getting picked up :)

Here's a reference to how the poetry plugin for Poe the Poet currently deal's with cleo: https://github.com/nat-n/poethepoet/blob/0133c427523d3967c87475f2e220740f24797e3d/poethepoet/plugin.py#L238 And the relevant user facing docs: https://poethepoet.natn.io/poetry_plugin.html

nat-n avatar Jan 08 '25 20:01 nat-n

@nat-n hi. The plan is for the new parser to expose "not consumed"/"remaining" args so that they can be passed down through the stack. It is similar to how optparse works in that matter.

Secrus avatar Jan 08 '25 21:01 Secrus