From 0b9f26761f06acd5e7a80326ee43746e9552ba75 Mon Sep 17 00:00:00 2001 From: Master Yoda Date: Thu, 3 May 2018 10:27:12 -0700 Subject: [PATCH] Test the command line parsing --- system/CLI/CLI.php | 70 +++++++++++------ tests/system/CLI/CLITest.php | 99 +++++++++++++++++++------ user_guide_src/source/libraries/cli.rst | 16 ++++ 3 files changed, 138 insertions(+), 47 deletions(-) diff --git a/system/CLI/CLI.php b/system/CLI/CLI.php index 5dd3cdbcf5..4a664a2f23 100644 --- a/system/CLI/CLI.php +++ b/system/CLI/CLI.php @@ -35,7 +35,6 @@ * @since Version 3.0.0 * @filesource */ - use CodeIgniter\CLI\Exceptions\CLIException; /** @@ -46,10 +45,12 @@ use CodeIgniter\CLI\Exceptions\CLIException; * * Portions of this code were initially from the FuelPHP Framework, * version 1.7.x, and used here under the MIT license they were - * originally made available under. - * - * http://fuelphp.com + * originally made available under. Reference: http://fuelphp.com * + * Some of the code in this class is Windows-specific, and not + * possible to test using travis-ci. It has been phpunit-annotated + * to prevent messing up code coverage. + * * @package CodeIgniter\HTTP */ class CLI @@ -120,6 +121,7 @@ class CLI * @var array */ protected static $segments = []; + /** * @var array */ @@ -137,6 +139,10 @@ class CLI // http://www.php.net/manual/en/readline.installation.php static::$readline_support = extension_loaded('readline'); + // clear segments & options to keep testing clean + static::$segments = []; + static::$options = []; + static::parseCommandLine(); static::$initialized = true; @@ -151,8 +157,8 @@ class CLI * php index.php user -v --v -name=John --name=John * * @param string $prefix - * * @return string + */ public static function input(string $prefix = null): string { @@ -190,6 +196,7 @@ class CLI * @param string $validation Validation rules * * @return string The user input + */ public static function prompt($field, $options = null, $validation = null): string { @@ -198,7 +205,7 @@ class CLI if (is_string($options)) { - $extra_output = ' [' . static::color($options, 'white') .']'; + $extra_output = ' [' . static::color($options, 'white') . ']'; $default = $options; } @@ -215,8 +222,8 @@ class CLI } else { - $extra_output = ' [' .$extra_output_default.', '. implode(', ', $opts) . ']'; - $validation .= '|in_list['. implode(',', $options) .']'; + $extra_output = ' [' . $extra_output_default . ', ' . implode(', ', $opts) . ']'; + $validation .= '|in_list[' . implode(',', $options) . ']'; $validation = trim($validation, '|'); } @@ -226,11 +233,11 @@ class CLI fwrite(STDOUT, $field . $extra_output . ': '); // Read the input from keyboard. - $input = trim(static::input()) ? : $default; + $input = trim(static::input()) ?: $default; if (isset($validation)) { - while (! static::validate($field, $input, $validation)) + while ( ! static::validate($field, $input, $validation)) { $input = static::prompt($field, $options, $validation); } @@ -387,6 +394,7 @@ class CLI * Clears the screen of output * * @return void + * @codeCoverageIgnore */ public static function clearScreen() { @@ -416,7 +424,9 @@ class CLI { if (static::isWindows() && ! isset($_SERVER['ANSICON'])) { + // @codeCoverageIgnoreStart return $text; + // @codeCoverageIgnoreEnd } if ( ! array_key_exists($foreground, static::$foreground_colors)) @@ -461,7 +471,9 @@ class CLI { if (static::isWindows() || (int) shell_exec('tput cols') == 0) { + // @codeCoverageIgnoreStart return $default; + // @codeCoverageIgnoreEnd } return (int) shell_exec('tput cols'); @@ -482,7 +494,9 @@ class CLI { if (static::isWindows()) { + // @codeCoverageIgnoreStart return $default; + // @codeCoverageIgnoreEnd } return (int) shell_exec('tput lines'); @@ -606,6 +620,7 @@ class CLI { $optionsFound = false; + // start picking segments off from #1, ignoring the invoking program for ($i = 1; $i < $_SERVER['argc']; $i ++ ) { // If there's no '-' at the beginning of the argument @@ -621,15 +636,10 @@ class CLI // value belonging to this option. $optionsFound = true; - if (mb_substr($_SERVER['argv'][$i], 0, 1) != '-') - { - continue; - } - $arg = str_replace('-', '', $_SERVER['argv'][$i]); $value = null; - // if the next item doesn't have a dash it's a value. + // if there is a following segment, and it doesn't start with a dash, it's a value. if (isset($_SERVER['argv'][$i + 1]) && mb_substr($_SERVER['argv'][$i + 1], 0, 1) != '-') { $value = $_SERVER['argv'][$i + 1]; @@ -685,6 +695,18 @@ class CLI //-------------------------------------------------------------------- + /** + * Returns the raw array of segments found. + * + * @return array + */ + public static function getSegments() + { + return static::$segments; + } + + //-------------------------------------------------------------------- + /** * Gets a single command-line option. Returns TRUE if the option * exists, but doesn't have a value, and is simply acting as a flag. @@ -722,7 +744,7 @@ class CLI //-------------------------------------------------------------------- /** - * Returns the options a string, suitable for passing along on + * Returns the options as a string, suitable for passing along on * the CLI to other commands. * * @return string @@ -767,7 +789,7 @@ class CLI $table_rows = []; // We need only indexes and not keys - if (! empty($thead)) + if ( ! empty($thead)) { $table_rows[] = array_values($thead); } @@ -789,7 +811,7 @@ class CLI $max_cols_lengths = []; // Read row by row and define the longest columns - for ($row = 0; $row < $total_rows; $row++) + for ($row = 0; $row < $total_rows; $row ++) { $column = 0; // Current column index foreach ($table_rows[$row] as $col) @@ -800,19 +822,19 @@ class CLI // If the current column does not have a value among the larger ones // or the value of this is greater than the existing one // then, now, this assumes the maximum length - if (! isset($max_cols_lengths[$column]) || $all_cols_lengths[$row][$column] > $max_cols_lengths[$column]) + if ( ! isset($max_cols_lengths[$column]) || $all_cols_lengths[$row][$column] > $max_cols_lengths[$column]) { $max_cols_lengths[$column] = $all_cols_lengths[$row][$column]; } // We can go check the size of the next column... - $column++; + $column ++; } } // Read row by row and add spaces at the end of the columns // to match the exact column length - for ($row = 0; $row < $total_rows; $row++) + for ($row = 0; $row < $total_rows; $row ++) { $column = 0; foreach ($table_rows[$row] as $col) @@ -822,14 +844,14 @@ class CLI { $table_rows[$row][$column] = $table_rows[$row][$column] . str_repeat(' ', $diff); } - $column++; + $column ++; } } $table = ''; // Joins columns and append the well formatted rows to the table - for ($row = 0; $row < $total_rows; $row++) + for ($row = 0; $row < $total_rows; $row ++) { // Set the table border-top if ($row === 0) diff --git a/tests/system/CLI/CLITest.php b/tests/system/CLI/CLITest.php index a15fedb1b0..f7cd0494d1 100644 --- a/tests/system/CLI/CLITest.php +++ b/tests/system/CLI/CLITest.php @@ -2,6 +2,7 @@ class CLITest extends \CIUnitTestCase { + private $stream_filter; public function setUp() @@ -189,9 +190,58 @@ EOT; public function testWrap() { $this->assertEquals('', CLI::wrap('')); - $this->assertEquals('1234'. PHP_EOL .' 5678'. PHP_EOL .' 90'. PHP_EOL .' abc'. PHP_EOL .' de'. PHP_EOL .' fghij'. PHP_EOL .' 0987654321', CLI::wrap('1234 5678 90'. PHP_EOL .'abc de fghij'. PHP_EOL .'0987654321', 5, 1)); - $this->assertEquals('1234 5678 90'. PHP_EOL .' abc de fghij'. PHP_EOL .' 0987654321', CLI::wrap('1234 5678 90'. PHP_EOL .'abc de fghij'. PHP_EOL .'0987654321', 999, 2)); - $this->assertEquals('1234 5678 90'. PHP_EOL .'abc de fghij'. PHP_EOL .'0987654321', CLI::wrap('1234 5678 90'. PHP_EOL .'abc de fghij'. PHP_EOL .'0987654321')); + $this->assertEquals('1234' . PHP_EOL . ' 5678' . PHP_EOL . ' 90' . PHP_EOL . ' abc' . PHP_EOL . ' de' . PHP_EOL . ' fghij' . PHP_EOL . ' 0987654321', CLI::wrap('1234 5678 90' . PHP_EOL . 'abc de fghij' . PHP_EOL . '0987654321', 5, 1)); + $this->assertEquals('1234 5678 90' . PHP_EOL . ' abc de fghij' . PHP_EOL . ' 0987654321', CLI::wrap('1234 5678 90' . PHP_EOL . 'abc de fghij' . PHP_EOL . '0987654321', 999, 2)); + $this->assertEquals('1234 5678 90' . PHP_EOL . 'abc de fghij' . PHP_EOL . '0987654321', CLI::wrap('1234 5678 90' . PHP_EOL . 'abc de fghij' . PHP_EOL . '0987654321')); + } + + public function testParseCommand() + { + $_SERVER['argv'] = ['ignored', 'b', 'c']; + $_SERVER['argc'] = 3; + CLI::init(); + $this->assertEquals(null, CLI::getSegment(3)); + $this->assertEquals('b', CLI::getSegment(1)); + $this->assertEquals('c', CLI::getSegment(2)); + $this->assertEquals('b/c', CLI::getURI()); + $this->assertEquals([],CLI::getOptions()); + $this->assertEmpty(CLI::getOptionString()); + $this->assertEquals(['b', 'c'], CLI::getSegments()); + } + + public function testParseCommandMixed() + { + $_SERVER['argv'] = ['ignored', 'b', 'c', 'd', '-parm', 'pvalue', 'd2']; + $_SERVER['argc'] = 7; + CLI::init(); + $this->assertEquals(null, CLI::getSegment(7)); + $this->assertEquals('b', CLI::getSegment(1)); + $this->assertEquals('c', CLI::getSegment(2)); + $this->assertEquals('d', CLI::getSegment(3)); + $this->assertEquals(['b', 'c', 'd', 'd2'], CLI::getSegments()); + } + + public function testParseCommandOption() + { + $_SERVER['argv'] = ['ignored', 'b', 'c', '-parm', 'pvalue', 'd']; + $_SERVER['argc'] = 6; + CLI::init(); + $this->assertEquals(['parm' => 'pvalue'], CLI::getOptions()); + $this->assertEquals('pvalue', CLI::getOption('parm')); + $this->assertEquals('-parm pvalue ', CLI::getOptionString()); + $this->assertNull(CLI::getOption('bogus')); + $this->assertEquals(['b', 'c', 'd'], CLI::getSegments()); + } + + public function testParseCommandMultipleOptions() + { + $_SERVER['argv'] = ['ignored', 'b', 'c', '-parm', 'pvalue', 'd', '-p2', '-p3', 'value 3']; + $_SERVER['argc'] = 9; + CLI::init(); + $this->assertEquals(['parm' => 'pvalue', 'p2' => null, 'p3' => 'value 3'], CLI::getOptions()); + $this->assertEquals('pvalue', CLI::getOption('parm')); + $this->assertEquals('-parm pvalue -p2 -p3 "value 3" ', CLI::getOptionString()); + $this->assertEquals(['b', 'c', 'd'], CLI::getSegments()); } /** @@ -209,8 +259,8 @@ EOT; public function tableProvider() { - $head = ['ID', 'Title']; - $one_row = [['id' => 1, 'foo' => 'bar']]; + $head = ['ID', 'Title']; + $one_row = [['id' => 1, 'foo' => 'bar']]; $many_rows = [ ['id' => 1, 'foo' => 'bar'], ['id' => 2, 'foo' => 'bar * 2'], @@ -219,42 +269,45 @@ EOT; return [ [$one_row, [], "+---+-----+\n" . - "| 1 | bar |\n" . - "+---+-----+\n"], + "| 1 | bar |\n" . + "+---+-----+\n"], [$one_row, $head, "+----+-------+\n" . - "| ID | Title |\n" . - "+----+-------+\n" . - "| 1 | bar |\n" . - "+----+-------+\n"], + "| ID | Title |\n" . + "+----+-------+\n" . + "| 1 | bar |\n" . + "+----+-------+\n"], [$many_rows, [], "+---+-----------------+\n" . - "| 1 | bar |\n" . - "| 2 | bar * 2 |\n" . - "| 3 | bar + bar + bar |\n" . - "+---+-----------------+\n"], + "| 1 | bar |\n" . + "| 2 | bar * 2 |\n" . + "| 3 | bar + bar + bar |\n" . + "+---+-----------------+\n"], [$many_rows, $head, "+----+-----------------+\n" . - "| ID | Title |\n" . - "+----+-----------------+\n" . - "| 1 | bar |\n" . - "| 2 | bar * 2 |\n" . - "| 3 | bar + bar + bar |\n" . - "+----+-----------------+\n"], + "| ID | Title |\n" . + "+----+-----------------+\n" . + "| 1 | bar |\n" . + "| 2 | bar * 2 |\n" . + "| 3 | bar + bar + bar |\n" . + "+----+-----------------+\n"], ]; } -} +} class CLITestStreamFilter extends \php_user_filter { + public static $buffer = ''; public function filter($in, $out, &$consumed, $closing) { - while ($bucket = stream_bucket_make_writeable($in)) { + while ($bucket = stream_bucket_make_writeable($in)) + { self::$buffer .= $bucket->data; $consumed += $bucket->datalen; } return PSFS_PASS_ON; } + } stream_filter_register('CLITestStreamFilter', 'CodeIgniter\CLI\CLITestStreamFilter'); diff --git a/user_guide_src/source/libraries/cli.rst b/user_guide_src/source/libraries/cli.rst index a9e9a68e6f..b0c0cddff5 100644 --- a/user_guide_src/source/libraries/cli.rst +++ b/user_guide_src/source/libraries/cli.rst @@ -224,3 +224,19 @@ pass ``false`` as the first parameter and the progress bar will be removed. | 7 | A great item title | 2017-11-16 10:35:02 | 1 | | 8 | Another great item title | 2017-11-16 13:46:54 | 0 | +----+--------------------------+---------------------+--------+ + +**wait()** + +Waits a certain number of seconds, optionally showing a wait message and +waiting for a key press. + +:: + + // wait for specified interval, with countdown displayed + CLI::wait($seconds, true); + + // show continuation message and wait for input + CLI::wait(0, false); + + // wait for specified interval + CLI::wait($seconds, false);