Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a way to dynamically autocomplete task arguments/options
- Loading branch information
Showing
18 changed files
with
4,475 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# Autocomplete | ||
|
||
## Installation | ||
|
||
If you use bash, you can enable autocomplete for castor by running the | ||
following task: | ||
|
||
``` | ||
castor completion | sudo tee /etc/bash_completion.d/castor | ||
``` | ||
|
||
Then reload your shell. | ||
|
||
Others shells are also supported (zsh, fish, etc). To get the list of supported | ||
shells and their dedicated instructions, run: | ||
|
||
``` | ||
castor completion --help | ||
``` | ||
|
||
## Autocomplete arguments | ||
|
||
You have to options to make your arguments autocompleted. | ||
|
||
### Static suggestions | ||
|
||
In case your suggestions are fixed, you can pass them in the `suggestedValues` | ||
property of the `AsArgument` and `AsOption` attributes: | ||
|
||
```php | ||
#[AsTask()] | ||
function my_task( | ||
#[AsArgument(name: 'argument', suggestedValues: ['foo', 'bar', 'baz'])] | ||
string $argument, | ||
): void { | ||
} | ||
``` | ||
|
||
When trying to autocomplete the arguments, your shell will now suggest these | ||
values: | ||
|
||
```bash | ||
$ castor my-task [TAB] | ||
bar baz foo | ||
``` | ||
|
||
### Dynamic suggestions | ||
|
||
In case you need some logic to list the suggestions (like suggesting paths or | ||
docker services, making a database query or HTTP request to determine some | ||
values, etc.), you can use the `autocomplete` property of the `AsArgument` and | ||
`AsOption` attributes to provide the function that will return the suggestions: | ||
|
||
```php | ||
namespace example; | ||
|
||
use Symfony\Component\Console\Completion\CompletionInput; | ||
|
||
#[AsTask()] | ||
function autocomplete_argument( | ||
#[AsArgument(name: 'argument', autocomplete: 'example\get_argument_autocompletion')] | ||
string $argument, | ||
): void { | ||
} | ||
|
||
function get_argument_autocompletion(CompletionInput $input): array | ||
{ | ||
// You can search for a file on the filesystem, make a network call, etc. | ||
|
||
return [ | ||
'foo', | ||
'bar', | ||
'baz', | ||
]; | ||
} | ||
``` | ||
|
||
>[!NOTE] | ||
> Because the syntax `my_callback(...)` is not allowed on attribute, you need to | ||
> specify the `autocomplete` callback with either: | ||
> - the string syntax (`my_namespace\my_function` or `'MyNamespace\MyClass::myFunction'`) | ||
> - the array syntax (`['MyNamespace\MyClass', 'myFunction']`). | ||
This function receives an optional `Symfony\Component\Console\Completion\CompletionInput` | ||
argument to allow you to pre-filter the suggestions returned to the shell. | ||
|
||
>[!TIP] | ||
> The shell script is able to handle huge amounts of suggestions and will | ||
> automatically filter the suggested values based on the existing input from the | ||
> user. You do not have to implement any filter logic in the function. | ||
> | ||
> You may use CompletionInput::getCompletionValue() to get the current input if | ||
> that helps improving performance (e.g. by reducing the number of rows fetched | ||
> from the database). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<?php | ||
|
||
namespace Castor\Tests; | ||
|
||
use Castor\Attribute\AsArgument; | ||
use Castor\Attribute\AsTask; | ||
use Castor\Console\Command\TaskCommand; | ||
use Castor\EventDispatcher; | ||
use Castor\ExpressionLanguage; | ||
use Symfony\Component\Console\Completion\CompletionInput; | ||
use Symfony\Component\Console\Tester\CommandCompletionTester; | ||
|
||
class AutocompleteTest extends TaskTestCase | ||
{ | ||
/** @dataProvider getData */ | ||
public function testCompletion(\Closure $function, array $expectedValues, string $input = '') | ||
{ | ||
$reflectionFunction = new \ReflectionFunction($function); | ||
|
||
$command = new TaskCommand(new AsTask('task'), $reflectionFunction, $this->createMock(EventDispatcher::class), $this->createMock(ExpressionLanguage::class)); | ||
|
||
$tester = new CommandCompletionTester($command); | ||
$suggestions = $tester->complete([$input]); | ||
|
||
$this->assertSame($expectedValues, $suggestions); | ||
} | ||
|
||
public function getData(): \Generator | ||
{ | ||
yield [task_with_suggested_values(...), ['a', 'b', 'c']]; | ||
yield [task_with_autocomplete(...), ['d', 'e', 'f']]; | ||
yield [task_with_autocomplete_filtered(...), ['foo', 'bar', 'baz']]; | ||
yield [task_with_autocomplete_filtered(...), ['bar', 'baz'], 'ba']; | ||
} | ||
} | ||
|
||
function task_with_suggested_values( | ||
#[AsArgument(name: 'argument', suggestedValues: ['a', 'b', 'c'])] | ||
string $argument, | ||
): void { | ||
} | ||
|
||
function task_with_autocomplete( | ||
#[AsArgument(name: 'argument', autocomplete: 'Castor\Tests\complete')] | ||
string $argument, | ||
): void { | ||
} | ||
|
||
/** @return string[] */ | ||
function complete(CompletionInput $input): array | ||
{ | ||
return [ | ||
'd', | ||
'e', | ||
'f', | ||
]; | ||
} | ||
|
||
function task_with_autocomplete_filtered( | ||
#[AsArgument(name: 'argument', autocomplete: 'Castor\Tests\complete_filtered')] | ||
string $argument, | ||
): void { | ||
} | ||
|
||
/** @return string[] */ | ||
function complete_filtered(CompletionInput $input): array | ||
{ | ||
return array_filter([ | ||
'foo', | ||
'bar', | ||
'baz', | ||
], fn (string $value) => str_starts_with($value, $input->getCompletionValue())); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Castor\Tests\Examples\Generated; | ||
|
||
use Castor\Tests\TaskTestCase; | ||
|
||
class ArgsAutocompleteArgumentTest extends TaskTestCase | ||
{ | ||
// args:autocomplete-argument | ||
public function test(): void | ||
{ | ||
$process = $this->runTask(['args:autocomplete-argument', 'FIXME(argument)']); | ||
|
||
$this->assertSame(0, $process->getExitCode()); | ||
$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput()); | ||
if (file_exists(__FILE__ . '.err.txt')) { | ||
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput()); | ||
} else { | ||
$this->assertSame('', $process->getErrorOutput()); | ||
} | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
tests/Examples/Generated/ArgsAutocompleteArgumentTest.php.output.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
array(1) { | ||
[0]=> | ||
string(15) "FIXME(argument)" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
namespace Castor\Tests\Examples\Generated; | ||
|
||
use Castor\Tests\TaskTestCase; | ||
|
||
class AutocompleteInvalidTest extends TaskTestCase | ||
{ | ||
// no task | ||
public function test(): void | ||
{ | ||
$process = $this->runTask([], '{{ base }}/tests/Examples/fixtures/broken/autocomplete-invalid'); | ||
|
||
$this->assertSame(1, $process->getExitCode()); | ||
$this->assertStringEqualsFile(__FILE__ . '.output.txt', $process->getOutput()); | ||
if (file_exists(__FILE__ . '.err.txt')) { | ||
$this->assertStringEqualsFile(__FILE__ . '.err.txt', $process->getErrorOutput()); | ||
} else { | ||
$this->assertSame('', $process->getErrorOutput()); | ||
} | ||
} | ||
} |
Oops, something went wrong.