Skip to content

Commit 2e50ccf

Browse files
Port over website code to generate internal API docs
1 parent 3b3d03f commit 2e50ccf

File tree

4 files changed

+327
-0
lines changed

4 files changed

+327
-0
lines changed

bin/command.php

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
<?php
2+
3+
namespace WP_CLI\Handbook;
4+
5+
use WP_CLI;
6+
use Mustache_Engine;
7+
8+
/**
9+
* WP-CLI commands to generate docs from the codebase.
10+
*/
11+
12+
define( 'WP_CLI_HANDBOOK_PATH', dirname( dirname( __FILE__ ) ) );
13+
14+
15+
/**
16+
* @when before_wp_load
17+
*/
18+
class Command {
19+
20+
/**
21+
* Generate internal API doc pages
22+
*
23+
* @subcommand gen-api-docs
24+
*/
25+
public function gen_api_docs() {
26+
$apis = self::invoke_wp_cli( 'wp handbook api-dump' );
27+
$categories = array(
28+
'Registration' => array(),
29+
'Output' => array(),
30+
'Input' => array(),
31+
'Execution' => array(),
32+
'System' => array(),
33+
'Misc' => array(),
34+
);
35+
36+
$prepare_api_slug = function( $full_name ) {
37+
$replacements = array(
38+
'()' => '',
39+
'::' => '-',
40+
'_' => '-',
41+
'\\' => '-',
42+
);
43+
return strtolower( str_replace( array_keys( $replacements ), array_values( $replacements ), $full_name ) );
44+
};
45+
46+
$prepare_code_block = function( $description ) {
47+
return preg_replace_callback( '#```(.+)```#Us', function( $matches ) {
48+
return str_replace( PHP_EOL, PHP_EOL . ' ', $matches[1] );
49+
}, $description );
50+
};
51+
52+
foreach( $apis as $api ) {
53+
54+
$api['api_slug'] = $prepare_api_slug( $api['full_name'] );
55+
$api['phpdoc']['long_description'] = $prepare_code_block( $api['phpdoc']['long_description'] );
56+
57+
if ( ! empty( $api['phpdoc']['parameters']['category'][0][0] )
58+
&& isset( $categories[ $api['phpdoc']['parameters']['category'][0][0] ] ) ) {
59+
$categories[ $api['phpdoc']['parameters']['category'][0][0] ][] = $api;
60+
} else {
61+
$categories['Misc'][] = $api;
62+
}
63+
}
64+
$out = '***' . PHP_EOL . PHP_EOL;
65+
66+
foreach( $categories as $name => $apis ) {
67+
$out .= '## ' . $name . PHP_EOL . PHP_EOL;
68+
$out .= self::render( 'internal-api-list.mustache', array( 'apis' => $apis ) );
69+
foreach( $apis as $i => $api ) {
70+
$api['category'] = $name;
71+
$api['related'] = $apis;
72+
$api['phpdoc']['parameters'] = array_map( function( $parameter ){
73+
foreach( $parameter as $key => $values ) {
74+
if ( isset( $values[2] ) ) {
75+
$values[2] = str_replace( array( PHP_EOL ), array( '<br />' ), $values[2] );
76+
$parameter[ $key ] = $values;
77+
}
78+
}
79+
return $parameter;
80+
}, $api['phpdoc']['parameters'] );
81+
unset( $api['related'][ $i ] );
82+
$api['related'] = array_values( $api['related'] );
83+
$api['has_related'] = ! empty( $api['related'] );
84+
$api_doc = self::render( 'internal-api.mustache', $api );
85+
$path = WP_CLI_HANDBOOK_PATH . "/internal-api/{$api['api_slug']}.md";
86+
if ( ! is_dir( dirname( $path ) ) ) {
87+
mkdir( dirname( $path ) );
88+
}
89+
file_put_contents( $path, $api_doc );
90+
}
91+
$out .= PHP_EOL . PHP_EOL;
92+
}
93+
94+
file_put_contents( WP_CLI_HANDBOOK_PATH . '/internal-api.md', $out );
95+
WP_CLI::success( 'Generated /docs/internal-api/' );
96+
}
97+
98+
/**
99+
* Dump internal API PHPDoc to JSON
100+
*
101+
* @subcommand api-dump
102+
*/
103+
public function api_dump() {
104+
$apis = array();
105+
$functions = get_defined_functions();
106+
foreach( $functions['user'] as $function ) {
107+
$reflection = new \ReflectionFunction( $function );
108+
$phpdoc = $reflection->getDocComment();
109+
if ( false === stripos( $phpdoc, '@access public' ) ) {
110+
continue;
111+
}
112+
$apis[] = self::get_simple_representation( $reflection );
113+
}
114+
$classes = get_declared_classes();
115+
foreach( $classes as $class ) {
116+
if ( false === stripos( $class, 'WP_CLI' ) ) {
117+
continue;
118+
}
119+
$reflection = new \ReflectionClass( $class );
120+
foreach( $reflection->getMethods() as $method ) {
121+
$method_reflection = new \ReflectionMethod( $method->class, $method->name );
122+
$phpdoc = $method_reflection->getDocComment();
123+
if ( false === stripos( $phpdoc, '@access public' ) ) {
124+
continue;
125+
}
126+
$apis[] = self::get_simple_representation( $method_reflection );
127+
}
128+
}
129+
echo json_encode( $apis );
130+
}
131+
132+
/**
133+
* Get a simple representation of a function or method
134+
*
135+
* @param Reflection
136+
* @return array
137+
*/
138+
private static function get_simple_representation( $reflection ) {
139+
$signature = $reflection->getName();
140+
$parameters = array();
141+
foreach( $reflection->getParameters() as $parameter ) {
142+
$parameter_signature = '$' . $parameter->getName();
143+
if ( $parameter->isOptional() ) {
144+
$default_value = $parameter->getDefaultValue();
145+
if ( false === $default_value ) {
146+
$parameter_signature .= ' = false';
147+
} else if ( array() === $default_value ) {
148+
$parameter_signature .= ' = array()';
149+
} else if ( '' === $default_value ) {
150+
$parameter_signature .= " = ''";
151+
} else if ( null === $default_value ) {
152+
$parameter_signature .= ' = null';
153+
} else if ( true === $default_value ) {
154+
$parameter_signature .= ' = true';
155+
} else {
156+
$parameter_signature .= ' = ' . $default_value;
157+
}
158+
}
159+
$parameters[] = $parameter_signature;
160+
}
161+
if ( ! empty( $parameters ) ) {
162+
$signature = $signature . '( ' . implode( ', ', $parameters ) . ' )';
163+
} else {
164+
$signature = $signature . '()';
165+
}
166+
$phpdoc = $reflection->getDocComment();
167+
$type = strtolower( str_replace( 'Reflection', '', get_class( $reflection ) ) );
168+
$class = '';
169+
switch ( $type ) {
170+
case 'function':
171+
$full_name = $reflection->getName();
172+
break;
173+
case 'method':
174+
$separator = $reflection->isStatic() ? '::' : '->';
175+
$class = $reflection->class;
176+
$full_name = $class . $separator . $reflection->getName();
177+
$signature = $class . $separator . $signature;
178+
break;
179+
}
180+
return array(
181+
'phpdoc' => self::parse_docblock( $phpdoc ),
182+
'type' => $type,
183+
'signature' => $signature,
184+
'short_name' => $reflection->getShortName(),
185+
'full_name' => $full_name,
186+
'class' => $class,
187+
);
188+
}
189+
190+
/**
191+
* Parse PHPDoc into a structured representation
192+
*/
193+
private static function parse_docblock( $docblock ) {
194+
$ret = array(
195+
'description' => '',
196+
'parameters' => array(),
197+
);
198+
$extra_line = '';
199+
$in_param = false;
200+
foreach( preg_split("/(\r?\n)/", $docblock ) as $line ){
201+
if ( preg_match('/^(?=\s+?\*[^\/])(.+)/', $line, $matches ) ) {
202+
$info = trim( $matches[1] );
203+
$info = preg_replace( '/^(\*\s+?)/', '', $info );
204+
if ( $in_param ) {
205+
list( $param, $key ) = $in_param;
206+
$ret['parameters'][ $param_name ][ $key ][2] .= PHP_EOL . $info;
207+
if ( '}' === substr( $info, -1 ) ) {
208+
$in_param = false;
209+
}
210+
} else if ( $info[0] !== "@" ) {
211+
$ret['description'] .= PHP_EOL . "{$extra_line}{$info}";
212+
} else {
213+
preg_match( '/@(\w+)/', $info, $matches );
214+
$param_name = $matches[1];
215+
$value = str_replace( "@$param_name ", '', $info );
216+
if ( ! isset( $ret['parameters'][ $param_name ] ) ) {
217+
$ret['parameters'][ $param_name ] = array();
218+
}
219+
$ret['parameters'][ $param_name ][] = preg_split( '/[\s]+/', $value, 3 );
220+
end( $ret['parameters'][ $param_name ] );
221+
$key = key( $ret['parameters'][ $param_name ] );
222+
reset( $ret['parameters'][ $param_name ] );
223+
if ( ! empty( $ret['parameters'][ $param_name ][ $key ][ 2 ] )
224+
&& '{' === substr( $ret['parameters'][ $param_name ][ $key ][ 2 ] , -1 ) ) {
225+
$in_param = array( $param_name, $key );
226+
}
227+
}
228+
$extra_line = '';
229+
} else {
230+
$extra_line .= PHP_EOL;
231+
}
232+
}
233+
$ret['description'] = str_replace( '\/', '/', trim( $ret['description'], PHP_EOL ) );
234+
$bits = explode( PHP_EOL, $ret['description'] );
235+
$ret['short_description'] = array_shift( $bits );
236+
$long_description = trim( implode( PHP_EOL, $bits ), PHP_EOL );
237+
$ret['long_description'] = $long_description;
238+
return $ret;
239+
}
240+
241+
private static function invoke_wp_cli( $cmd ) {
242+
ob_start();
243+
system( "WP_CLI_CONFIG_PATH=/dev/null $cmd", $return_code );
244+
$json = ob_get_clean();
245+
246+
if ( $return_code ) {
247+
echo "WP-CLI returned error code: $return_code\n";
248+
exit(1);
249+
}
250+
251+
return json_decode( $json, true );
252+
}
253+
254+
private static function render( $path, $binding ) {
255+
$m = new Mustache_Engine;
256+
$template = file_get_contents( WP_CLI_HANDBOOK_PATH . "/bin/templates/$path" );
257+
return $m->render( $template, $binding );
258+
}
259+
260+
}
261+
262+
WP_CLI::add_command( 'handbook', '\WP_CLI\Handbook\Command' );
263+
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<ul>
2+
3+
{{#apis}}
4+
5+
<li><strong><a href="https://make.wordpress.org/cli/handbook/internal-api/{{api_slug}}/">{{full_name}}()</a></strong> - {{phpdoc.short_description}}</li>
6+
7+
{{/apis}}
8+
9+
</ul>

bin/templates/internal-api.mustache

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# {{full_name}}()
2+
3+
{{phpdoc.short_description}}
4+
5+
***
6+
7+
## Usage
8+
9+
{{signature}}
10+
11+
<div>
12+
{{#phpdoc.parameters.param}}
13+
<strong>{{1}}</strong> ({{0}}) {{{2}}}<br />
14+
{{/phpdoc.parameters.param}}
15+
{{#phpdoc.parameters.return}}
16+
<strong>@return</strong> ({{0}}) {{2}}<br />
17+
{{/phpdoc.parameters.return}}
18+
</div>
19+
20+
{{#phpdoc.long_description}}
21+
22+
***
23+
24+
## Notes
25+
26+
{{{phpdoc.long_description}}}
27+
28+
{{/phpdoc.long_description}}
29+
30+
*Internal API documentation is generated from the WP-CLI codebase on every release. To suggest improvements, please submit a pull request.*
31+
32+
{{#has_related}}
33+
34+
***
35+
36+
## Related
37+
38+
<ul>
39+
40+
{{/has_related}}
41+
42+
{{#related}}
43+
44+
<li><strong><a href="https://make.wordpress.org/cli/handbook/internal-api/{{api_slug}}/">{{full_name}}()</a></strong> - {{phpdoc.short_description}}</li>
45+
46+
{{/related}}
47+
48+
{{#has_related}}
49+
50+
</ul>
51+
52+
{{/has_related}}
53+

wp-cli.yml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require:
2+
- bin/command.php

0 commit comments

Comments
 (0)