|
2 | 2 | namespace WP_CLI_Org; |
3 | 3 |
|
4 | 4 | use WP_CLI; |
| 5 | +use Mustache_Engine; |
5 | 6 |
|
6 | 7 | /** |
7 | 8 | * WP-CLI commands for generating the docs |
8 | 9 | */ |
9 | 10 |
|
10 | 11 | /** |
11 | | - * Generate the homepage from the repo's README.md |
| 12 | + * Run all generation commands to generate full website. |
| 13 | + * |
| 14 | + * @when before_wp_load |
| 15 | + */ |
| 16 | +function generate() { |
| 17 | + generate_homepage(); |
| 18 | + generate_config(); |
| 19 | + generate_contributing(); |
| 20 | + generate_commands(); |
| 21 | + generate_docs(); |
| 22 | + generate_internal_api_docs(); |
| 23 | +} |
| 24 | +WP_CLI::add_command( 'website generate', 'WP_CLI_Org\generate' ); |
| 25 | + |
| 26 | +/** |
| 27 | + * Generate the homepage from WP-CLI's README.md |
12 | 28 | * |
13 | 29 | * @when before_wp_load |
14 | 30 | */ |
@@ -45,7 +61,55 @@ function generate_homepage() { |
45 | 61 | WP_CLI::add_command( 'website generate-homepage', '\WP_CLI_Org\generate_homepage' ); |
46 | 62 |
|
47 | 63 | /** |
48 | | - * Generate the contributing page from the repo's CONTRIBUTING.md |
| 64 | + * Generate the /config/ page. |
| 65 | + * |
| 66 | + * @when before_wp_load |
| 67 | + */ |
| 68 | +function generate_config() { |
| 69 | + $config_spec = invoke_wp_cli( 'wp cli param-dump' ); |
| 70 | + |
| 71 | + $out = ''; |
| 72 | + |
| 73 | + $global_args = array(); |
| 74 | + foreach ( $config_spec as $key => $details ) { |
| 75 | + if ( isset( $details['hidden'] ) || isset( $details['deprecated'] ) ) |
| 76 | + continue; |
| 77 | + |
| 78 | + if ( false !== $details['file'] ) { |
| 79 | + $config = "$key: " . $details['file']; |
| 80 | + } else { |
| 81 | + $config = ''; |
| 82 | + } |
| 83 | + |
| 84 | + if ( false !== $details['runtime'] ) { |
| 85 | + $flag = ( true === $details['runtime'] ) |
| 86 | + ? "--[no-]$key" |
| 87 | + : "--$key" . $details['runtime']; |
| 88 | + } else { |
| 89 | + $flag = ''; |
| 90 | + } |
| 91 | + |
| 92 | + $default = json_encode( $details['default'] ); |
| 93 | + |
| 94 | + $description = ( isset( $details['desc'] ) ) ? $details['desc'] : ''; |
| 95 | + |
| 96 | + $out .= render( 'config.mustache', compact( 'config', 'flag', 'default', 'description' ) ); |
| 97 | + if ( ! empty( $flag ) ) { |
| 98 | + $global_args[] = array( |
| 99 | + 'flag' => $flag, |
| 100 | + 'description' => $description, |
| 101 | + ); |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + file_put_contents( '_includes/param-list.html', $out ); |
| 106 | + file_put_contents( '_includes/global-parameters.html', render( 'global-parameters.mustache', array( 'args' => $global_args ) ) ); |
| 107 | + WP_CLI::success( 'Generated /config/' ); |
| 108 | +} |
| 109 | +WP_CLI::add_command( 'website generate-config', '\WP_CLI_Org\generate_config' ); |
| 110 | + |
| 111 | +/** |
| 112 | + * Generate the contributing page from WP-CLI's CONTRIBUTING.md |
49 | 113 | * |
50 | 114 | * @when before_wp_load |
51 | 115 | */ |
@@ -86,6 +150,187 @@ function generate_contributing() { |
86 | 150 | } |
87 | 151 | WP_CLI::add_command( 'website generate-contributing', '\WP_CLI_Org\generate_contributing' ); |
88 | 152 |
|
| 153 | +/** |
| 154 | + * Generate the /commands/ page. |
| 155 | + * |
| 156 | + * @when before_wp_load |
| 157 | + */ |
| 158 | +function generate_commands() { |
| 159 | + $wp = invoke_wp_cli( 'wp --skip-packages cli cmd-dump' ); |
| 160 | + |
| 161 | + foreach( $wp['subcommands'] as $k => $cmd ) { |
| 162 | + if ( in_array( $cmd['name'], array( 'website', 'api-dump' ) ) ) { |
| 163 | + unset( $wp['subcommands'][ $k ] ); |
| 164 | + } |
| 165 | + } |
| 166 | + $wp['subcommands'] = array_values( $wp['subcommands'] ); |
| 167 | + |
| 168 | + // generate main page |
| 169 | + file_put_contents( '_includes/cmd-list.html', render( 'cmd-list.mustache', $wp ) ); |
| 170 | + WP_CLI::log( 'Generated /commands/' ); |
| 171 | + |
| 172 | + foreach ( $wp['subcommands'] as $cmd ) { |
| 173 | + gen_cmd_pages( $cmd ); |
| 174 | + } |
| 175 | + WP_CLI::success( 'Generated all command pages.' ); |
| 176 | +} |
| 177 | +WP_CLI::add_command( 'website generate-commands', '\WP_CLI_Org\generate_commands' ); |
| 178 | + |
| 179 | +/** |
| 180 | + * Generate the /docs/ page. |
| 181 | + * |
| 182 | + * @when before_wp_load |
| 183 | + */ |
| 184 | +function generate_docs() { |
| 185 | + $docs = array( |
| 186 | + 'Guides' => array( |
| 187 | + 'Installing' => array(), |
| 188 | + 'Quick start' => array(), |
| 189 | + ), |
| 190 | + 'References' => array( |
| 191 | + 'Global parameters' => array( |
| 192 | + 'path' => '/config/', |
| 193 | + 'title' => 'Global parameters', |
| 194 | + 'description' => 'Variables defining how a command is executed, including which WordPress user the command is run as and which WordPress instance the command is run against.', |
| 195 | + ), |
| 196 | + 'Built-in commands' => array( |
| 197 | + 'path' => '/commands/', |
| 198 | + 'title' => 'Built-in commands', |
| 199 | + 'description' => 'Commands included in every copy of WP-CLI.', |
| 200 | + ), |
| 201 | + 'Package index' => array( |
| 202 | + 'path' => '/package-index/', |
| 203 | + 'title' => 'Package index', |
| 204 | + 'description' => 'Commands maintained and supported by the community.', |
| 205 | + ), |
| 206 | + 'Internal API' => array(), |
| 207 | + ), |
| 208 | + 'Contributing' => array(), |
| 209 | + 'Misc' => array(), |
| 210 | + ); |
| 211 | + foreach( glob( __DIR__ . '/docs/*/index.md' ) as $file ) { |
| 212 | + $contents = file_get_contents( $file ); |
| 213 | + $parts = explode( '---', $contents ); |
| 214 | + $header = $parts[1]; |
| 215 | + preg_match( '#category:\s(.+)#', $header, $matches ); |
| 216 | + if ( ! empty( $matches[1] ) && array_key_exists( $matches[1], $docs ) ) { |
| 217 | + $category = $matches[1]; |
| 218 | + } else { |
| 219 | + $category = 'Misc'; |
| 220 | + } |
| 221 | + preg_match( '#title:\s(.+)#', $header, $matches ); |
| 222 | + $title = ! empty( $matches[1] ) ? $matches[1] : ''; |
| 223 | + preg_match( '#description:\s(.+)#', $header, $matches ); |
| 224 | + $description = ! empty( $matches[1] ) ? $matches[1] : ''; |
| 225 | + if ( ! isset( $docs[ $category ][ $title ] ) ) { |
| 226 | + $docs[ $category ][ $title ] = array(); |
| 227 | + } |
| 228 | + $docs[ $category ][ $title ]['path'] = '/docs/' . basename( dirname( $file ) ) . '/'; |
| 229 | + $docs[ $category ][ $title ]['title'] = $title; |
| 230 | + $docs[ $category ][ $title ]['description'] = $description; |
| 231 | + } |
| 232 | + $out = ''; |
| 233 | + foreach( $docs as $category => $cat_docs ) { |
| 234 | + if ( empty( $cat_docs ) ) { |
| 235 | + continue; |
| 236 | + } |
| 237 | + $cat_slug = strtolower( $category ); |
| 238 | + $out .= '<h3 id="' . $cat_slug . '">' . $category . '</h3>' . PHP_EOL . PHP_EOL; |
| 239 | + $out .= '<ul>' . PHP_EOL; |
| 240 | + foreach( $cat_docs as $cat_doc ) { |
| 241 | + $out .= '<li><a href="' . $cat_doc['path'] . '"><strong>' . $cat_doc['title'] . '</strong></a>'; |
| 242 | + if ( ! empty( $cat_doc['description'] ) ) { |
| 243 | + $out .= ' - ' . $cat_doc['description']; |
| 244 | + } |
| 245 | + $out .= '</li>' . PHP_EOL; |
| 246 | + } |
| 247 | + $out .= '</ul>' . PHP_EOL . PHP_EOL; |
| 248 | + } |
| 249 | + |
| 250 | + file_put_contents( '_includes/doc-list.html', $out ); |
| 251 | + WP_CLI::success( 'Generated /docs/' ); |
| 252 | +} |
| 253 | +WP_CLI::add_command( 'website generate-docs', '\WP_CLI_Org\generate_docs' ); |
| 254 | + |
| 255 | +/** |
| 256 | + * Generate the /docs/internal-apis/ page. |
| 257 | + * |
| 258 | + * @when before_wp_load |
| 259 | + */ |
| 260 | +function generate_internal_api_docs() { |
| 261 | + $apis = invoke_wp_cli( 'wp api-dump' ); |
| 262 | + $categories = array( |
| 263 | + 'Registration' => array(), |
| 264 | + 'Output' => array(), |
| 265 | + 'Input' => array(), |
| 266 | + 'Execution' => array(), |
| 267 | + 'System' => array(), |
| 268 | + 'Misc' => array(), |
| 269 | + ); |
| 270 | + |
| 271 | + $prepare_api_slug = function( $full_name ) { |
| 272 | + $replacements = array( |
| 273 | + '()' => '', |
| 274 | + '::' => '-', |
| 275 | + '_' => '-', |
| 276 | + '\\' => '-', |
| 277 | + ); |
| 278 | + return strtolower( str_replace( array_keys( $replacements ), array_values( $replacements ), $full_name ) ); |
| 279 | + }; |
| 280 | + |
| 281 | + $prepare_code_block = function( $description ) { |
| 282 | + return preg_replace_callback( '#```(.+)```#Us', function( $matches ) { |
| 283 | + return str_replace( PHP_EOL, PHP_EOL . ' ', $matches[1] ); |
| 284 | + }, $description ); |
| 285 | + }; |
| 286 | + |
| 287 | + foreach( $apis as $api ) { |
| 288 | + |
| 289 | + $api['api_slug'] = $prepare_api_slug( $api['full_name'] ); |
| 290 | + $api['phpdoc']['long_description'] = $prepare_code_block( $api['phpdoc']['long_description'] ); |
| 291 | + |
| 292 | + if ( ! empty( $api['phpdoc']['parameters']['category'][0][0] ) |
| 293 | + && isset( $categories[ $api['phpdoc']['parameters']['category'][0][0] ] ) ) { |
| 294 | + $categories[ $api['phpdoc']['parameters']['category'][0][0] ][] = $api; |
| 295 | + } else { |
| 296 | + $categories['Misc'][] = $api; |
| 297 | + } |
| 298 | + } |
| 299 | + $out = '***' . PHP_EOL . PHP_EOL; |
| 300 | + |
| 301 | + foreach( $categories as $name => $apis ) { |
| 302 | + $out .= '## ' . $name . PHP_EOL . PHP_EOL; |
| 303 | + $out .= render( 'internal-api-list.mustache', array( 'apis' => $apis ) ); |
| 304 | + foreach( $apis as $i => $api ) { |
| 305 | + $api['category'] = $name; |
| 306 | + $api['related'] = $apis; |
| 307 | + $api['phpdoc']['parameters'] = array_map( function( $parameter ){ |
| 308 | + foreach( $parameter as $key => $values ) { |
| 309 | + if ( isset( $values[2] ) ) { |
| 310 | + $values[2] = str_replace( array( PHP_EOL ), array( '<br />' ), $values[2] ); |
| 311 | + $parameter[ $key ] = $values; |
| 312 | + } |
| 313 | + } |
| 314 | + return $parameter; |
| 315 | + }, $api['phpdoc']['parameters'] ); |
| 316 | + unset( $api['related'][ $i ] ); |
| 317 | + $api['related'] = array_values( $api['related'] ); |
| 318 | + $api['has_related'] = ! empty( $api['related'] ); |
| 319 | + $api_doc = render( 'internal-api.mustache', $api ); |
| 320 | + $path = "docs/internal-api/{$api['api_slug']}"; |
| 321 | + if ( ! is_dir( $path ) ) { |
| 322 | + mkdir( $path ); |
| 323 | + } |
| 324 | + file_put_contents( "$path/index.md", $api_doc ); |
| 325 | + } |
| 326 | + $out .= PHP_EOL . PHP_EOL; |
| 327 | + } |
| 328 | + |
| 329 | + file_put_contents( '_includes/internal-api-list.html', $out ); |
| 330 | + WP_CLI::success( 'Generated /docs/internal-api/' ); |
| 331 | +} |
| 332 | +WP_CLI::add_command( 'website generate-internal-api-docs', '\WP_CLI_Org\generate_internal_api_docs' ); |
| 333 | + |
89 | 334 | /** |
90 | 335 | * Dump the list of internal APIs, as JSON. |
91 | 336 | * |
@@ -233,3 +478,79 @@ function parse_docblock( $docblock ) { |
233 | 478 | $ret['long_description'] = $long_description; |
234 | 479 | return $ret; |
235 | 480 | } |
| 481 | + |
| 482 | +function gen_cmd_pages( $cmd, $parent = array() ) { |
| 483 | + $parent[] = $cmd['name']; |
| 484 | + |
| 485 | + $binding = $cmd; |
| 486 | + $binding['synopsis'] = implode( ' ', $parent ); |
| 487 | + $binding['path'] = implode( '/', $parent ); |
| 488 | + $path = '/commands/'; |
| 489 | + $binding['breadcrumbs'] = '[Commands](' . $path . ')'; |
| 490 | + foreach( $parent as $i => $p ) { |
| 491 | + $path .= $p . '/'; |
| 492 | + if ( $i < ( count( $parent ) - 1 ) ) { |
| 493 | + $binding['breadcrumbs'] .= " » [{$p}]({$path})"; |
| 494 | + } else { |
| 495 | + $binding['breadcrumbs'] .= " » {$p}"; |
| 496 | + } |
| 497 | + } |
| 498 | + $binding['has-subcommands'] = isset( $cmd['subcommands'] ) ? array(true) : false; |
| 499 | + |
| 500 | + if ( $cmd['longdesc'] ) { |
| 501 | + $docs = $cmd['longdesc']; |
| 502 | + $docs = htmlspecialchars( $docs, ENT_COMPAT, 'UTF-8' ); |
| 503 | + |
| 504 | + // decrease header level |
| 505 | + $docs = preg_replace( '/^## /m', '### ', $docs ); |
| 506 | + |
| 507 | + // escape `--` so that it doesn't get converted into `—` |
| 508 | + $docs = preg_replace( '/^(\[?)--/m', '\1\--', $docs ); |
| 509 | + $docs = preg_replace( '/^\s\s--/m', ' \1\--', $docs ); |
| 510 | + |
| 511 | + // hack to prevent double encoding in code blocks |
| 512 | + $docs = preg_replace( '/ < /', ' < ', $docs ); |
| 513 | + $docs = preg_replace( '/ > /', ' > ', $docs ); |
| 514 | + $docs = preg_replace( '/ <</', ' <<', $docs ); |
| 515 | + $docs = preg_replace( '/"/', '"', $docs ); |
| 516 | + |
| 517 | + // Strip global parameters -> added in footer |
| 518 | + $docs = preg_replace( '/#?## GLOBAL PARAMETERS.+/s', '', $docs ); |
| 519 | + |
| 520 | + $binding['docs'] = $docs; |
| 521 | + $binding['github_issues_link'] = 'https://github.com/wp-cli/wp-cli/issues?q=is%3Aopen+label%3A' . urlencode( 'command:' . str_replace( ' ', '-', $binding['synopsis'] ) ) . '+sort%3Aupdated-desc'; |
| 522 | + } |
| 523 | + |
| 524 | + $path = __DIR__ . "/commands/" . $binding['path']; |
| 525 | + if ( !is_dir( $path ) ) { |
| 526 | + mkdir( $path ); |
| 527 | + } |
| 528 | + file_put_contents( "$path/index.md", render( 'subcmd-list.mustache', $binding ) ); |
| 529 | + WP_CLI::log( 'Generated /commands/' . $binding['path'] . '/' ); |
| 530 | + |
| 531 | + if ( !isset( $cmd['subcommands'] ) ) |
| 532 | + return; |
| 533 | + |
| 534 | + foreach ( $cmd['subcommands'] as $subcmd ) { |
| 535 | + gen_cmd_pages( $subcmd, $parent ); |
| 536 | + } |
| 537 | +} |
| 538 | + |
| 539 | +function invoke_wp_cli( $cmd ) { |
| 540 | + ob_start(); |
| 541 | + system( "WP_CLI_CONFIG_PATH=/dev/null $cmd", $return_code ); |
| 542 | + $json = ob_get_clean(); |
| 543 | + |
| 544 | + if ( $return_code ) { |
| 545 | + echo "WP-CLI returned error code: $return_code\n"; |
| 546 | + exit(1); |
| 547 | + } |
| 548 | + |
| 549 | + return json_decode( $json, true ); |
| 550 | +} |
| 551 | + |
| 552 | +function render( $path, $binding ) { |
| 553 | + $m = new Mustache_Engine; |
| 554 | + $template = file_get_contents( __DIR__ . "/_templates/$path" ); |
| 555 | + return $m->render( $template, $binding ); |
| 556 | +} |
0 commit comments