Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
95.59% |
217 / 227 |
|
82.35% |
28 / 34 |
CRAP | |
0.00% |
0 / 1 |
MaintenanceParameters | |
95.59% |
217 / 227 |
|
82.35% |
28 / 34 |
98 | |
0.00% |
0 / 1 |
getFieldReference | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
assignGroup | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
supportsOption | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
addOption | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
hasOption | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOption | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addArg | |
86.36% |
19 / 22 |
|
0.00% |
0 / 1 |
6.09 | |||
deleteOption | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
setAllowUnregisteredOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setDescription | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasArg | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getArg | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getArgs | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
getArgName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setOption | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setArg | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
clear | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
mergeOptions | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
loadWithArgv | |
100.00% |
39 / 39 |
|
100.00% |
1 / 1 |
16 | |||
setOptionValue | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 | |||
error | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getErrors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasErrors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setOptionsAndArgs | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
validate | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
10 | |||
getHelp | |
100.00% |
49 / 49 |
|
100.00% |
1 / 1 |
11 | |||
formatHelpItems | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
5 | |||
getOptionNames | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOptions | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOptionsSequence | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setUsagePrefix | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getArgRepresentation | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | namespace MediaWiki\Maintenance; |
22 | |
23 | use UnexpectedValueException; |
24 | |
25 | /** |
26 | * Command line parameter handling for maintenance scripts. |
27 | * |
28 | * @since 1.39 |
29 | * @ingroup Maintenance |
30 | */ |
31 | class MaintenanceParameters { |
32 | |
33 | /** |
34 | * Array of desired/allowed params |
35 | * @var array<string,array> |
36 | * @phan-var array<string,array{desc:string,require:bool,withArg:string,shortName:string|bool,multiOccurrence:bool}> |
37 | */ |
38 | private $mOptDefs = []; |
39 | |
40 | /** @var array<string,string> Mapping short options to long ones */ |
41 | private $mShortOptionMap = []; |
42 | |
43 | /** @var array<int,array> Desired/allowed args */ |
44 | private $mArgDefs = []; |
45 | |
46 | /** @var array<string,int> Map of arg names to offsets */ |
47 | private $mArgOffsets = []; |
48 | |
49 | /** @var bool Allow arbitrary options to be passed, or only specified ones? */ |
50 | private $mAllowUnregisteredOptions = false; |
51 | |
52 | /** @var string|null Name of the script currently running */ |
53 | private $mName = null; |
54 | |
55 | /** @var string|null A description of the script, children should change this via addDescription() */ |
56 | private $mDescription = null; |
57 | |
58 | /** @var array<string,string> This is the list of options that were actually passed */ |
59 | private $mOptions = []; |
60 | |
61 | /** @var array<int,string> This is the list of arguments that were actually passed */ |
62 | private $mArgs = []; |
63 | |
64 | /** @var array<string,array> maps group names to lists of option names */ |
65 | private $mOptionGroups = []; |
66 | |
67 | /** |
68 | * Used to read the options in the order they were passed. |
69 | * This is an array of arrays where |
70 | * 0 => the option name and 1 => option value. |
71 | * |
72 | * @var array |
73 | */ |
74 | private $optionsSequence = []; |
75 | |
76 | /** @var string[] */ |
77 | private $errors = []; |
78 | |
79 | /** @var string */ |
80 | private $usagePrefix = 'php maintenance/run.php'; |
81 | |
82 | /** |
83 | * Returns a reference to a member field. |
84 | * This is a backwards compatibility hack, it should be removed as soon as possible! |
85 | * |
86 | * @param string $fieldName |
87 | * |
88 | * @return mixed A reference to a member field |
89 | * @internal For use by the Maintenance class, for backwards compatibility support. |
90 | */ |
91 | public function &getFieldReference( string $fieldName ) { |
92 | return $this->$fieldName; |
93 | } |
94 | |
95 | /** |
96 | * Assigns a list of options to the given group. |
97 | * The given options will be shown as part of the given group |
98 | * in the help message. |
99 | * |
100 | * @param string $groupName |
101 | * @param array $paramNames |
102 | */ |
103 | public function assignGroup( string $groupName, array $paramNames ) { |
104 | $this->mOptionGroups[ $groupName ] = array_merge( |
105 | $this->mOptionGroups[ $groupName ] ?? [], |
106 | $paramNames |
107 | ); |
108 | } |
109 | |
110 | /** |
111 | * Checks to see if a particular option in supported. Normally this means it |
112 | * has been registered by the script via addOption. |
113 | * @param string $name The name of the option<string,string> |
114 | * @return bool true if the option exists, false otherwise |
115 | */ |
116 | public function supportsOption( string $name ) { |
117 | return isset( $this->mOptDefs[$name] ); |
118 | } |
119 | |
120 | /** |
121 | * Add a option to the script. Will be displayed on --help |
122 | * with the associated description |
123 | * |
124 | * @param string $name The name of the param (help, version, etc) |
125 | * @param string $description The description of the param to show on --help |
126 | * @param bool $required Is the param required? |
127 | * @param bool $withArg Is an argument required with this option? |
128 | * @param string|bool $shortName Character to use as short name |
129 | * @param bool $multiOccurrence Can this option be passed multiple times? |
130 | */ |
131 | public function addOption( string $name, string $description, bool $required = false, |
132 | bool $withArg = false, $shortName = false, bool $multiOccurrence = false |
133 | ) { |
134 | $this->mOptDefs[$name] = [ |
135 | 'desc' => $description, |
136 | 'require' => $required, |
137 | 'withArg' => $withArg, |
138 | 'shortName' => $shortName, |
139 | 'multiOccurrence' => $multiOccurrence |
140 | ]; |
141 | |
142 | if ( $shortName !== false ) { |
143 | $this->mShortOptionMap[$shortName] = $name; |
144 | } |
145 | } |
146 | |
147 | /** |
148 | * Checks to see if a particular option was set. |
149 | * |
150 | * @param string $name The name of the option |
151 | * @return bool |
152 | */ |
153 | public function hasOption( string $name ): bool { |
154 | return isset( $this->mOptions[$name] ); |
155 | } |
156 | |
157 | /** |
158 | * Get the value of an option, or return the default. |
159 | * |
160 | * If the option was defined to support multiple occurrences, |
161 | * this will return an array. |
162 | * |
163 | * @param string $name The name of the param |
164 | * @param mixed|null $default Anything you want, default null |
165 | * @return mixed |
166 | * @return-taint none |
167 | */ |
168 | public function getOption( string $name, $default = null ) { |
169 | if ( $this->hasOption( $name ) ) { |
170 | return $this->mOptions[$name]; |
171 | } else { |
172 | return $default; |
173 | } |
174 | } |
175 | |
176 | /** |
177 | * Define a positional argument. getArg() can later be used to get the value given |
178 | * for the argument, by index or by name. |
179 | * |
180 | * @param string $arg Name of the arg, like 'start' |
181 | * @param string $description Short description of the arg |
182 | * @param bool $required Is this required? |
183 | * @param bool $multi Does it allow multiple values? (Last arg only) |
184 | * @return int the offset of the argument |
185 | */ |
186 | public function addArg( string $arg, string $description, bool $required = true, bool $multi = false ): int { |
187 | if ( isset( $this->mArgOffsets[$arg] ) ) { |
188 | throw new UnexpectedValueException( "Argument already defined: $arg" ); |
189 | } |
190 | |
191 | $argCount = count( $this->mArgDefs ); |
192 | if ( $argCount ) { |
193 | $prevArg = $this->mArgDefs[ $argCount - 1 ]; |
194 | if ( !$prevArg['require'] && $required ) { |
195 | throw new UnexpectedValueException( |
196 | "Required argument {$arg} cannot follow an optional argument {$prevArg['name']}" |
197 | ); |
198 | } |
199 | |
200 | if ( $prevArg['multi'] ) { |
201 | throw new UnexpectedValueException( |
202 | "Argument {$arg} cannot follow multi-value argument {$prevArg['name']}" |
203 | ); |
204 | } |
205 | } |
206 | |
207 | $this->mArgDefs[] = [ |
208 | 'name' => $arg, |
209 | 'desc' => $description, |
210 | 'require' => $required, |
211 | 'multi' => $multi, |
212 | ]; |
213 | |
214 | $ofs = count( $this->mArgDefs ) - 1; |
215 | $this->mArgOffsets[$arg] = $ofs; |
216 | return $ofs; |
217 | } |
218 | |
219 | /** |
220 | * Remove an option. Useful for removing options that won't be used in your script. |
221 | * @param string $name The option to remove. |
222 | */ |
223 | public function deleteOption( string $name ) { |
224 | unset( $this->mOptDefs[$name] ); |
225 | unset( $this->mOptions[$name] ); |
226 | |
227 | foreach ( $this->optionsSequence as $i => [ $opt, ] ) { |
228 | if ( $opt === $name ) { |
229 | unset( $this->optionsSequence[$i] ); |
230 | break; |
231 | } |
232 | } |
233 | } |
234 | |
235 | /** |
236 | * Sets whether to allow unknown options to be passed to the script. |
237 | * By default, unknown options cause an error. |
238 | * @param bool $allow Should we allow? |
239 | */ |
240 | public function setAllowUnregisteredOptions( bool $allow ) { |
241 | $this->mAllowUnregisteredOptions = $allow; |
242 | } |
243 | |
244 | /** |
245 | * Set a short description of what the script does. |
246 | * @param string $text |
247 | */ |
248 | public function setDescription( string $text ) { |
249 | $this->mDescription = $text; |
250 | } |
251 | |
252 | /** |
253 | * Was a value for the given argument provided? |
254 | * @param int|string $argId The index (from zero) of the argument, or |
255 | * the name declared for the argument by addArg(). |
256 | * @return bool |
257 | */ |
258 | public function hasArg( $argId ): bool { |
259 | // arg lookup by name |
260 | if ( is_string( $argId ) && isset( $this->mArgOffsets[$argId] ) ) { |
261 | $argId = $this->mArgOffsets[$argId]; |
262 | } |
263 | |
264 | return isset( $this->mArgs[$argId] ); |
265 | } |
266 | |
267 | /** |
268 | * Get an argument. |
269 | * @param int|string $argId The index (from zero) of the argument, or |
270 | * the name declared for the argument by addArg(). |
271 | * @param string|null $default The default if it doesn't exist |
272 | * @return string|null |
273 | * @return-taint none |
274 | */ |
275 | public function getArg( $argId, ?string $default = null ): ?string { |
276 | // arg lookup by name |
277 | if ( is_string( $argId ) && isset( $this->mArgOffsets[$argId] ) ) { |
278 | $argId = $this->mArgOffsets[$argId]; |
279 | } |
280 | |
281 | return $this->mArgs[$argId] ?? $default; |
282 | } |
283 | |
284 | /** |
285 | * Get arguments. |
286 | * @param int|string $offset The index (from zero) of the first argument, or |
287 | * the name declared for the argument by addArg(). |
288 | * @return string[] |
289 | */ |
290 | public function getArgs( $offset = 0 ): array { |
291 | if ( is_string( $offset ) && isset( $this->mArgOffsets[$offset] ) ) { |
292 | $offset = $this->mArgOffsets[$offset]; |
293 | } |
294 | |
295 | return array_slice( $this->mArgs, $offset ); |
296 | } |
297 | |
298 | /** |
299 | * Get the name of an argument at the given index. |
300 | * |
301 | * @param int $argIndex The integer value (from zero) for the arg |
302 | * |
303 | * @return string|null The name of the argument, or null if the argument does not exist. |
304 | */ |
305 | public function getArgName( int $argIndex ): ?string { |
306 | return $this->mArgDefs[ $argIndex ]['name'] ?? null; |
307 | } |
308 | |
309 | /** |
310 | * Programmatically set the value of the given option. |
311 | * Useful for setting up child scripts, see runChild(). |
312 | * |
313 | * @param string $name |
314 | * @param mixed|null $value |
315 | */ |
316 | public function setOption( string $name, $value ): void { |
317 | $this->mOptions[$name] = $value; |
318 | } |
319 | |
320 | /** |
321 | * Programmatically set the value of the given argument. |
322 | * Useful for setting up child scripts, see runChild(). |
323 | * |
324 | * @param string|int $argId |
325 | * @param string $value |
326 | */ |
327 | public function setArg( $argId, $value ): void { |
328 | // arg lookup by name |
329 | if ( is_string( $argId ) && isset( $this->mArgOffsets[$argId] ) ) { |
330 | $argId = $this->mArgOffsets[$argId]; |
331 | } |
332 | $this->mArgs[$argId] = $value; |
333 | } |
334 | |
335 | /** |
336 | * Clear all parameter values. |
337 | * Note that all parameter definitions remain intact. |
338 | */ |
339 | public function clear() { |
340 | $this->mOptions = []; |
341 | $this->mArgs = []; |
342 | $this->optionsSequence = []; |
343 | $this->errors = []; |
344 | } |
345 | |
346 | /** |
347 | * Merge options declarations from $other into this instance. |
348 | * |
349 | * @param MaintenanceParameters $other |
350 | */ |
351 | public function mergeOptions( MaintenanceParameters $other ) { |
352 | $this->mOptDefs = $other->mOptDefs + $this->mOptDefs; |
353 | $this->mShortOptionMap = $other->mShortOptionMap + $this->mShortOptionMap; |
354 | |
355 | $this->mOptionGroups = array_merge_recursive( $this->mOptionGroups, $other->mOptionGroups ); |
356 | |
357 | $this->clear(); |
358 | } |
359 | |
360 | /** |
361 | * Load params and arguments from a given array |
362 | * of command-line arguments |
363 | * |
364 | * @param array $argv The argument array. |
365 | * @param int $skip Skip that many elements at the beginning of $argv. |
366 | */ |
367 | public function loadWithArgv( array $argv, int $skip = 0 ) { |
368 | $this->clear(); |
369 | |
370 | $options = []; |
371 | $args = []; |
372 | $this->optionsSequence = []; |
373 | |
374 | // Ignore a number of arguments at the beginning of the array. |
375 | // Typically used to ignore the script name at index 0. |
376 | $argv = array_slice( $argv, $skip ); |
377 | |
378 | # Parse arguments |
379 | for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) { |
380 | if ( $arg == '--' ) { |
381 | # End of options, remainder should be considered arguments |
382 | $arg = next( $argv ); |
383 | while ( $arg !== false ) { |
384 | $args[] = $arg; |
385 | $arg = next( $argv ); |
386 | } |
387 | break; |
388 | } elseif ( substr( $arg, 0, 2 ) == '--' ) { |
389 | # Long options |
390 | $option = substr( $arg, 2 ); |
391 | if ( isset( $this->mOptDefs[$option] ) && $this->mOptDefs[$option]['withArg'] ) { |
392 | $param = next( $argv ); |
393 | if ( $param === false ) { |
394 | $this->error( "Option --$option needs a value after it!" ); |
395 | } |
396 | |
397 | $this->setOptionValue( $options, $option, $param ); |
398 | } else { |
399 | $bits = explode( '=', $option, 2 ); |
400 | $this->setOptionValue( $options, $bits[0], $bits[1] ?? 1 ); |
401 | } |
402 | } elseif ( $arg == '-' ) { |
403 | # Lonely "-", often used to indicate stdin or stdout. |
404 | $args[] = $arg; |
405 | } elseif ( substr( $arg, 0, 1 ) == '-' ) { |
406 | # Short options |
407 | $argLength = strlen( $arg ); |
408 | for ( $p = 1; $p < $argLength; $p++ ) { |
409 | $givenShort = $arg[$p]; |
410 | $option = $givenShort; |
411 | if ( !isset( $this->mOptDefs[$givenShort] ) && isset( $this->mShortOptionMap[$givenShort] ) ) { |
412 | $option = $this->mShortOptionMap[$givenShort]; |
413 | } |
414 | |
415 | if ( isset( $this->mOptDefs[$option]['withArg'] ) && $this->mOptDefs[$option]['withArg'] ) { |
416 | $param = next( $argv ); |
417 | if ( $param === false ) { |
418 | $this->error( "Option -$givenShort needs a value after it!" ); |
419 | } |
420 | $this->setOptionValue( $options, $option, $param ); |
421 | } else { |
422 | $this->setOptionValue( $options, $option, 1 ); |
423 | } |
424 | } |
425 | } else { |
426 | $args[] = $arg; |
427 | } |
428 | } |
429 | |
430 | $this->mOptions = $options; |
431 | $this->mArgs = $args; |
432 | } |
433 | |
434 | /** |
435 | * Helper function used solely by loadWithArgv |
436 | * to prevent code duplication |
437 | * |
438 | * This sets the param in the options array based on |
439 | * whether or not it can be specified multiple times. |
440 | * |
441 | * @param array &$options |
442 | * @param string $option |
443 | * @param mixed $value |
444 | */ |
445 | private function setOptionValue( array &$options, string $option, $value ) { |
446 | $this->optionsSequence[] = [ $option, $value ]; |
447 | |
448 | if ( isset( $this->mOptDefs[$option] ) ) { |
449 | $multi = $this->mOptDefs[$option]['multiOccurrence']; |
450 | } else { |
451 | $multi = false; |
452 | } |
453 | $exists = array_key_exists( $option, $options ); |
454 | if ( $multi && $exists ) { |
455 | $options[$option][] = $value; |
456 | } elseif ( $multi ) { |
457 | $options[$option] = [ $value ]; |
458 | } elseif ( !$exists ) { |
459 | $options[$option] = $value; |
460 | } else { |
461 | $this->error( "Option --$option given twice" ); |
462 | } |
463 | } |
464 | |
465 | private function error( string $msg ) { |
466 | $this->errors[] = $msg; |
467 | } |
468 | |
469 | /** |
470 | * Get any errors encountered while processing parameters. |
471 | * |
472 | * @return string[] |
473 | */ |
474 | public function getErrors(): array { |
475 | return $this->errors; |
476 | } |
477 | |
478 | /** |
479 | * Whether any errors have been recorded so far. |
480 | * |
481 | * @return bool |
482 | */ |
483 | public function hasErrors(): bool { |
484 | return (bool)$this->errors; |
485 | } |
486 | |
487 | /** |
488 | * Set the script name, for use in the help message |
489 | * |
490 | * @param string $name |
491 | */ |
492 | public function setName( string $name ) { |
493 | $this->mName = $name; |
494 | } |
495 | |
496 | /** |
497 | * Get the script name, as shown in the help message |
498 | * |
499 | * @return string |
500 | */ |
501 | public function getName(): string { |
502 | return $this->mName; |
503 | } |
504 | |
505 | /** |
506 | * Force option and argument values. |
507 | * |
508 | * @internal |
509 | * |
510 | * @param array $opts |
511 | * @param array $args |
512 | */ |
513 | public function setOptionsAndArgs( array $opts, array $args ) { |
514 | $this->mOptions = $opts; |
515 | $this->mArgs = $args; |
516 | |
517 | $this->optionsSequence = []; |
518 | foreach ( $opts as $name => $value ) { |
519 | $array = (array)$value; |
520 | |
521 | foreach ( $array as $v ) { |
522 | $this->optionsSequence[] = [ $name, $v ]; |
523 | } |
524 | } |
525 | } |
526 | |
527 | /** |
528 | * Run some validation checks on the params, etc. |
529 | * |
530 | * Error details can be obtained via getErrors(). |
531 | * |
532 | * @return bool |
533 | */ |
534 | public function validate() { |
535 | $valid = true; |
536 | # Check to make sure we've got all the required options |
537 | foreach ( $this->mOptDefs as $opt => $info ) { |
538 | if ( $info['require'] && !$this->hasOption( $opt ) ) { |
539 | $this->error( "Option --$opt is required!" ); |
540 | $valid = false; |
541 | } |
542 | } |
543 | # Check arg list too |
544 | foreach ( $this->mArgDefs as $k => $info ) { |
545 | if ( $info['require'] && !$this->hasArg( $k ) ) { |
546 | $this->error( 'Argument <' . $info['name'] . '> is required!' ); |
547 | $valid = false; |
548 | } |
549 | } |
550 | if ( !$this->mAllowUnregisteredOptions ) { |
551 | # Check for unexpected options |
552 | foreach ( $this->mOptions as $opt => $val ) { |
553 | if ( !$this->supportsOption( $opt ) ) { |
554 | $this->error( "Unexpected option --$opt!" ); |
555 | $valid = false; |
556 | } |
557 | } |
558 | } |
559 | |
560 | return $valid; |
561 | } |
562 | |
563 | /** |
564 | * Get help text. |
565 | * |
566 | * @return string |
567 | */ |
568 | public function getHelp(): string { |
569 | $screenWidth = 80; // TODO: Calculate this! |
570 | $tab = " "; |
571 | $descWidth = $screenWidth - ( 2 * strlen( $tab ) ); |
572 | |
573 | ksort( $this->mOptDefs ); |
574 | |
575 | $output = []; |
576 | |
577 | // Description ... |
578 | if ( $this->mDescription ) { |
579 | $output[] = "\n" . wordwrap( $this->mDescription, $screenWidth ) . "\n"; |
580 | } |
581 | $output[] = "\nUsage: {$this->usagePrefix} " . basename( $this->mName ); |
582 | |
583 | // ... append options ... |
584 | if ( $this->mOptDefs ) { |
585 | $output[] = ' [OPTION]...'; |
586 | |
587 | foreach ( $this->mOptDefs as $name => $opt ) { |
588 | if ( $opt['require'] ) { |
589 | $output[] = " --$name"; |
590 | |
591 | if ( $opt['withArg'] ) { |
592 | $vname = strtoupper( $name ); |
593 | $output[] = " <$vname>"; |
594 | } |
595 | } |
596 | } |
597 | } |
598 | |
599 | // ... and append arguments. |
600 | if ( $this->mArgDefs ) { |
601 | $args = ''; |
602 | foreach ( $this->mArgDefs as $arg ) { |
603 | $argRepr = $this->getArgRepresentation( $arg ); |
604 | |
605 | $args .= ' '; |
606 | $args .= $argRepr; |
607 | } |
608 | $output[] = $args; |
609 | } |
610 | $output[] = "\n\n"; |
611 | |
612 | // Go through the declared groups and output the options for each group separately. |
613 | // Maintain the remaining options in $params. |
614 | $params = $this->mOptDefs; |
615 | foreach ( $this->mOptionGroups as $groupName => $groupOptions ) { |
616 | $output[] = $this->formatHelpItems( |
617 | array_intersect_key( $params, array_flip( $groupOptions ) ), |
618 | $groupName, |
619 | $descWidth, $tab |
620 | ); |
621 | $params = array_diff_key( $params, array_flip( $groupOptions ) ); |
622 | } |
623 | |
624 | $output[] = $this->formatHelpItems( |
625 | $params, |
626 | 'Script specific options', |
627 | $descWidth, $tab |
628 | ); |
629 | |
630 | // Print arguments |
631 | if ( count( $this->mArgDefs ) > 0 ) { |
632 | $output[] = "Arguments:\n"; |
633 | // Arguments description |
634 | foreach ( $this->mArgDefs as $info ) { |
635 | $argRepr = $this->getArgRepresentation( $info ); |
636 | $output[] = |
637 | wordwrap( |
638 | "$tab$argRepr: " . $info['desc'], |
639 | $descWidth, |
640 | "\n$tab$tab" |
641 | ) . "\n"; |
642 | } |
643 | $output[] = "\n"; |
644 | } |
645 | |
646 | return implode( '', $output ); |
647 | } |
648 | |
649 | private function formatHelpItems( array $items, $heading, $descWidth, $tab ) { |
650 | if ( $items === [] ) { |
651 | return ''; |
652 | } |
653 | |
654 | $output = []; |
655 | |
656 | $output[] = "$heading:\n"; |
657 | |
658 | foreach ( $items as $name => $info ) { |
659 | $out = $name; |
660 | |
661 | if ( $info['shortName'] !== false ) { |
662 | $out .= ' (-' . $info['shortName'] . ')'; |
663 | } |
664 | |
665 | if ( $info['withArg'] ) { |
666 | $vname = strtoupper( $name ); |
667 | $out .= " <$vname>"; |
668 | } |
669 | |
670 | $output[] = |
671 | wordwrap( |
672 | "$tab--$out: " . strtr( $info['desc'], [ "\n" => "\n$tab$tab" ] ), |
673 | $descWidth, |
674 | "\n$tab$tab" |
675 | ) . "\n"; |
676 | } |
677 | |
678 | $output[] = "\n"; |
679 | |
680 | return implode( '', $output ); |
681 | } |
682 | |
683 | /** |
684 | * Returns the names of defined options |
685 | * @return string[] |
686 | */ |
687 | public function getOptionNames(): array { |
688 | return array_keys( $this->mOptDefs ); |
689 | } |
690 | |
691 | /** |
692 | * Returns any option values |
693 | * @return array |
694 | */ |
695 | public function getOptions(): array { |
696 | return $this->mOptions; |
697 | } |
698 | |
699 | /** |
700 | * Returns option values as an ordered sequence. |
701 | * Useful for option chaining (Ex. dumpBackup.php). |
702 | * @return array[] a list of pairs of like [ $option, $value ] |
703 | */ |
704 | public function getOptionsSequence(): array { |
705 | return $this->optionsSequence; |
706 | } |
707 | |
708 | /** |
709 | * @param string $usagePrefix |
710 | */ |
711 | public function setUsagePrefix( string $usagePrefix ) { |
712 | $this->usagePrefix = $usagePrefix; |
713 | } |
714 | |
715 | /** |
716 | * @param array $argInfo |
717 | * |
718 | * @return string |
719 | */ |
720 | private function getArgRepresentation( array $argInfo ): string { |
721 | if ( $argInfo['require'] ) { |
722 | $rep = '<' . $argInfo['name'] . '>'; |
723 | } else { |
724 | $rep = '[' . $argInfo['name'] . ']'; |
725 | } |
726 | |
727 | if ( $argInfo['multi'] ) { |
728 | $rep .= '...'; |
729 | } |
730 | |
731 | return $rep; |
732 | } |
733 | |
734 | } |