Skip to content

Commit 17835d1

Browse files
Merge pull request #261 from wp-cli/2768-wp-cli
Use WP-CLI to generate the website
2 parents d382d61 + 48f7e93 commit 17835d1

File tree

3 files changed

+350
-19
lines changed

3 files changed

+350
-19
lines changed

README.md

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1-
These files comprise wp-cli.org*. However, all command documentation is dynamically generated from the PHPDoc for each command. If you'd like to suggest command documentation changes, please submit a pull request against the [primary repo](https://github.com/wp-cli/wp-cli). Command documentation changes are deployed to the website with each release.
1+
These files comprise wp-cli.org*.
22

3-
### Setup
3+
All command documentation is dynamically generated from the PHPDoc for each command. If you'd like to suggest command documentation changes, please submit a pull request against the [primary repo](https://github.com/wp-cli/wp-cli). Command documentation changes are deployed to the website with each release.
44

5-
1. Install [Composer](http://getcomposer.org/).
5+
### Regenerate website
66

7-
2. Install dev dependencies with `composer install --dev`
8-
9-
3. Build:
7+
The website uses a series of WP-CLI commands to generate its documentation:
108

119
```bash
12-
vendor/bin/phake
10+
NAME
11+
12+
wp website
13+
14+
SYNOPSIS
15+
16+
wp website <command>
17+
18+
SUBCOMMANDS
19+
20+
generate Run all generation commands to generate full website.
21+
generate-commands Generate the /commands/ page.
22+
generate-config Generate the /config/ page.
23+
generate-contributing Generate the contributing page from WP-CLI's CONTRIBUTING.md
24+
generate-docs Generate the /docs/ page.
25+
generate-homepage Generate the homepage from WP-CLI's README.md
26+
generate-internal-api-docs Generate the /docs/internal-apis/ page.
1327
```
1428

1529
### Preview locally

command.php

Lines changed: 323 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,29 @@
22
namespace WP_CLI_Org;
33

44
use WP_CLI;
5+
use Mustache_Engine;
56

67
/**
78
* WP-CLI commands for generating the docs
89
*/
910

1011
/**
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
1228
*
1329
* @when before_wp_load
1430
*/
@@ -45,7 +61,55 @@ function generate_homepage() {
4561
WP_CLI::add_command( 'website generate-homepage', '\WP_CLI_Org\generate_homepage' );
4662

4763
/**
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
49113
*
50114
* @when before_wp_load
51115
*/
@@ -86,6 +150,187 @@ function generate_contributing() {
86150
}
87151
WP_CLI::add_command( 'website generate-contributing', '\WP_CLI_Org\generate_contributing' );
88152

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+
89334
/**
90335
* Dump the list of internal APIs, as JSON.
91336
*
@@ -233,3 +478,79 @@ function parse_docblock( $docblock ) {
233478
$ret['long_description'] = $long_description;
234479
return $ret;
235480
}
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'] .= " &raquo; [{$p}]({$path})";
494+
} else {
495+
$binding['breadcrumbs'] .= " &raquo; {$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 `&mdash;`
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( '/ &lt; /', ' < ', $docs );
513+
$docs = preg_replace( '/ &gt; /', ' > ', $docs );
514+
$docs = preg_replace( '/ &lt;&lt;/', ' <<', $docs );
515+
$docs = preg_replace( '/&quot;/', '"', $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

Comments
 (0)