8
8
// phpcs:disable WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
9
9
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_mkdir
10
10
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
11
+ // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
11
12
12
13
function sample_ini () {
13
14
return <<<INI
14
15
namespace = App_Namespace
15
- wiki_directory = ../../repo.wiki
16
+ base_dir = ..
17
+ wiki_directory = ../repo.wiki
16
18
github_blob_url = https://github.com/username/repo/blob/main/
17
19
[hooks]
18
20
ignore_filter[] = filter_name1
19
21
ignore_filter[] = filter_name2
20
22
INI ;
21
23
}
22
- $ ini_file = __DIR__ . '/extract-hooks.ini ' ;
24
+ $ dirs = array ( getcwd (), __DIR__ );
25
+ if ( isset ( $ _SERVER ['argv ' ][1 ] ) && file_exists ( $ _SERVER ['argv ' ][1 ] ) ) {
26
+ if ( is_dir ( $ _SERVER ['argv ' ][1 ] ) ) {
27
+ array_unshift ( $ dirs , $ _SERVER ['argv ' ][1 ] );
28
+ } else {
29
+ array_unshift ( $ dirs , dirname ( $ _SERVER ['argv ' ][1 ] ) );
30
+ }
31
+ }
32
+
33
+ foreach ( $ dirs as $ dir ) {
34
+ $ ini_file = $ dir . '/extract-hooks.ini ' ;
35
+ if ( file_exists ( $ ini_file ) ) {
36
+ break ;
37
+ }
38
+ }
39
+
23
40
if ( ! file_exists ( $ ini_file ) ) {
24
- echo 'Please create an extract-hooks.ini file in the same directory as this script. Example: ' , PHP_EOL , esc_attr ( sample_ini () ) ;
41
+ echo 'Please provide an extract-hooks.ini file in the current directory or the same directory as this script. Example: ' , PHP_EOL , sample_ini (), PHP_EOL ;
25
42
exit ( 1 );
26
43
}
44
+ echo 'Loading ' , realpath ( $ ini_file ), PHP_EOL ;
27
45
$ ini = parse_ini_file ( $ ini_file );
28
46
29
- foreach ( array ( 'namespace ' , 'wiki_directory ' , 'github_url ' ) as $ key ) {
47
+ foreach ( array ( 'namespace ' , 'base_dir ' , ' wiki_directory ' , 'github_url ' ) as $ key ) {
30
48
if ( ! isset ( $ ini [ $ key ] ) ) {
31
- echo 'Missing ini entry ' , esc_attr ( $ key ) , '. Example: ' , PHP_EOL , esc_attr ( sample_ini () ) ;
49
+ echo 'Missing ini entry ' , $ key , '. Example: ' , PHP_EOL , sample_ini (), PHP_EOL ;
32
50
exit ( 1 );
33
51
}
34
52
}
35
53
36
54
if ( empty ( $ ini ['ignore_filter ' ] ) ) {
37
55
$ ini ['ignore_filter ' ] = array ();
38
56
}
39
-
40
- $ base = dirname ( __DIR__ );
57
+ if ( empty ( $ ini ['ignore_regex ' ] ) ) {
58
+ $ ini ['ignore_regex ' ] = false ;
59
+ }
60
+ if ( empty ( $ ini ['section ' ] ) ) {
61
+ $ ini ['section ' ] = 'file ' ;
62
+ }
63
+ // if the base dir is not absolute (also on windows), prepend it with $dir.
64
+ if ( '/ ' === substr ( $ ini ['base_dir ' ], 0 , 1 ) ) {
65
+ $ base = $ ini ['base_dir ' ];
66
+ } else {
67
+ $ base = realpath ( $ dir . '/ ' . $ ini ['base_dir ' ] );
68
+ }
69
+ echo 'Scanning ' , $ base , PHP_EOL ;
41
70
$ files = new RecursiveIteratorIterator (
42
71
new RecursiveDirectoryIterator ( $ base ),
43
72
RecursiveIteratorIterator::LEAVES_ONLY
@@ -101,27 +130,24 @@ function sample_ini() {
101
130
}
102
131
103
132
if (
104
- $ hook
105
- && ! in_array ( $ hook , $ ini ['ignore_filter ' ] )
106
- && ! preg_match ( '/^(activitypub_)/ ' , $ hook )
107
-
133
+ $ hook
134
+ && ! in_array ( $ hook , $ ini ['ignore_filter ' ] )
135
+ && ( ! $ ini ['ignore_regex ' ] || ! preg_match ( $ ini ['ignore_regex ' ], $ hook ) )
108
136
) {
109
137
if ( ! isset ( $ filters [ $ hook ] ) ) {
110
138
$ filters [ $ hook ] = array (
111
- 'files ' => array (),
112
- 'section ' => $ main_dir ,
113
- 'type ' => $ token [1 ],
114
- 'params ' => array (),
115
- 'signatures ' => array () ,
139
+ 'files ' => array (),
140
+ 'section ' => ' dir ' === $ ini [ ' section ' ] ? $ main_dir : basename ( $ file -> getFilename () ) ,
141
+ 'type ' => $ token [1 ],
142
+ 'params ' => array (),
143
+ 'comment ' => '' ,
116
144
);
117
145
}
118
146
119
147
$ ret = extract_vars ( $ filters [ $ hook ]['params ' ], $ tokens , $ i );
120
148
$ filters [ $ hook ]['files ' ][ $ dir . '/ ' . $ file ->getFilename () . ': ' . $ token [2 ] ] = $ ret [1 ];
121
- $ filters [ $ hook ]['base_dirs ' ][ $ main_dir ] = true ;
122
149
$ filters [ $ hook ]['params ' ] = $ ret [0 ];
123
-
124
- $ filters [ $ hook ] = array_merge ( parse_docblock ( $ comment , $ filters [ $ hook ]['params ' ] ), $ filters [ $ hook ] );
150
+ $ filters [ $ hook ] = array_merge ( $ filters [ $ hook ], parse_docblock ( $ comment , $ filters [ $ hook ]['params ' ] ) );
125
151
}
126
152
}
127
153
}
@@ -142,7 +168,8 @@ function extract_vars( $params, $tokens, $i ) {
142
168
$ vars = array ( '' );
143
169
$ signature = $ tokens [ $ i ][1 ];
144
170
$ line = $ tokens [ $ i ][2 ];
145
- for ( $ j = $ i + 1 ; $ j < $ i + 100 ; $ j ++ ) {
171
+ $ search_window = 50 ;
172
+ for ( $ j = $ i + 1 ; $ j < $ i + $ search_window ; $ j ++ ) {
146
173
if ( ! isset ( $ tokens [ $ j ] ) ) {
147
174
break ;
148
175
}
@@ -200,23 +227,29 @@ function extract_vars( $params, $tokens, $i ) {
200
227
}
201
228
}
202
229
}
230
+ if ( $ j === $ i + $ search_window ) {
231
+ $ signature = rtrim ( $ signature ) . PHP_EOL . '// ... ' ;
232
+ }
203
233
array_shift ( $ vars );
204
234
foreach ( $ vars as $ k => $ var ) {
205
235
if ( isset ( $ params [ $ k ] ) ) {
206
- if ( $ params [ $ k ] !== $ var ) {
207
- $ params [ $ k ] .= ' | ' . $ var ;
236
+ if ( ! in_array ( $ var , $ params [ $ k ] ) ) {
237
+ $ params [ $ k ][] = $ var ;
208
238
}
209
239
} else {
210
- $ params [ $ k ] = $ var ;
240
+ $ params [ $ k ] = array ( $ var ) ;
211
241
}
212
242
}
213
243
return array ( $ params , $ signature );
214
244
}
215
245
216
246
function parse_docblock ( $ raw_comment , $ params ) {
247
+ if ( preg_match ( '#^([ \t]*\*\s*|//\s*)?Documented (in|at) #m ' , $ raw_comment ) ) {
248
+ return array ();
249
+ }
217
250
// Adapted from https://github.com/kamermans/docblock-reflection.
218
251
$ tags = array ();
219
- $ lines = explode ( PHP_EOL , trim ( $ raw_comment ) );
252
+ $ lines = array_filter ( explode ( PHP_EOL , trim ( $ raw_comment ) ) );
220
253
$ matches = null ;
221
254
$ comment = '' ;
222
255
@@ -233,7 +266,7 @@ function parse_docblock( $raw_comment, $params ) {
233
266
234
267
case 0 :
235
268
case 2 :
236
- break ;
269
+ return array () ;
237
270
238
271
default :
239
272
// Handle multi-line docblock.
@@ -244,18 +277,28 @@ function parse_docblock( $raw_comment, $params ) {
244
277
245
278
foreach ( $ lines as $ line ) {
246
279
$ line = preg_replace ( '#^[ \t]*\* ?# ' , '' , $ line );
280
+ if ( preg_match ( '#^Documented (in|at) # ' , $ line ) ) {
281
+ return array ();
282
+ }
283
+
284
+ if ( preg_match ( '#@(param)(.*)# ' , $ line , $ matches ) ) {
285
+ $ tag_value = \trim ( $ matches [2 ] );
247
286
287
+ // If this tag was already parsed, make its value an array.
288
+ if ( isset ( $ tags ['params ' ] ) ) {
289
+ $ tags ['params ' ][] = array ( $ tag_value );
290
+ } else {
291
+ $ tags ['params ' ] = array ( array ( $ tag_value ) );
292
+ }
293
+ continue ;
294
+ }
248
295
if ( preg_match ( '#@([^ ]+)(.*)# ' , $ line , $ matches ) ) {
249
296
$ tag_name = $ matches [1 ] . 's ' ;
250
297
$ tag_value = \trim ( $ matches [2 ] );
251
298
252
299
// If this tag was already parsed, make its value an array.
253
300
if ( isset ( $ tags [ $ tag_name ] ) ) {
254
- if ( ! \is_array ( $ tags [ $ tag_name ] ) ) {
255
- $ tags [ $ tag_name ] = array ( $ tags [ $ tag_name ] );
256
- }
257
-
258
- $ tags [ $ tag_name ][] = $ tag_value ;
301
+ $ tags [ $ tag_name ][] = array ( $ tag_value );
259
302
} else {
260
303
$ tags [ $ tag_name ] = $ tag_value ;
261
304
}
@@ -270,6 +313,8 @@ function parse_docblock( $raw_comment, $params ) {
270
313
foreach ( $ params as $ k => $ param ) {
271
314
if ( ! isset ( $ tags ['params ' ][ $ k ] ) ) {
272
315
$ tags ['params ' ][ $ k ] = $ param ;
316
+ } elseif ( ! in_array ( $ tags ['params ' ][ $ k ], $ param ) ) {
317
+ $ tags ['params ' ][ $ k ] = array_merge ( $ tags ['params ' ][ $ k ], $ param );
273
318
}
274
319
}
275
320
@@ -302,10 +347,9 @@ function parse_docblock( $raw_comment, $params ) {
302
347
}
303
348
$ doc = '' ;
304
349
$ index .= "- [` $ hook`]( $ hook) " ;
305
-
306
350
if ( ! empty ( $ data ['comment ' ] ) ) {
307
351
$ index .= ' ' . strtok ( $ data ['comment ' ], PHP_EOL );
308
- $ doc .= PHP_EOL . $ data ['comment ' ] . PHP_EOL . PHP_EOL ;
352
+ $ doc .= PHP_EOL . preg_replace ( ' /^Example:?$/m ' , ' ### Example ' . PHP_EOL , $ data ['comment ' ] ) . PHP_EOL . PHP_EOL ;
309
353
}
310
354
311
355
$ index .= PHP_EOL ;
@@ -322,57 +366,59 @@ function parse_docblock( $raw_comment, $params ) {
322
366
$ params = "## Parameters \n" ;
323
367
$ first = false ;
324
368
$ count = 0 ;
325
- foreach ( $ data ['params ' ] as $ param ) {
326
- if ( false === strpos ( $ param , ' ' ) || false !== strpos ( $ param , '\'' ) ) {
327
- // This was an extracted variable, so let's create a parameter definition.
328
- $ vars = explode ( '| ' , $ param );
329
- foreach ( array_keys ( $ vars ) as $ k ) {
330
- if ( preg_match ( '#array\(# ' , $ vars [ $ k ], $ matches ) ) {
331
- $ vars [ $ k ] = '$array ' ;
332
- }
333
- if ( preg_match ( '#\$(?:[a-zA-Z0-9_]+)\[[ \'"]([^ \'"]+)[ \'"]# ' , $ vars [ $ k ], $ matches ) ) {
334
- $ vars [ $ k ] = '$ ' . $ matches [1 ];
335
- }
336
- if ( preg_match ( '#_[_xn]\(\s*([ \'"][^ \'"]+[ \'"])# ' , $ vars [ $ k ], $ matches ) ) {
337
- $ vars [ $ k ] = $ matches [1 ];
338
- }
369
+ foreach ( $ data ['params ' ] as $ i => $ vars ) {
370
+ $ param = false ;
371
+ foreach ( $ vars as $ k => $ var ) {
372
+ if ( false !== strpos ( $ var , ' ' ) && false === strpos ( $ var , '\'' ) ) {
373
+ $ param = $ var ;
374
+ $ p = preg_split ( '/ +/ ' , $ param , 3 );
375
+ $ vars [ $ k ] = $ p [1 ];
339
376
}
340
-
341
- foreach ( $ vars as $ k => $ var ) {
342
- if ( $ var && trim ( $ var , '})] ' ) === $ var ) {
343
- unset( $ vars [ $ k ] );
344
- $ type = 'unknown ' ;
345
- if ( strlen ( $ var ) - strlen ( trim ( $ var , '" \'' ) ) === 2 ) {
346
- $ type = 'string ' ;
347
- $ var = '$string ' ;
348
- } elseif ( is_numeric ( $ var ) ) {
349
- $ type = 'int ' ;
350
- $ var = '$int ' ;
351
- } elseif ( 'true ' === $ var || 'false ' === $ var ) {
352
- $ type = 'bool ' ;
353
- $ var = '$ ' . $ var ;
354
- } elseif ( 'null ' === $ var ) {
355
- $ type = 'null ' ;
356
- $ var = '$null ' ;
357
- } elseif ( in_array ( $ var , array ( '$url ' ), true ) ) {
358
- $ type = 'string ' ;
359
- } elseif ( '$array ' === $ var ) {
360
- $ type = 'array ' ;
361
- $ var = '$array ' ;
362
- }
363
- $ param = $ type . ' ' . $ var ;
364
- if ( $ vars ) {
365
- $ param .= ' Other examples: ` ' . implode ( '`, ` ' , $ vars ) . '` ' ;
366
- }
367
- break ;
368
- }
377
+ }
378
+ $ type = 'unknown ' ;
379
+
380
+ // This was an extracted variable, so let's create a parameter definition.
381
+ foreach ( $ vars as $ k => $ var ) {
382
+ if ( preg_match ( '#array\(# ' , $ var , $ matches ) ) {
383
+ $ type = 'array ' ;
384
+ $ vars [ $ k ] = '$array ' ;
385
+ } elseif ( preg_match ( '#\$(?:[a-zA-Z0-9_]+)\[[ \'"]([^ \'"]+)[ \'"]# ' , $ var , $ matches ) ) {
386
+ $ vars [ $ k ] = '$ ' . $ matches [1 ];
387
+ } elseif ( preg_match ( '#([a-zA-Z0-9_]+)\(\)# ' , $ var , $ matches ) ) {
388
+ $ vars [ $ k ] = '$ ' . str_replace ( 'wp_get_ ' , '' , $ matches [1 ] );
389
+ } elseif ( preg_match ( '#\$(?:[a-zA-Z0-9_]+)->(.+)$# ' , $ var , $ matches ) ) {
390
+ $ vars [ $ k ] = '$ ' . $ matches [1 ];
391
+ } elseif ( preg_match ( '#_[_xn]\(\s*([ \'"][^ \'"]+[ \'"])# ' , $ var , $ matches ) ) {
392
+ $ type = 'string ' ;
393
+ $ vars [ $ k ] = '$ ' . preg_replace ( '/[^a-z0-9]/ ' , '_ ' , strtolower ( trim ( $ matches [1 ], '" \'' ) ) );
394
+ } elseif ( strlen ( $ var ) - strlen ( trim ( $ var , '" \'' ) ) === 2 ) {
395
+ $ type = 'string ' ;
396
+ $ vars [ $ k ] = '$ ' . preg_replace ( '/[^a-z0-9]/ ' , '_ ' , strtolower ( trim ( $ var , '" \'' ) ) );
397
+ } elseif ( is_numeric ( $ var ) ) {
398
+ $ type = 'int ' ;
399
+ $ vars [ $ k ] = '$int ' ;
400
+ } elseif ( 'true ' === $ var || 'false ' === $ var ) {
401
+ $ type = 'bool ' ;
402
+ $ vars [ $ k ] = '$ ' . $ var ;
403
+ } elseif ( 'null ' === $ var ) {
404
+ $ vars [ $ k ] = '$ret ' ;
405
+ } elseif ( in_array ( $ var , array ( '$url ' ), true ) ) {
406
+ $ type = 'string ' ;
407
+ } elseif ( '$array ' === $ var ) {
408
+ $ type = 'array ' ;
409
+ $ vars [ $ k ] = '$array ' ;
369
410
}
370
- if ( false === strpos ( $ param , ' ' ) ) {
371
- // If we didn't manage to find a type, let's use unknown.
372
- $ param = 'unknown ' . $ param ;
411
+ }
412
+ if ( ! $ param ) {
413
+ $ var = reset ( $ vars );
414
+ $ param = $ type . ' ' . $ var ;
415
+ $ other = array_unique ( array_diff ( $ vars , array ( $ param , $ var , 'null ' ) ) );
416
+ if ( $ other ) {
417
+ $ param .= ' Other variable names: ` ' . implode ( '`, ` ' , $ other ) . '` ' ;
373
418
}
374
419
}
375
420
421
+
376
422
$ count += 1 ;
377
423
$ p = preg_split ( '/ +/ ' , $ param , 3 );
378
424
if ( '\\' === substr ( $ p [0 ], 0 , 1 ) ) {
@@ -419,13 +465,13 @@ function parse_docblock( $raw_comment, $params ) {
419
465
}
420
466
$ signature .= PHP_EOL . '); ' ;
421
467
422
- $ doc .= '### Generic Example ' . PHP_EOL . PHP_EOL . '```php ' . PHP_EOL . $ signature . PHP_EOL . '``` ' . PHP_EOL . PHP_EOL ;
468
+ $ doc .= '### Auto-generated Example ' . PHP_EOL . PHP_EOL . '```php ' . PHP_EOL . $ signature . PHP_EOL . '``` ' . PHP_EOL . PHP_EOL ;
423
469
$ doc .= $ params . PHP_EOL . PHP_EOL ;
424
470
}
425
471
426
- if ( ! empty ( $ data ['return ' ] ) ) {
472
+ if ( ! empty ( $ data ['returns ' ] ) ) {
427
473
$ doc .= "## Returns \n" ;
428
- $ p = preg_split ( '/ +/ ' , $ data ['return ' ], 2 );
474
+ $ p = preg_split ( '/ +/ ' , $ data ['returns ' ], 2 );
429
475
if ( '\\' === substr ( $ p [0 ], 0 , 1 ) ) {
430
476
$ p [0 ] = substr ( $ p [0 ], 1 );
431
477
} elseif ( ! in_array ( strtok ( $ p [0 ], '| ' ), array ( 'int ' , 'string ' , 'bool ' , 'array ' , 'unknown ' ) ) && substr ( $ p [0 ], 0 , 3 ) !== 'WP_ ' ) {
@@ -449,9 +495,9 @@ function parse_docblock( $raw_comment, $params ) {
449
495
);
450
496
451
497
}
452
- file_put_contents (
453
- $ docs . '/Hooks.md ' ,
454
- $ index
455
- );
498
+ file_put_contents (
499
+ $ docs . '/Hooks.md ' ,
500
+ $ index
501
+ );
456
502
457
503
echo 'Genearated ' . count ( $ filters ) . ' hooks documentation files in ' . realpath ( $ docs ) . PHP_EOL ; // phpcs:ignore
0 commit comments