From e0391190f62ae8f0f253c38a421cb6b350cf7539 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 19:35:38 +0000 Subject: [PATCH 1/6] Initial plan From 0bff8634390af4d9856d7c0d0c08374ae56e8ca2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 19:42:35 +0000 Subject: [PATCH 2/6] Fix multi-line description placement in help output Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- features/help.feature | 109 ++++++++++++++++++++++++++++++ php/commands/src/Help_Command.php | 54 ++++++++++++++- 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/features/help.feature b/features/help.feature index 236c9e254..09d659980 100644 --- a/features/help.feature +++ b/features/help.feature @@ -1287,3 +1287,112 @@ Feature: Get help about WP-CLI commands | func | | proc_open | | proc_close | + + Scenario: Multi-paragraph description appears in DESCRIPTION section + Given a WP installation + And a wp-content/plugins/test-cli/command.php file: + """ + + * : Cloudflare zone ID. + * + * ## EXAMPLES + * + * # Clear the cloudflare cache for a domain. + * wp test-multiline clear-cloudflare-cache 12345 + * Success: The Cloudflare cache has been cleared. + * + * @subcommand clear-cloudflare-cache + * @alias clear_cloudflare_cache + */ + public function clear_cloudflare_cache( $args ) {} + } + + WP_CLI::add_command( 'test-multiline', 'Test_Multiline_Description' ); + """ + And I run `wp plugin activate test-cli` + + When I run `wp help test-multiline clear-cloudflare-cache` + Then STDOUT should contain: + """ + DESCRIPTION + + Clear the Cloudflare cache. Default: purge everything. Uses + $CLOUDFLARE_API_KEY environment variable. + + API documentation: https://developers.cloudflare.com/api/resources/cache/ + + SYNOPSIS + """ + And STDOUT should contain: + """ + ALIAS + + clear_cloudflare_cache + + OPTIONS + """ + + Scenario: Multi-paragraph description appears in DESCRIPTION section without alias + Given a WP installation + And a wp-content/plugins/test-cli/command.php file: + """ + + * : Cloudflare zone ID. + * + * ## EXAMPLES + * + * # Clear the cloudflare cache for a domain. + * wp test-multiline no-alias 12345 + * Success: The Cloudflare cache has been cleared. + */ + public function no_alias( $args ) {} + } + + WP_CLI::add_command( 'test-multiline', 'Test_Multiline_No_Alias' ); + """ + And I run `wp plugin activate test-cli` + + When I run `wp help test-multiline no-alias` + Then STDOUT should contain: + """ + DESCRIPTION + + Clear the Cloudflare cache. Default: purge everything. Uses + $CLOUDFLARE_API_KEY environment variable. + + API documentation: https://developers.cloudflare.com/api/resources/cache/ + + SYNOPSIS + """ + And STDOUT should not contain: + """ + ALIAS + """ + And STDOUT should contain: + """ + OPTIONS + + + """ diff --git a/php/commands/src/Help_Command.php b/php/commands/src/Help_Command.php index 384953392..a58d75eb0 100644 --- a/php/commands/src/Help_Command.php +++ b/php/commands/src/Help_Command.php @@ -47,7 +47,10 @@ private static function show_help( $command ) { $out = substr_replace( $out, $subcommands_header, $matches[1][1], strlen( $subcommands ) ); } - $out .= self::parse_reference_links( $command->get_longdesc() ); + // Parse reference links and extract only the sections part (OPTIONS, EXAMPLES, etc.) + $longdesc_with_links = self::parse_reference_links( $command->get_longdesc() ); + $longdesc_sections = self::get_longdesc_sections( $longdesc_with_links ); + $out .= $longdesc_sections; // Definition lists. $out = (string) preg_replace_callback( '/([^\n]+)\n: (.+?)(\n\n|$)/s', [ __CLASS__, 'rewrap_param_desc' ], $out ); @@ -174,6 +177,14 @@ private static function get_initial_markdown( $command ) { } } + // Add description paragraphs from longdesc to shortdesc for DESCRIPTION section + // Parse reference links first, then extract description + $longdesc_with_links = self::parse_reference_links( $command->get_longdesc() ); + $longdesc_description = self::get_longdesc_description( $longdesc_with_links ); + if ( $longdesc_description ) { + $binding['shortdesc'] .= "\n" . $longdesc_description; + } + if ( $command->can_have_subcommands() ) { $binding['has-subcommands']['subcommands'] = self::render_subcommands( $command ); } @@ -223,7 +234,7 @@ private static function get_max_len( $strings ) { private static function parse_reference_links( $longdesc ) { $description = ''; foreach ( explode( "\n", $longdesc ) as $line ) { - if ( 0 === strpos( $line, '#' ) ) { + if ( 0 === strpos( $line, '##' ) ) { break; } $description .= $line . "\n"; @@ -258,4 +269,43 @@ static function ( $matches ) use ( &$links ) { return $longdesc; } + + /** + * Extract description paragraphs from longdesc (content before first section header). + * + * @param string $longdesc The longdescription from the command. + * @return string The description paragraphs only. + */ + private static function get_longdesc_description( $longdesc ) { + $description = ''; + foreach ( explode( "\n", $longdesc ) as $line ) { + if ( 0 === strpos( $line, '##' ) ) { + break; + } + $description .= $line . "\n"; + } + + return trim( $description ); + } + + /** + * Extract section content from longdesc (content from first section header onwards). + * + * @param string $longdesc The longdescription from the command. + * @return string The section content only (OPTIONS, EXAMPLES, etc.). + */ + private static function get_longdesc_sections( $longdesc ) { + $sections = ''; + $in_sections = false; + foreach ( explode( "\n", $longdesc ) as $line ) { + if ( ! $in_sections && 0 === strpos( $line, '##' ) ) { + $in_sections = true; + } + if ( $in_sections ) { + $sections .= $line . "\n"; + } + } + + return $sections; + } } From 0fab651c058ac2c6cb13a8338cdcea52e9568498 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 19:45:59 +0000 Subject: [PATCH 3/6] Refactor to avoid duplicate parse_reference_links calls and reduce code duplication Co-authored-by: swissspidy <841956+swissspidy@users.noreply.github.com> --- php/commands/src/Help_Command.php | 57 +++++++++++++++++-------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/php/commands/src/Help_Command.php b/php/commands/src/Help_Command.php index a58d75eb0..808279a59 100644 --- a/php/commands/src/Help_Command.php +++ b/php/commands/src/Help_Command.php @@ -36,7 +36,10 @@ public function __invoke( $args ) { } private static function show_help( $command ) { - $out = self::get_initial_markdown( $command ); + // Parse reference links once for the entire longdesc + $longdesc_with_links = self::parse_reference_links( $command->get_longdesc() ); + + $out = self::get_initial_markdown( $command, $longdesc_with_links ); // Remove subcommands if in columns - will wordwrap separately. $subcommands = ''; @@ -47,10 +50,9 @@ private static function show_help( $command ) { $out = substr_replace( $out, $subcommands_header, $matches[1][1], strlen( $subcommands ) ); } - // Parse reference links and extract only the sections part (OPTIONS, EXAMPLES, etc.) - $longdesc_with_links = self::parse_reference_links( $command->get_longdesc() ); - $longdesc_sections = self::get_longdesc_sections( $longdesc_with_links ); - $out .= $longdesc_sections; + // Extract only the sections part (OPTIONS, EXAMPLES, etc.) + $longdesc_sections = self::get_longdesc_sections( $longdesc_with_links ); + $out .= $longdesc_sections; // Definition lists. $out = (string) preg_replace_callback( '/([^\n]+)\n: (.+?)(\n\n|$)/s', [ __CLASS__, 'rewrap_param_desc' ], $out ); @@ -153,7 +155,7 @@ private static function pass_through_pager( $out ) { return $process ? proc_close( $process ) : -1; } - private static function get_initial_markdown( $command ) { + private static function get_initial_markdown( $command, $longdesc_with_links = null ) { $name = implode( ' ', Dispatcher\get_path( $command ) ); $binding = [ @@ -178,8 +180,9 @@ private static function get_initial_markdown( $command ) { } // Add description paragraphs from longdesc to shortdesc for DESCRIPTION section - // Parse reference links first, then extract description - $longdesc_with_links = self::parse_reference_links( $command->get_longdesc() ); + if ( null === $longdesc_with_links ) { + $longdesc_with_links = self::parse_reference_links( $command->get_longdesc() ); + } $longdesc_description = self::get_longdesc_description( $longdesc_with_links ); if ( $longdesc_description ) { $binding['shortdesc'] .= "\n" . $longdesc_description; @@ -232,16 +235,10 @@ private static function get_max_len( $strings ) { * @return string The longdescription which has links as footnote. */ private static function parse_reference_links( $longdesc ) { - $description = ''; - foreach ( explode( "\n", $longdesc ) as $line ) { - if ( 0 === strpos( $line, '##' ) ) { - break; - } - $description .= $line . "\n"; - } + $description = self::extract_before_sections( $longdesc ); // Fires if it has description text at the head of `$longdesc`. - if ( $description ) { + if ( trim( $description ) ) { $links = []; // An array of URLs from the description. $pattern = '/\[.+?\]\((https?:\/\/.+?)\)/'; $newdesc = (string) preg_replace_callback( @@ -277,15 +274,7 @@ static function ( $matches ) use ( &$links ) { * @return string The description paragraphs only. */ private static function get_longdesc_description( $longdesc ) { - $description = ''; - foreach ( explode( "\n", $longdesc ) as $line ) { - if ( 0 === strpos( $line, '##' ) ) { - break; - } - $description .= $line . "\n"; - } - - return trim( $description ); + return trim( self::extract_before_sections( $longdesc ) ); } /** @@ -308,4 +297,22 @@ private static function get_longdesc_sections( $longdesc ) { return $sections; } + + /** + * Extract content before first section header (##). + * + * @param string $text The text to extract from. + * @return string Content before first section header. + */ + private static function extract_before_sections( $text ) { + $before_sections = ''; + foreach ( explode( "\n", $text ) as $line ) { + if ( 0 === strpos( $line, '##' ) ) { + break; + } + $before_sections .= $line . "\n"; + } + + return $before_sections; + } } From a196d905b720e44c31c88844f304501796de5049 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 11 Jan 2026 21:07:50 +0100 Subject: [PATCH 4/6] Apply suggestions from code review --- features/help.feature | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/help.feature b/features/help.feature index 09d659980..df2021039 100644 --- a/features/help.feature +++ b/features/help.feature @@ -1364,17 +1364,17 @@ Feature: Get help about WP-CLI commands * ## EXAMPLES * * # Clear the cloudflare cache for a domain. - * wp test-multiline no-alias 12345 + * wp test-multiline noalias 12345 * Success: The Cloudflare cache has been cleared. */ - public function no_alias( $args ) {} + public function noalias( $args ) {} } WP_CLI::add_command( 'test-multiline', 'Test_Multiline_No_Alias' ); """ And I run `wp plugin activate test-cli` - When I run `wp help test-multiline no-alias` + When I run `wp help test-multiline noalias` Then STDOUT should contain: """ DESCRIPTION From d10ec67388088ef14652e9b7f3d327a234f371ac Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 11 Jan 2026 21:27:21 +0100 Subject: [PATCH 5/6] Update php/commands/src/Help_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- php/commands/src/Help_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/commands/src/Help_Command.php b/php/commands/src/Help_Command.php index 808279a59..7d1ac2821 100644 --- a/php/commands/src/Help_Command.php +++ b/php/commands/src/Help_Command.php @@ -185,7 +185,7 @@ private static function get_initial_markdown( $command, $longdesc_with_links = n } $longdesc_description = self::get_longdesc_description( $longdesc_with_links ); if ( $longdesc_description ) { - $binding['shortdesc'] .= "\n" . $longdesc_description; + $binding['shortdesc'] .= "\n\n" . $longdesc_description; } if ( $command->can_have_subcommands() ) { From f36e68ee509acf6cdafebd41638c414d8d057e8a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 11 Jan 2026 21:27:32 +0100 Subject: [PATCH 6/6] Update php/commands/src/Help_Command.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- php/commands/src/Help_Command.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/php/commands/src/Help_Command.php b/php/commands/src/Help_Command.php index 7d1ac2821..6ddb268dc 100644 --- a/php/commands/src/Help_Command.php +++ b/php/commands/src/Help_Command.php @@ -237,7 +237,7 @@ private static function get_max_len( $strings ) { private static function parse_reference_links( $longdesc ) { $description = self::extract_before_sections( $longdesc ); - // Fires if it has description text at the head of `$longdesc`. + // Process if there is description text at the head of `$longdesc`. if ( trim( $description ) ) { $links = []; // An array of URLs from the description. $pattern = '/\[.+?\]\((https?:\/\/.+?)\)/';