zenstruck / console-extra

A modular set of features to reduce configuration boilerplate for your Symfony commands.
MIT License
78 stars 3 forks source link

Auto-complete / suggested values #38

Closed tacman closed 8 months ago

tacman commented 2 years ago

Is there a way to add suggested values (as an array or closure) to invokable commands? From the Symfony console documentation at https://symfony.com/doc/current/console/input.html#adding-argument-option-value-completion

// ...
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;

class GreetCommand extends Command
{
    // ...
    protected function configure(): void
    {
        $this
            ->addArgument(
                'names',
                InputArgument::IS_ARRAY,
                'Who do you want to greet (separate multiple names with a space)?',
                null,
                function (CompletionInput $input) {
                    // the value the user already typed, e.g. when typing "app:greet Fa" before
                    // pressing Tab, this will contain "Fa"
                    $currentValue = $input->getCompletionValue();

                    // get the list of username names from somewhere (e.g. the database)
                    // you may use $currentValue to filter down the names
                    $availableUsernames = ...;

                    // then suggested the usernames as values
                    return $availableUsernames;
                }
            )
        ;
    }
}

Is there a way to enhance the configuration? Maybe something like

$argument = $this->getArgument('names');
$argument->setSuggestedValues(function (...));

Obviously, I'm trying to see if it's possible to switch completely to invokable commands.

kbond commented 2 years ago

Good idea! I think the best to way would be to add a string|callable $suggestions arg to the Argument/Option attributes. The callable couldn't be a closure but could be a method in the class.

kbond commented 2 years ago

Thinking about the syntax for defining suggestions:

public function __invoke(
    // standard array
    #[Argument(suggestions: ['foo', 'bar', 'baz'])]
    string $username,

    // standard callable
    #[Argument(suggestions: [self::class, 'someStaticMethod'])]
    string $username,

    // doesn't work:
    #[Argument(suggestions: [$this, 'someInstanceMethod'])]
    string $username,

    // solution to above - might need some logic to bind $this to the callable
    #[Argument(suggestions: [self::class, 'someInstanceMethod'])]
    string $username,
) {
    // ...
}

WDYT?

tacman commented 2 years ago

I like those ideas. Much cleaner than injecting it into the configure() method. Options 1 and 2 I imagine are fairly straightforward to integrate, yes?

kbond commented 2 years ago

I think so, even 4 would probably just need \Closure::fromCallable($suggestions)->bindTo($this).

kbond commented 2 years ago

~Also, I think if the user passes a \Closure, we should auto-bind to $this~ Nevermind, you can't pass a closure to an attribute 😕

kbond commented 2 years ago

This will require some fancy footwork to be compatible with 5.4/6.0. The ability to add suggestions to InputOption and InputArgument was added in 6.1.