mirror of
https://gh.wpcy.net/https://github.com/wp-cli/handbook.git
synced 2026-05-03 10:26:42 +08:00
823 lines
25 KiB
PHP
823 lines
25 KiB
PHP
<?php
|
|
/**
|
|
* WP-CLI commands to generate docs from the codebase.
|
|
*/
|
|
|
|
namespace WP_CLI\Handbook;
|
|
|
|
use Mustache_Engine;
|
|
use Reflection;
|
|
use WP_CLI;
|
|
use WP_CLI\Utils;
|
|
|
|
|
|
define( 'WP_CLI_HANDBOOK_PATH', dirname( __DIR__ ) );
|
|
|
|
|
|
/**
|
|
* @when before_wp_load
|
|
*/
|
|
class Command {
|
|
|
|
/**
|
|
* Regenerates all doc pages.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [--verbose]
|
|
* : If set will list command pages as they are generated.
|
|
*
|
|
* @subcommand gen-all
|
|
*/
|
|
public function gen_all( $args, $assoc_args ) {
|
|
// Warn if not invoked with null WP_CLI_CONFIG_PATH.
|
|
if ( '/dev/null' !== getenv( 'WP_CLI_CONFIG_PATH' ) ) {
|
|
WP_CLI::warning( "Should be invoked on the target WP-CLI with 'WP_CLI_CONFIG_PATH=/dev/null'." );
|
|
}
|
|
|
|
self::gen_api_docs();
|
|
self::gen_behat_docs();
|
|
self::gen_commands( $args, $assoc_args );
|
|
self::gen_commands_manifest();
|
|
self::gen_hb_manifest();
|
|
WP_CLI::success( 'Generated all doc pages.' );
|
|
}
|
|
|
|
private function prepare_api_slug( $full_name ) {
|
|
$replacements = [
|
|
'\\w+' => '',
|
|
'\\s' => '',
|
|
'\\d' => '',
|
|
'a-z' => '',
|
|
's?' => '',
|
|
'::' => '-',
|
|
'_' => '-',
|
|
'\\' => '-',
|
|
' ' => '-',
|
|
'.' => '-',
|
|
'|' => '-',
|
|
];
|
|
$full_name = strtolower( str_replace( array_keys( $replacements ), array_values( $replacements ), $full_name ) );
|
|
$full_name = preg_replace( '/[^a-zA-Z0-9-]/', '', $full_name );
|
|
$full_name = preg_replace( '/-+/', '-', $full_name );
|
|
$full_name = trim( $full_name, '-' );
|
|
return $full_name;
|
|
}
|
|
|
|
/**
|
|
* Generates internal API doc pages.
|
|
*
|
|
* @subcommand gen-api-docs
|
|
*/
|
|
public function gen_api_docs() {
|
|
$apis = $this->get_internal_apis();
|
|
|
|
$categories = [
|
|
'Registration' => [],
|
|
'Output' => [],
|
|
'Input' => [],
|
|
'Execution' => [],
|
|
'System' => [],
|
|
'Misc' => [],
|
|
];
|
|
|
|
foreach ( $apis as $api ) {
|
|
|
|
$api['api_slug'] = $this->prepare_api_slug( $api['full_name'] );
|
|
|
|
if ( ! empty( $api['phpdoc']['parameters']['category'][0][0] )
|
|
&& isset( $categories[ $api['phpdoc']['parameters']['category'][0][0] ] ) ) {
|
|
$categories[ $api['phpdoc']['parameters']['category'][0][0] ][] = $api;
|
|
} else {
|
|
$categories['Misc'][] = $api;
|
|
}
|
|
}
|
|
$out = <<<'EOT'
|
|
# Internal API
|
|
|
|
WP-CLI includes a number of utilities which are considered stable and meant to be used by commands.
|
|
|
|
This also means functions and methods not listed here are considered part of the private API. They may change or disappear at any time.
|
|
|
|
*Internal API documentation is generated from the WP-CLI codebase on every release. To suggest improvements, please submit a pull request.*
|
|
|
|
***
|
|
|
|
EOT;
|
|
|
|
self::empty_dir( WP_CLI_HANDBOOK_PATH . '/internal-api/' );
|
|
|
|
foreach ( $categories as $name => $apis ) {
|
|
$out .= '## ' . $name . PHP_EOL . PHP_EOL;
|
|
$out .= self::render( 'internal-api-list.mustache', [ 'apis' => $apis ] );
|
|
foreach ( $apis as $i => $api ) {
|
|
$api['category'] = $name;
|
|
$api['related'] = $apis;
|
|
$api['phpdoc']['parameters'] = array_map(
|
|
function ( $parameter ) {
|
|
foreach ( $parameter as $key => $values ) {
|
|
if ( isset( $values[2] ) ) {
|
|
$values[2] = str_replace( array( PHP_EOL ), array( '<br />' ), $values[2] );
|
|
$parameter[ $key ] = $values;
|
|
}
|
|
}
|
|
return $parameter;
|
|
},
|
|
$api['phpdoc']['parameters']
|
|
);
|
|
unset( $api['related'][ $i ] );
|
|
$api['related'] = array_values( $api['related'] );
|
|
$api['has_related'] = ! empty( $api['related'] );
|
|
|
|
$api_doc = self::render( 'internal-api.mustache', $api );
|
|
$path = WP_CLI_HANDBOOK_PATH . "/internal-api/{$api['api_slug']}.md";
|
|
if ( ! is_dir( dirname( $path ) ) ) {
|
|
mkdir( dirname( $path ) );
|
|
}
|
|
file_put_contents( $path, $api_doc );
|
|
}
|
|
$out .= PHP_EOL . PHP_EOL;
|
|
}
|
|
|
|
file_put_contents( WP_CLI_HANDBOOK_PATH . '/internal-api.md', $out );
|
|
WP_CLI::success( 'Generated internal-api/' );
|
|
}
|
|
|
|
/**
|
|
* Generates Behat steps doc pages.
|
|
*
|
|
* @subcommand gen-behat-docs
|
|
*/
|
|
public function gen_behat_docs() {
|
|
$apis = $this->get_behat_steps();
|
|
|
|
$categories = [
|
|
'Given' => [],
|
|
'When' => [],
|
|
'Then' => [],
|
|
];
|
|
|
|
foreach ( $apis as $api ) {
|
|
|
|
$api['api_slug'] = $this->prepare_api_slug( $api['full_name'] );
|
|
|
|
if ( isset( $api['phpdoc']['parameters']['Given'] ) ) {
|
|
$categories['Given'][] = $api;
|
|
} elseif ( isset( $api['phpdoc']['parameters']['When'] ) ) {
|
|
$categories['When'][] = $api;
|
|
} elseif ( isset( $api['phpdoc']['parameters']['Then'] ) ) {
|
|
$categories['Then'][] = $api;
|
|
}
|
|
}
|
|
$out = <<<'EOT'
|
|
# Behat Steps
|
|
|
|
WP-CLI makes use of a Behat-based testing framework and provides a set of custom step definitions to write feature tests.
|
|
|
|
*Behat steps documentation is generated from the WP-CLI codebase on every release. To suggest improvements, please submit a pull request.*
|
|
|
|
***
|
|
|
|
EOT;
|
|
|
|
self::empty_dir( WP_CLI_HANDBOOK_PATH . '/behat-steps/' );
|
|
|
|
foreach ( $categories as $name => $apis ) {
|
|
$out .= '## ' . $name . PHP_EOL . PHP_EOL;
|
|
$out .= self::render( 'behat-steps-list.mustache', [ 'apis' => $apis ] );
|
|
foreach ( $apis as $i => $api ) {
|
|
$api['category'] = $name;
|
|
$api['related'] = $apis;
|
|
$api['phpdoc']['parameters'] = array_map(
|
|
function ( $parameter ) {
|
|
foreach ( $parameter as $key => $values ) {
|
|
if ( isset( $values[2] ) ) {
|
|
$values[2] = str_replace( array( PHP_EOL ), array( '<br />' ), $values[2] );
|
|
$parameter[ $key ] = $values;
|
|
}
|
|
}
|
|
return $parameter;
|
|
},
|
|
$api['phpdoc']['parameters']
|
|
);
|
|
unset( $api['related'][ $i ] );
|
|
$api['related'] = array_values( $api['related'] );
|
|
$api['has_related'] = ! empty( $api['related'] );
|
|
|
|
$api_doc = self::render( 'behat-steps.mustache', $api );
|
|
$path = WP_CLI_HANDBOOK_PATH . "/behat-steps/{$api['api_slug']}.md";
|
|
if ( ! is_dir( dirname( $path ) ) ) {
|
|
mkdir( dirname( $path ) );
|
|
}
|
|
file_put_contents( $path, $api_doc );
|
|
}
|
|
$out .= PHP_EOL . PHP_EOL;
|
|
}
|
|
|
|
file_put_contents( WP_CLI_HANDBOOK_PATH . '/behat-steps.md', $out );
|
|
WP_CLI::success( 'Generated behat-steps/' );
|
|
}
|
|
|
|
/**
|
|
* Generates all command pages.
|
|
*
|
|
* ## OPTIONS
|
|
*
|
|
* [--verbose]
|
|
* : If set will list command pages as they are generated.
|
|
*
|
|
* @subcommand gen-commands
|
|
*/
|
|
public function gen_commands( $args, $assoc_args ) {
|
|
// Check invoked with packages directory set to `bin/packages'.
|
|
if ( ! preg_match( '/bin\/packages\/?$/', getenv( 'WP_CLI_PACKAGES_DIR' ) ) ) {
|
|
WP_CLI::error( "Needs to be invoked on the target WP-CLI with 'WP_CLI_PACKAGES_DIR=bin/packages'." );
|
|
}
|
|
|
|
// Check non-bundled commands installed.
|
|
$runner = WP_CLI::get_runner();
|
|
$have_nonbundled_installed = true;
|
|
foreach ( [ 'admin', 'find', 'profile', 'dist-archive' ] as $cmd ) {
|
|
$have_nonbundled_installed = $have_nonbundled_installed && is_array( $runner->find_command_to_run( [ $cmd ] ) );
|
|
}
|
|
if ( ! $have_nonbundled_installed ) {
|
|
WP_CLI::error( sprintf( "Install non-bundled packages by running '%s' first.", 'bin/install_packages.sh' ) );
|
|
}
|
|
|
|
self::empty_dir( WP_CLI_HANDBOOK_PATH . '/commands/' );
|
|
|
|
$wp = WP_CLI::runcommand(
|
|
'cli cmd-dump',
|
|
[
|
|
'launch' => false,
|
|
'return' => 'stdout',
|
|
'parse' => 'json',
|
|
]
|
|
);
|
|
|
|
$verbose = Utils\get_flag_value( $assoc_args, 'verbose', false );
|
|
|
|
foreach ( $wp['subcommands'] as $cmd ) {
|
|
if ( in_array( $cmd['name'], [ 'website', 'handbook' ], true ) ) {
|
|
continue;
|
|
}
|
|
self::gen_cmd_pages( $cmd, [] /*parent*/, $verbose );
|
|
}
|
|
|
|
WP_CLI::success( 'Generated all command pages.' );
|
|
}
|
|
|
|
/**
|
|
* Update the commands data array with new data
|
|
*/
|
|
private static function update_commands_data( $command, &$commands_data, $full ) {
|
|
$reflection = new \ReflectionClass( $command );
|
|
$repo_url = '';
|
|
if ( 'help' === substr( $full, 0, 4 )
|
|
|| 'cli' === substr( $full, 0, 3 ) ) {
|
|
$repo_url = 'https://github.com/wp-cli/wp-cli';
|
|
}
|
|
if ( $reflection->hasProperty( 'when_invoked' ) ) {
|
|
$filename = '';
|
|
$when_invoked = $reflection->getProperty( 'when_invoked' );
|
|
$when_invoked->setAccessible( true );
|
|
$closure = $when_invoked->getValue( $command );
|
|
$closure_reflection = new \ReflectionFunction( $closure );
|
|
// PHP stores use clause arguments of closures as static variables internally - see https://bugs.php.net/bug.php?id=71250
|
|
$static = $closure_reflection->getStaticVariables();
|
|
if ( is_array( $static ) && isset( $static['callable'] ) ) {
|
|
// See `CommandFactory::create_subcommand()`.
|
|
if ( is_array( $static['callable'] ) && isset( $static['callable'][0] ) ) {
|
|
$reflection_class = new \ReflectionClass( $static['callable'][0] );
|
|
$filename = $reflection_class->getFileName();
|
|
} elseif ( is_callable( $static['callable'] ) ) {
|
|
$reflection_func = new \ReflectionFunction( $static['callable'] );
|
|
$filename = $reflection_func->getFileName();
|
|
}
|
|
}
|
|
if ( $filename ) {
|
|
preg_match( '#(?:vendor/wp-cli/|wp-cli-dev/)([^/]+)#', $filename, $matches );
|
|
if ( ! empty( $matches[1] ) ) {
|
|
$repo_url = 'https://github.com/wp-cli/' . $matches[1];
|
|
}
|
|
} else {
|
|
WP_CLI::error( 'No callable for: ' . var_export( $static, true ) );
|
|
}
|
|
}
|
|
foreach ( $command->get_subcommands() as $subcommand ) {
|
|
$sub_full = trim( $full . ' ' . $subcommand->get_name() );
|
|
self::update_commands_data( $subcommand, $commands_data, $sub_full );
|
|
if ( '' === $repo_url && isset( $commands_data[ $sub_full ]['repo_url'] ) ) {
|
|
$repo_url = $commands_data[ $sub_full ]['repo_url'];
|
|
}
|
|
}
|
|
if ( $repo_url ) {
|
|
$commands_data[ $full ] = [
|
|
'repo_url' => $repo_url,
|
|
];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generates a manifest document of all command pages.
|
|
*
|
|
* @subcommand gen-commands-manifest
|
|
*/
|
|
public function gen_commands_manifest() {
|
|
$manifest = [];
|
|
$paths = [
|
|
WP_CLI_HANDBOOK_PATH . '/commands/*.md',
|
|
WP_CLI_HANDBOOK_PATH . '/commands/*/*.md',
|
|
WP_CLI_HANDBOOK_PATH . '/commands/*/*/*.md',
|
|
];
|
|
$commands_data = [];
|
|
foreach ( WP_CLI::get_root_command()->get_subcommands() as $command ) {
|
|
self::update_commands_data( $command, $commands_data, $command->get_name() );
|
|
}
|
|
foreach ( $paths as $path ) {
|
|
foreach ( glob( $path ) as $file ) {
|
|
$slug = basename( $file, '.md' );
|
|
$cmd_path = str_replace( [ WP_CLI_HANDBOOK_PATH . '/commands/', '.md' ], '', $file );
|
|
$title = '';
|
|
$contents = file_get_contents( $file );
|
|
if ( preg_match( '/^#\swp\s(.+)/', $contents, $matches ) ) {
|
|
$title = $matches[1];
|
|
}
|
|
$parent = null;
|
|
if ( stripos( $cmd_path, '/' ) ) {
|
|
$bits = explode( '/', $cmd_path );
|
|
array_pop( $bits );
|
|
$parent = implode( '/', $bits );
|
|
}
|
|
$ignored = [
|
|
'doctor',
|
|
'google-sitemap',
|
|
'maintenance',
|
|
'maintenance/release',
|
|
'super-cache',
|
|
];
|
|
if ( in_array( $slug, $ignored, true ) || in_array( $parent, $ignored, true ) ) {
|
|
continue;
|
|
}
|
|
|
|
$manifest[ $cmd_path ] = [
|
|
'title' => $title,
|
|
'slug' => $slug,
|
|
'cmd_path' => $cmd_path,
|
|
'parent' => $parent,
|
|
'markdown_source' => sprintf(
|
|
'https://github.com/wp-cli/handbook/blob/main/commands/%s.md',
|
|
$cmd_path
|
|
),
|
|
];
|
|
if ( ! empty( $commands_data[ $title ] ) ) {
|
|
$manifest[ $cmd_path ] = array_merge( $manifest[ $cmd_path ], $commands_data[ $title ] );
|
|
}
|
|
}
|
|
}
|
|
file_put_contents( WP_CLI_HANDBOOK_PATH . '/bin/commands-manifest.json', json_encode( $manifest, JSON_PRETTY_PRINT ) );
|
|
$count = count( $manifest );
|
|
WP_CLI::success( "Generated bin/commands-manifest.json of {$count} commands" );
|
|
}
|
|
|
|
/**
|
|
* Generates a manifest document of all handbook pages.
|
|
*
|
|
* @subcommand gen-hb-manifest
|
|
*/
|
|
public function gen_hb_manifest() {
|
|
$manifest = [];
|
|
|
|
$ignored_dirs = [
|
|
'.git',
|
|
'.github',
|
|
'bin',
|
|
'commands',
|
|
'vendor',
|
|
];
|
|
|
|
$files = new \RecursiveIteratorIterator(
|
|
new \RecursiveCallbackFilterIterator(
|
|
new \RecursiveDirectoryIterator( WP_CLI_HANDBOOK_PATH, \RecursiveDirectoryIterator::SKIP_DOTS ),
|
|
static function ( $file ) use ( $ignored_dirs ) {
|
|
/** @var SplFileInfo $file */
|
|
|
|
if ( $file->isDir() && in_array( $file->getBasename(), $ignored_dirs, true ) ) {
|
|
return false;
|
|
}
|
|
|
|
if ( $file->isFile() && $file->getExtension() !== 'md' ) {
|
|
return false;
|
|
}
|
|
|
|
if ( 'README.md' === $file->getBasename() ) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
),
|
|
\RecursiveIteratorIterator::CHILD_FIRST
|
|
);
|
|
|
|
foreach ( $files as $file ) {
|
|
if ( $file->isDir() ) {
|
|
continue;
|
|
}
|
|
|
|
$rel_path = str_replace( WP_CLI_HANDBOOK_PATH . '/', '', $file->getPathname() );
|
|
|
|
$path = explode( '/', $rel_path );
|
|
array_pop( $path );
|
|
|
|
$parent = ! empty( $path ) ? end( $path ) : null;
|
|
|
|
$path = implode( '/', $path );
|
|
|
|
$slug = $file->getBasename( '.md' );
|
|
|
|
$title = '';
|
|
$contents = file_get_contents( $file->getPathname() );
|
|
if ( preg_match( '/^#\s(.+)/', $contents, $matches ) ) {
|
|
$title = $matches[1];
|
|
}
|
|
$manifest[ $slug ] = [
|
|
'title' => $title,
|
|
'slug' => 'index' === $slug ? 'handbook' : $slug,
|
|
'markdown_source' => sprintf(
|
|
'https://github.com/wp-cli/handbook/blob/main/%s',
|
|
$rel_path
|
|
),
|
|
'parent' => $parent,
|
|
];
|
|
}
|
|
|
|
ksort( $manifest );
|
|
|
|
file_put_contents( WP_CLI_HANDBOOK_PATH . '/bin/handbook-manifest.json', json_encode( $manifest, JSON_PRETTY_PRINT ) );
|
|
WP_CLI::success( 'Generated bin/handbook-manifest.json' );
|
|
}
|
|
|
|
private function get_internal_apis() {
|
|
$apis = [];
|
|
$functions = get_defined_functions();
|
|
foreach ( $functions['user'] as $function ) {
|
|
$reflection = new \ReflectionFunction( $function );
|
|
$phpdoc = $reflection->getDocComment();
|
|
if ( false === stripos( $phpdoc, '@access public' ) ) {
|
|
continue;
|
|
}
|
|
$apis[] = self::get_simple_representation( $reflection );
|
|
}
|
|
|
|
$classes = get_declared_classes();
|
|
foreach ( $classes as $class ) {
|
|
if ( false === stripos( $class, 'WP_CLI' ) ) {
|
|
continue;
|
|
}
|
|
|
|
$reflection = new \ReflectionClass( $class );
|
|
foreach ( $reflection->getMethods() as $method ) {
|
|
$method_reflection = new \ReflectionMethod( $method->class, $method->name );
|
|
$phpdoc = $method_reflection->getDocComment();
|
|
if ( false === stripos( $phpdoc, '@access public' ) ) {
|
|
continue;
|
|
}
|
|
$apis[] = self::get_simple_representation( $method_reflection );
|
|
}
|
|
}
|
|
|
|
return $apis;
|
|
}
|
|
|
|
private function get_behat_steps() {
|
|
$apis = [];
|
|
$classes = [
|
|
'\WP_CLI\Tests\Context\FeatureContext',
|
|
];
|
|
|
|
foreach ( $classes as $class ) {
|
|
if ( false === stripos( $class, 'WP_CLI' ) ) {
|
|
continue;
|
|
}
|
|
|
|
$reflection = new \ReflectionClass( $class );
|
|
foreach ( $reflection->getMethods() as $method ) {
|
|
$method_reflection = new \ReflectionMethod( $method->class, $method->name );
|
|
$phpdoc = $method_reflection->getDocComment();
|
|
if ( false === stripos( $phpdoc, '@access public' ) ) {
|
|
continue;
|
|
}
|
|
$apis[] = self::get_simple_representation( $method_reflection );
|
|
}
|
|
}
|
|
|
|
return $apis;
|
|
}
|
|
|
|
private static function gen_cmd_pages( $cmd, $parent = [], $verbose = false ) { // phpcs:ignore Universal.NamingConventions.NoReservedKeywordParameterNames.parentFound
|
|
$parent[] = $cmd['name'];
|
|
|
|
static $params;
|
|
if ( ! isset( $params ) ) {
|
|
$params = WP_CLI::runcommand(
|
|
'cli param-dump',
|
|
[
|
|
'launch' => false,
|
|
'return' => 'stdout',
|
|
'parse' => 'json',
|
|
]
|
|
);
|
|
// Preserve positioning of 'url' param.
|
|
$url_param = $params['url'];
|
|
unset( $params['url'] );
|
|
$new_params = [];
|
|
foreach ( $params as $param => $meta ) {
|
|
$new_params[ $param ] = $meta;
|
|
if ( 'path' === $param ) {
|
|
$new_params['url'] = $url_param;
|
|
}
|
|
}
|
|
$params = $new_params;
|
|
}
|
|
|
|
$binding = $cmd;
|
|
$binding['synopsis'] = implode( ' ', $parent );
|
|
$binding['path'] = implode( '/', $parent );
|
|
$path = '/commands/';
|
|
$binding['breadcrumbs'] = '[Commands](' . $path . ')';
|
|
foreach ( $parent as $i => $p ) {
|
|
$path .= $p . '/';
|
|
if ( $i < ( count( $parent ) - 1 ) ) {
|
|
$binding['breadcrumbs'] .= " » [{$p}]({$path})";
|
|
} else {
|
|
$binding['breadcrumbs'] .= " » {$p}";
|
|
}
|
|
}
|
|
$binding['has-subcommands'] = isset( $cmd['subcommands'] ) ? [ true ] : false;
|
|
|
|
$hook_name = $cmd['hook'];
|
|
$hook_description = $hook_name ? Utils\get_hook_description( $hook_name ) : null;
|
|
if ( $hook_description && 'after_wp_load' !== $hook_name ) {
|
|
if ( $binding['has-subcommands'] ) {
|
|
$binding['description'] .= "\n\nUnless overridden, these commands run on the `$hook_name` hook, $hook_description";
|
|
} else {
|
|
$binding['description'] .= "\n\nThis command runs on the `$hook_name` hook, $hook_description";
|
|
}
|
|
}
|
|
|
|
if ( $cmd['longdesc'] ) {
|
|
$docs = $cmd['longdesc'];
|
|
$docs = htmlspecialchars( $docs, ENT_COMPAT, 'UTF-8' );
|
|
|
|
// Decrease header level.
|
|
$docs = preg_replace( '/^## /m', '### ', $docs );
|
|
|
|
// Escape `--` so that it doesn't get converted into `—`.
|
|
$docs = preg_replace( '/^(\[?)--/m', '\1\--', $docs );
|
|
$docs = preg_replace( '/^\s\s--/m', ' \1\--', $docs );
|
|
|
|
// Remove word wrapping from docs
|
|
// Match words, '().,;', and --arg before/after the newline.
|
|
$bits = explode( "\n", $docs );
|
|
$in_yaml_doc = false;
|
|
$in_code_bloc = false;
|
|
$total_bits = count( $bits );
|
|
for ( $i = 0; $i < $total_bits; $i++ ) {
|
|
if ( ! isset( $bits[ $i ] ) || ! isset( $bits[ $i + 1 ] ) ) {
|
|
continue;
|
|
}
|
|
if ( '---' === $bits[ $i ] || '\---' === $bits[ $i ] ) {
|
|
$in_yaml_doc = ! $in_yaml_doc;
|
|
}
|
|
if ( '```' === $bits[ $i ] ) {
|
|
$in_code_bloc = ! $in_code_bloc;
|
|
}
|
|
if ( $in_yaml_doc || $in_code_bloc ) {
|
|
continue;
|
|
}
|
|
|
|
if ( preg_match( '#([\w\(\)\.\,\;]|[`]{1})$#', $bits[ $i ] )
|
|
&& preg_match( '#^([\w\(\)\.\,\;`]|\\\--[\w]|[`]{1})#', $bits[ $i + 1 ] ) ) {
|
|
$bits[ $i ] .= ' ' . $bits[ $i + 1 ];
|
|
unset( $bits[ $i + 1 ] );
|
|
--$i;
|
|
$bits = array_values( $bits );
|
|
}
|
|
}
|
|
$docs = implode( "\n", $bits );
|
|
|
|
// Hack to prevent double encoding in code blocks.
|
|
$docs = preg_replace( '/ < /', ' < ', $docs );
|
|
$docs = preg_replace( '/ > /', ' > ', $docs );
|
|
$docs = preg_replace( '/ <</', ' <<', $docs );
|
|
$docs = preg_replace( '/"/', '"', $docs );
|
|
$docs = preg_replace( '/wp> /', 'wp> ', $docs );
|
|
$docs = preg_replace( '/2>\//', '2>/', $docs );
|
|
$docs = preg_replace( '/=>/', '=>', $docs );
|
|
$docs = preg_replace( '/ && /', ' && ', $docs );
|
|
|
|
$global_parameters = <<<'EOT'
|
|
These [global parameters](https://make.wordpress.org/cli/handbook/config/) have the same behavior across all commands and affect how WP-CLI interacts with WordPress.
|
|
|
|
| **Argument** | **Description** |
|
|
|:----------------|:-----------------------------|
|
|
EOT;
|
|
foreach ( $params as $param => $meta ) {
|
|
if ( false === $meta['runtime']
|
|
|| empty( $meta['desc'] )
|
|
|| ! empty( $meta['deprecated'] ) ) {
|
|
continue;
|
|
}
|
|
$param_arg = '--' . $param;
|
|
if ( ! empty( $meta['runtime'] ) && true !== $meta['runtime'] ) {
|
|
$param_arg .= $meta['runtime'];
|
|
}
|
|
if ( 'color' === $param ) {
|
|
$param_arg = '--[no-]color';
|
|
}
|
|
$global_parameters .= PHP_EOL . '| `' . str_replace( '|', '\\|', $param_arg ) . '` | ' . str_replace( '|', '\\|', $meta['desc'] ) . ' |';
|
|
}
|
|
|
|
// Replace Global parameters with a nice table.
|
|
if ( $binding['has-subcommands'] ) {
|
|
$replace_global = '';
|
|
} else {
|
|
$replace_global = '$1' . PHP_EOL . PHP_EOL . $global_parameters;
|
|
}
|
|
$docs = preg_replace( '/(#?## GLOBAL PARAMETERS).+/s', $replace_global, $docs );
|
|
|
|
$binding['docs'] = $docs;
|
|
}
|
|
|
|
$path = dirname( __DIR__ ) . '/commands/' . $binding['path'];
|
|
if ( ! is_dir( dirname( $path ) ) ) {
|
|
mkdir( dirname( $path ) );
|
|
}
|
|
file_put_contents( "$path.md", self::render( 'subcmd-list.mustache', $binding ) );
|
|
if ( $verbose ) {
|
|
WP_CLI::log( 'Generated commands/' . $binding['path'] . '/' );
|
|
}
|
|
|
|
if ( ! isset( $cmd['subcommands'] ) ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( $cmd['subcommands'] as $subcmd ) {
|
|
self::gen_cmd_pages( $subcmd, $parent, $verbose );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a simple representation of a function or method
|
|
*
|
|
* @param Reflection
|
|
* @return array
|
|
*/
|
|
private static function get_simple_representation( $reflection ) {
|
|
$signature = $reflection->getName();
|
|
$parameters = [];
|
|
foreach ( $reflection->getParameters() as $parameter ) {
|
|
$parameter_signature = '$' . $parameter->getName();
|
|
if ( $parameter->isOptional() && $parameter->isDefaultValueAvailable() ) {
|
|
$default_value = $parameter->getDefaultValue();
|
|
if ( false === $default_value ) {
|
|
$parameter_signature .= ' = false';
|
|
} elseif ( [] === $default_value ) {
|
|
$parameter_signature .= ' = []';
|
|
} elseif ( '' === $default_value ) {
|
|
$parameter_signature .= " = ''";
|
|
} elseif ( null === $default_value ) {
|
|
$parameter_signature .= ' = null';
|
|
} elseif ( true === $default_value ) {
|
|
$parameter_signature .= ' = true';
|
|
} else {
|
|
$parameter_signature .= ' = ' . $default_value;
|
|
}
|
|
}
|
|
$parameters[] = $parameter_signature;
|
|
}
|
|
if ( ! empty( $parameters ) ) {
|
|
$signature = $signature . '( ' . implode( ', ', $parameters ) . ' )';
|
|
} else {
|
|
$signature = $signature . '()';
|
|
}
|
|
$phpdoc = self::parse_docblock( $reflection->getDocComment() );
|
|
$type = strtolower( str_replace( 'Reflection', '', get_class( $reflection ) ) );
|
|
$class = '';
|
|
switch ( $type ) {
|
|
case 'method':
|
|
$separator = $reflection->isStatic() ? '::' : '->';
|
|
$class = $reflection->class;
|
|
$full_name = $class . $separator . $reflection->getName();
|
|
$signature = $class . $separator . $signature;
|
|
break;
|
|
case 'function':
|
|
default:
|
|
$full_name = $reflection->getName();
|
|
break;
|
|
}
|
|
|
|
if ( isset( $phpdoc['behat_step'] ) ) {
|
|
$full_name = $phpdoc['behat_step'];
|
|
}
|
|
|
|
return [
|
|
'phpdoc' => $phpdoc,
|
|
'type' => $type,
|
|
'signature' => $signature,
|
|
'short_name' => $reflection->getShortName(),
|
|
'full_name' => $full_name,
|
|
'class' => $class,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Parse PHPDoc into a structured representation.
|
|
*
|
|
* @param string $docblock
|
|
* @return array
|
|
*/
|
|
private static function parse_docblock( $docblock ) {
|
|
$ret = [
|
|
'description' => '',
|
|
'parameters' => [],
|
|
];
|
|
$extra_line = '';
|
|
$in_param = false;
|
|
foreach ( preg_split( "/(\r?\n)/", $docblock ) as $line ) {
|
|
if ( preg_match( '/^(?=\s+?\*[^\/])(.+)/', $line, $matches ) ) {
|
|
$info = trim( $matches[1] );
|
|
$info = preg_replace( '/^(\*\s+?)/', '', $info );
|
|
if ( $in_param ) {
|
|
list( $param_name, $key ) = $in_param;
|
|
$ret['parameters'][ $param_name ][ $key ][2] .= PHP_EOL . $info;
|
|
if ( '}' === substr( $info, -1 ) ) {
|
|
$in_param = false;
|
|
}
|
|
} elseif ( '@' !== $info[0] ) {
|
|
$ret['description'] .= PHP_EOL . "{$extra_line}{$info}";
|
|
} else {
|
|
preg_match( '/@(\w+)/', $info, $matches );
|
|
$param_name = $matches[1];
|
|
$value = str_replace( "@$param_name ", '', $info );
|
|
|
|
if ( in_array( $param_name, [ 'Given', 'Then', 'When' ], true ) ) {
|
|
$ret['behat_step'] = "$param_name $value";
|
|
}
|
|
|
|
if ( ! isset( $ret['parameters'][ $param_name ] ) ) {
|
|
$ret['parameters'][ $param_name ] = [];
|
|
}
|
|
$ret['parameters'][ $param_name ][] = preg_split( '/[\s]+/', $value, 3 );
|
|
end( $ret['parameters'][ $param_name ] );
|
|
$key = key( $ret['parameters'][ $param_name ] );
|
|
reset( $ret['parameters'][ $param_name ] );
|
|
if ( ! empty( $ret['parameters'][ $param_name ][ $key ][2] )
|
|
&& '{' === substr( $ret['parameters'][ $param_name ][ $key ][2], -1 ) ) {
|
|
$in_param = [ $param_name, $key ];
|
|
}
|
|
}
|
|
$extra_line = '';
|
|
} else {
|
|
$extra_line .= PHP_EOL;
|
|
}
|
|
}
|
|
$ret['description'] = str_replace( '\/', '/', trim( $ret['description'], PHP_EOL ) );
|
|
$bits = explode( PHP_EOL, $ret['description'] );
|
|
$short_desc = [ array_shift( $bits ) ];
|
|
while ( isset( $bits[0] ) && ! empty( $bits[0] ) ) {
|
|
$short_desc[] = array_shift( $bits );
|
|
}
|
|
$ret['short_description'] = trim( implode( ' ', $short_desc ) );
|
|
$long_description = trim( implode( PHP_EOL, $bits ), PHP_EOL );
|
|
$ret['long_description'] = $long_description;
|
|
return $ret;
|
|
}
|
|
|
|
private static function render( $path, $binding ) {
|
|
$m = new Mustache_Engine();
|
|
$template = file_get_contents( WP_CLI_HANDBOOK_PATH . "/bin/templates/$path" );
|
|
return $m->render( $template, $binding );
|
|
}
|
|
|
|
/**
|
|
* Removes existing contents of given directory.
|
|
*
|
|
* @param string $dir Name of directory to empty.
|
|
*/
|
|
private static function empty_dir( $dir ) {
|
|
$cmd = Utils\esc_cmd( 'rm -rf %s', $dir );
|
|
$pr = WP_CLI::launch( $cmd, false /*exit_on_error*/, true /*return_detailed*/ ); // Won't fail if directory doesn't exist.
|
|
if ( $pr->return_code ) {
|
|
WP_CLI::error( sprintf( 'Failed to `%s`: (%d) %s', $cmd, $pr->return_code, $pr->stderr ) );
|
|
}
|
|
if ( ! mkdir( $dir ) ) {
|
|
$error = error_get_last();
|
|
WP_CLI::error( sprintf( "Failed to create '%s' directory: %s", $dir, $error['message'] ) );
|
|
}
|
|
WP_CLI::log( sprintf( "Removed existing contents of '%s'", $dir ) );
|
|
}
|
|
}
|
|
|
|
WP_CLI::add_command( 'handbook', '\WP_CLI\Handbook\Command' );
|