Skip to content

Add new method promptByMultipleKeys() in CLI class #6302

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 99 additions & 5 deletions system/CLI/CLI.php
Original file line number Diff line number Diff line change
Expand Up @@ -281,25 +281,119 @@ public static function promptByKey($text, array $options, $validation = null): s
throw new InvalidArgumentException('$text can only be of type string|array');
}

if (! $options) {
throw new InvalidArgumentException('No options to select from were provided');
}
CLI::isZeroOptions($options);

if ($line = array_shift($text)) {
CLI::write($line);
}

CLI::printKeysAndValues($options);

return static::prompt(PHP_EOL . array_shift($text), array_keys($options), $validation);
}

/**
* This method is the same as promptByKey(), but this method supports multiple keys, separated by commas.
*
* @param string $text Output "field" text or an one or two value array where the first value is the text before listing the options
* and the second value the text before asking to select one option. Provide empty string to omit
* @param array $options A list of options (array(key => description)), the first option will be the default value
*
* @return array The selected key(s) and value(s) of $options
*/
public static function promptByMultipleKeys(string $text, array $options): array
{
CLI::isZeroOptions($options);

$extraOutputDefault = static::color('0', 'green');
$opts = $options;
unset($opts[0]);

if (empty($opts)) {
$extraOutput = $extraOutputDefault;
} else {
$optsKey = [];

foreach (array_keys($opts) as $key) {
$optsKey[] = $key;
}
$extraOutput = '[' . $extraOutputDefault . ', ' . implode(', ', $optsKey) . ']';
$extraOutput = 'You can specify multiple values separated by commas.' . PHP_EOL . $extraOutput;
}

CLI::write($text);
CLI::printKeysAndValues($options);
CLI::newLine();
$input = static::prompt($extraOutput) ?: 0; // 0 is default

// validation
while (true) {
$pattern = preg_match_all('/^\d+(,\d+)*$/', trim($input));

// separate input by comma and convert all to an int[]
$inputToArray = array_map(static fn ($value) => (int) $value, explode(',', $input));
// find max from key of $options
$maxOptions = array_key_last($options);
// find max from input
$maxInput = max($inputToArray);

// return the prompt again if $input contain(s) non-numeric charachter, except a comma.
// And if max from $options less than max from input
// it is mean user tried to access null value in $options
if (! $pattern || $maxOptions < $maxInput) {
static::error('Please select correctly.');
CLI::newLine();
$input = static::prompt($extraOutput) ?: 0;
} else {
break;
}
}

$input = [];

foreach ($options as $key => $description) {
foreach ($inputToArray as $inputKey) {
if ($key === $inputKey) {
$input[$key] = $description;
}
}
}

return $input;
}

//--------------------------------------------------------------------
// Utility for promptBy...
//--------------------------------------------------------------------

/**
* Validation for $options in promptByKey() and promptByMultipleKeys(). Return an error if $options is an empty array.
*/
private static function isZeroOptions(array $options): void
{
if (! $options) {
throw new InvalidArgumentException('No options to select from were provided');
}
}

/**
* Print each key and value one by one
*/
private static function printKeysAndValues(array $options): void
{
// +2 for the square brackets around the key
$keyMaxLength = max(array_map('mb_strwidth', array_keys($options))) + 2;

foreach ($options as $key => $description) {
$name = str_pad(' [' . $key . '] ', $keyMaxLength + 4, ' ');
CLI::write(CLI::color($name, 'green') . CLI::wrap($description, 125, $keyMaxLength + 4));
}

return static::prompt(PHP_EOL . array_shift($text), array_keys($options), $validation);
}

//--------------------------------------------------------------------
// End Utility for promptBy...
//--------------------------------------------------------------------

/**
* Validate one prompt "field" at a time
*
Expand Down
20 changes: 20 additions & 0 deletions tests/system/CLI/CLITest.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,26 @@ public function testPrompt()
PhpStreamWrapper::restore();
}

public function testPromptByMultipleKeys()
{
PhpStreamWrapper::register();

$input = '0,1';
PhpStreamWrapper::setContent($input);

$options = ['Playing game', 'Sleep', 'Badminton'];
$output = CLI::promptByMultipleKeys('Select your hobbies:', $options);

$expected = [
0 => 'Playing game',
1 => 'Sleep',
];

$this->assertSame($expected, $output);

PhpStreamWrapper::restore();
}

public function testIsWindows()
{
$this->assertSame(('\\' === DIRECTORY_SEPARATOR), CLI::isWindows());
Expand Down
6 changes: 6 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ Others
Enhancements
************

CLI
===
- Added methods ``CLI::promptByMultipleKeys()`` to support multiple value in input, unlike ``promptByKey()``. See :ref:`prompt-by-multiple-keys` for details.

Others
======
- Added the ``StreamFilterTrait`` to make it easier to work with capturing data from STDOUT and STDERR streams. See :ref:`testing-cli-output`.
- Added the ``PhpStreamWrapper`` to make it easier to work with setting data to ``php://stdin``. See :ref:`testing-cli-input`.
- Added before and after events to ``BaseModel::insertBatch()`` and ``BaseModel::updateBatch()`` methods. See :ref:`model-events-callbacks`.
Expand Down
11 changes: 11 additions & 0 deletions user_guide_src/source/cli/cli_library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ Named keys are also possible:

Finally, you can pass :ref:`validation <validation>` rules to the answer input as the third parameter, the acceptable answers are automatically restricted to the passed options.

.. _prompt-by-multiple-keys:

promptByMultipleKeys()
======================

This method is the same as ``promptByKey()``, but it supports multiple value.

.. literalinclude:: cli_library/023.php

.. important:: The method ``promptByMultipleKeys()``, unlike ``promptByKey()``, does not support named keys or validation.

Providing Feedback
******************

Expand Down
18 changes: 18 additions & 0 deletions user_guide_src/source/cli/cli_library/023.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

$hobbies = CLI::promptByMultipleKeys('Select your hobbies:', ['Playing game', 'Sleep', 'Badminton']);
/*
* Select your hobbies:
* [0] Playing game
* [1] Sleep
* [2] Badminton
*
* You can specify multiple values separated by commas.
* [0, 1, 2]:
*
* if your answer is '0,2', the return is the key and the value of the options :
* [
* [0] => "Playing game",
* [2] => "Badminton"
* ]
*/