Skip to content
Draft
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
18 changes: 15 additions & 3 deletions features/config.feature
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,14 @@ Feature: Have a config file

# TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9
When I try `WP_CLI_CONFIG_PATH=config.yml wp`
Then STDOUT should not contain:
Then STDOUT should contain:
"""
eval-file
"""
And STDOUT should contain:
"""
(disabled: from the config file)
"""

When I try `WP_CLI_CONFIG_PATH=config.yml wp help eval-file`
Then STDERR should contain:
Expand All @@ -121,17 +125,25 @@ Feature: Have a config file

# TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9
When I try `WP_CLI_CONFIG_PATH=config.yml wp core`
Then STDOUT should not contain:
Then STDOUT should contain:
"""
or: wp core multisite-convert
"""
And STDOUT should contain:
"""
(disabled: from the config file)
"""

# TODO: Throwing deprecations with PHP 8.1+ and WP < 5.9
When I try `WP_CLI_CONFIG_PATH=config.yml wp help core`
Then STDOUT should not contain:
Then STDOUT should contain:
"""
multisite-convert
"""
And STDOUT should contain:
"""
(disabled: from the config file)
"""

When I try `WP_CLI_CONFIG_PATH=config.yml wp core multisite-convert`
Then STDERR should contain:
Expand Down
36 changes: 36 additions & 0 deletions features/help.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1424,3 +1424,39 @@ Feature: Get help about WP-CLI commands
When I run `PAGER=less wp help | head -1`
Then STDOUT should not match /\x1b\[/
And STDOUT should not match /\033\[/

Scenario: Disabled commands are shown in help listings
Given an empty directory
And a wp-cli.yml file:
"""
disabled_commands:
- core
"""

When I run `wp help`
Then STDOUT should contain:
"""
core
"""
And STDOUT should contain:
"""
(disabled: from the config file)
"""

Scenario: Full help shows all subcommands recursively
Given an empty directory

When I run `wp help core --full`
Then STDOUT should contain:
"""
wp core
"""
And STDOUT should contain:
"""
wp core check-update
"""
And STDOUT should contain:
"""
wp core download
"""
And STDERR should be empty
5 changes: 4 additions & 1 deletion php/WP_CLI/Dispatcher/CompositeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,10 @@ public function show_usage() {
$prefix = ( 0 === $i ) ? 'usage: ' : ' or: ';
++$i;

if ( WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) {
$disabled_reason = WP_CLI::get_runner()->get_command_disabled_reason( $subcommand );
if ( false !== $disabled_reason ) {
$suffix = $disabled_reason ? " (disabled: $disabled_reason)" : ' (disabled)';
WP_CLI::line( $subcommand->get_usage( $prefix ) . $suffix );
continue;
}

Expand Down
14 changes: 13 additions & 1 deletion php/WP_CLI/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -941,12 +941,24 @@ private function generate_ssh_command( $bits, $wp_command ) {
* @return bool
*/
public function is_command_disabled( $command ) {
return false !== $this->get_command_disabled_reason( $command );
}

/**
* Get the reason why a command is disabled, or false if it isn't.
*
* @return string|false Reason string, or false if the command is not disabled.
*/
public function get_command_disabled_reason( $command ) {
$path = implode( ' ', array_slice( Dispatcher\get_path( $command ), 1 ) );
/**
* @var string[] $disabled_commands
*/
$disabled_commands = $this->config['disabled_commands'];
return in_array( $path, $disabled_commands, true );
if ( in_array( $path, $disabled_commands, true ) ) {
return 'from the config file';
}
return false;
}

/**
Expand Down
167 changes: 96 additions & 71 deletions php/commands/src/Help_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ class Help_Command extends WP_CLI_Command {
* [<command>...]
* : Get help on a specific command.
*
* [--full]
* : Show the full help, including help for all subcommands.
*
* ## EXAMPLES
*
* # get help for `core` command
Expand All @@ -23,83 +26,30 @@ class Help_Command extends WP_CLI_Command {
* # get help for `core download` subcommand
* wp help core download
*
* # get full help for `core`, including all subcommands
* wp help core --full
*
* @param string[] $args
* @param array $assoc_args
*/
public function __invoke( $args ) {
public function __invoke( $args, $assoc_args ) {
$r = WP_CLI::get_runner()->find_command_to_run( $args, Utils\get_env_or_config( 'WP_CLI_AUTOCORRECT' ) ? 'auto' : 'confirm' );

if ( is_array( $r ) ) {
list( $command ) = $r;

self::show_help( $command );
if ( ! empty( $assoc_args['full'] ) ) {
$out = self::get_help_full( $command );
self::pass_through_pager( $out );
} else {
self::show_help( $command );
}
exit;
}
}

private static function show_help( $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 = '';
$column_subpattern = '[ \t]+[^\t]+\t+';
if ( preg_match( '/(^## SUBCOMMANDS[^\n]*\n+' . $column_subpattern . '.+?)(?:^##|\z)/ms', $out, $matches, PREG_OFFSET_CAPTURE ) ) {
$subcommands = $matches[1][0];
$subcommands_header = "## SUBCOMMANDS\n";
$out = substr_replace( $out, $subcommands_header, $matches[1][1], strlen( $subcommands ) );
}

// 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 );

// Ensure lines with no leading whitespace that aren't section headers are indented.
$out = (string) preg_replace( '/^((?! |\t|##).)/m', "\t$1", $out );

$tab = str_repeat( ' ', 2 );

// Need to de-tab for wordwrapping to work properly.
$out = str_replace( "\t", $tab, $out );

$wordwrap_width = Shell::columns();

// Wordwrap with indent.
$out = (string) preg_replace_callback(
'/^( *)([^\n]+)\n/m',
static function ( $matches ) use ( $wordwrap_width ) {
return $matches[1] . str_replace( "\n", "\n{$matches[1]}", wordwrap( $matches[2], $wordwrap_width - strlen( $matches[1] ) ) ) . "\n";
},
$out
);

if ( $subcommands ) {
// Wordwrap with column indent.
$subcommands = (string) preg_replace_callback(
'/^(' . $column_subpattern . ')([^\n]+)\n/m',
static function ( $matches ) use ( $wordwrap_width, $tab ) {
// Need to de-tab for wordwrapping to work properly.
$matches[1] = str_replace( "\t", $tab, $matches[1] );
$matches[2] = str_replace( "\t", $tab, $matches[2] );
$padding_len = strlen( $matches[1] );
$padding = str_repeat( ' ', $padding_len );
return $matches[1] . str_replace( "\n", "\n$padding", wordwrap( $matches[2], $wordwrap_width - $padding_len ) ) . "\n";
},
$subcommands
);

// Put subcommands back.
$out = str_replace( $subcommands_header, $subcommands, $out );
}

// Section headers.
$out = (string) preg_replace( '/^## ([A-Z ]+)/m', WP_CLI::colorize( '%9\1%n' ), $out );

self::pass_through_pager( $out );
self::pass_through_pager( self::get_help_as_string( $command ) );
}

private static function rewrap_param_desc( $matches ) {
Expand Down Expand Up @@ -276,25 +226,100 @@ private static function get_initial_markdown( $command, $longdesc_with_links = n

private static function render_subcommands( $command ) {
$subcommands = [];
$disabled = [];
foreach ( $command->get_subcommands() as $subcommand ) {

if ( WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) {
continue;
$disabled_reason = WP_CLI::get_runner()->get_command_disabled_reason( $subcommand );
if ( false !== $disabled_reason ) {
$disabled[ $subcommand->get_name() ] = [
'desc' => $subcommand->get_shortdesc(),
'reason' => $disabled_reason,
];
} else {
$subcommands[ $subcommand->get_name() ] = $subcommand->get_shortdesc();
}

$subcommands[ $subcommand->get_name() ] = $subcommand->get_shortdesc();
}

$max_len = self::get_max_len( array_keys( $subcommands ) );
$max_len = self::get_max_len( array_merge( array_keys( $subcommands ), array_keys( $disabled ) ) );

$lines = [];
foreach ( $subcommands as $name => $desc ) {
$lines[] = str_pad( $name, $max_len ) . "\t\t\t" . $desc;
}
foreach ( $disabled as $name => $data ) {
$suffix = $data['reason'] ? " (disabled: {$data['reason']})" : ' (disabled)';
$lines[] = str_pad( $name, $max_len ) . "\t\t\t" . $data['desc'] . $suffix;
}

return $lines;
}

private static function get_help_full( $command ) {
$out = self::get_help_as_string( $command );

if ( $command->can_have_subcommands() ) {
foreach ( $command->get_subcommands() as $subcommand ) {
if ( WP_CLI::get_runner()->is_command_disabled( $subcommand ) ) {
continue;
}
$out .= "\n---\n\n" . self::get_help_full( $subcommand );
}
}

return $out;
}

private static function get_help_as_string( $command ) {
$longdesc_with_links = self::parse_reference_links( $command->get_longdesc() );
$out = self::get_initial_markdown( $command, $longdesc_with_links );

$subcommands = '';
$column_subpattern = '[ \t]+[^\t]+\t+';
if ( preg_match( '/(^## SUBCOMMANDS[^\n]*\n+' . $column_subpattern . '.+?)(?:^##|\z)/ms', $out, $matches, PREG_OFFSET_CAPTURE ) ) {
$subcommands = $matches[1][0];
$subcommands_header = "## SUBCOMMANDS\n";
$out = substr_replace( $out, $subcommands_header, $matches[1][1], strlen( $subcommands ) );
}

$longdesc_sections = self::get_longdesc_sections( $longdesc_with_links );
$out .= $longdesc_sections;

$out = (string) preg_replace_callback( '/([^\n]+)\n: (.+?)(\n\n|$)/s', [ __CLASS__, 'rewrap_param_desc' ], $out );
$out = (string) preg_replace( '/^((?! |\t|##).)/m', "\t$1", $out );

$tab = str_repeat( ' ', 2 );
$out = str_replace( "\t", $tab, $out );

$wordwrap_width = Shell::columns();

$out = (string) preg_replace_callback(
'/^( *)([^\n]+)\n/m',
static function ( $matches ) use ( $wordwrap_width ) {
return $matches[1] . str_replace( "\n", "\n{$matches[1]}", wordwrap( $matches[2], $wordwrap_width - strlen( $matches[1] ) ) ) . "\n";
},
$out
);

if ( $subcommands ) {
$subcommands = (string) preg_replace_callback(
'/^(' . $column_subpattern . ')([^\n]+)\n/m',
static function ( $matches ) use ( $wordwrap_width, $tab ) {
$matches[1] = str_replace( "\t", $tab, $matches[1] );
$matches[2] = str_replace( "\t", $tab, $matches[2] );
$padding_len = strlen( $matches[1] );
$padding = str_repeat( ' ', $padding_len );
return $matches[1] . str_replace( "\n", "\n$padding", wordwrap( $matches[2], $wordwrap_width - $padding_len ) ) . "\n";
},
$subcommands
);

$out = str_replace( $subcommands_header, $subcommands, $out );
}

$out = (string) preg_replace( '/^## ([A-Z ]+)/m', WP_CLI::colorize( '%9\1%n' ), $out );

return $out;
}

private static function get_max_len( $strings ) {
$max_len = 0;
foreach ( $strings as $str ) {
Expand Down
Loading