diff --git a/features/help.feature b/features/help.feature index 236c9e254..df2021039 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 noalias 12345 + * Success: The Cloudflare cache has been cleared. + */ + 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 noalias` + 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..6ddb268dc 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,7 +50,9 @@ 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() ); + // 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 ); @@ -150,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 = [ @@ -174,6 +179,15 @@ private static function get_initial_markdown( $command ) { } } + // Add description paragraphs from longdesc to shortdesc for DESCRIPTION section + 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\n" . $longdesc_description; + } + if ( $command->can_have_subcommands() ) { $binding['has-subcommands']['subcommands'] = self::render_subcommands( $command ); } @@ -221,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 ) { + // Process if there is description text at the head of `$longdesc`. + if ( trim( $description ) ) { $links = []; // An array of URLs from the description. $pattern = '/\[.+?\]\((https?:\/\/.+?)\)/'; $newdesc = (string) preg_replace_callback( @@ -258,4 +266,53 @@ 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 ) { + return trim( self::extract_before_sections( $longdesc ) ); + } + + /** + * 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; + } + + /** + * 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; + } }