Skip to content

Commit 7aaeb99

Browse files
committed
Add basic usage documentation generation
1 parent 9551440 commit 7aaeb99

File tree

3 files changed

+90
-13
lines changed

3 files changed

+90
-13
lines changed

psalm.baseline.xml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<files psalm-version="5.18.0@b113f3ed0259fd6e212d87c3df80eec95a6abf19">
2+
<files psalm-version="6.13.1@1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51">
33
<file src="src/CLIParser.php">
44
<InvalidPropertyAssignmentValue>
5-
<code>$allowedOptions</code>
5+
<code><![CDATA[$allowedOptions]]></code>
66
</InvalidPropertyAssignmentValue>
7+
<RiskyTruthyFalsyComparison>
8+
<code><![CDATA[empty($opt['value'])]]></code>
9+
<code><![CDATA[empty($option['value'])]]></code>
10+
<code><![CDATA[empty($this->allowedOptions)]]></code>
11+
</RiskyTruthyFalsyComparison>
712
</file>
813
</files>

rector.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use Rector\Config\RectorConfig;
55
use Rector\ValueObject\PhpVersion;
66
use Rector\Set\ValueObject\LevelSetList;
7+
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
78

89
return static function (RectorConfig $rectorConfig): void {
910
$rectorConfig->paths([
@@ -16,4 +17,8 @@
1617
$rectorConfig->sets([
1718
LevelSetList::UP_TO_PHP_80
1819
]);
20+
21+
$rectorConfig->skip([
22+
ClassPropertyAssignToConstructorPromotionRector::class
23+
]);
1924
};

src/CLIParser.php

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,24 @@ final class CLIParser {
1010

1111
/**
1212
* @var string[]
13+
* @readonly
1314
* @psalm-var list<string>
1415
*/
1516
private array $args;
17+
/**
18+
* @readonly
19+
*/
20+
private string $optionsString;
1621
/**
1722
* @psalm-var null|list<string>|array<string, array{
1823
* filter: int,
1924
* flags?: int,
2025
* options?: array{
2126
* default?: scalar,
2227
* ...
23-
* }
28+
* },
29+
* value_label?: string,
30+
* description?: string
2431
* }>
2532
*/
2633
private ?array $allowedOptions = null;
@@ -52,10 +59,11 @@ final class CLIParser {
5259
/**
5360
* @param string[] $args e.g. $_SERVER['argv']
5461
* @psalm-param list<string> $args
62+
* @param string $optionsString will be printed inside usage documentation right after the script name and should contain e.g. `--in=<file> [--out=<file>]`
5563
*/
56-
public function __construct(array $args) {
57-
array_shift($args);
64+
public function __construct(array $args, string $optionsString = '') {
5865
$this->args = $args;
66+
$this->optionsString = $optionsString;
5967
}
6068

6169
/**
@@ -68,7 +76,9 @@ public function __construct(array $args) {
6876
* options?: array{
6977
* default?: scalar,
7078
* ...
71-
* }
79+
* },
80+
* value_label?: string,
81+
* description?: string
7282
* }> $allowedOptions
7383
* @see https://www.php.net/manual/en/function.filter-var-array.php
7484
*/
@@ -109,6 +119,61 @@ public function setStrictMode(bool $strictMode): void {
109119
$this->strictMode = $strictMode;
110120
}
111121

122+
public function printUsage(): void {
123+
$usage = "\e[33mUsage:\e[0m".PHP_EOL;
124+
$script = $_SERVER['argv'][0] ?? 'script.php';
125+
$usage .= 'php '.$script.' '.$this->optionsString.PHP_EOL.PHP_EOL;
126+
if(!empty($this->allowedOptions)){
127+
$usage .= "\e[33mOptions:\e[0m".PHP_EOL;
128+
$isList = array_is_list($this->allowedOptions);
129+
$options = [];
130+
foreach($this->allowedOptions as $key => $option){
131+
if($isList){
132+
/**
133+
* @var string $option
134+
* @var string|false $flag
135+
*/
136+
$flag = array_search($option, $this->allowedFlags ?? [], true);
137+
$options[] = [
138+
'name' => $option,
139+
'flag' => $flag,
140+
'value' => '',
141+
'description' => '',
142+
'default' => null
143+
];
144+
} else {
145+
/**
146+
* @var string $key
147+
* @var string|false $flag
148+
*/
149+
$flag = array_search($key, $this->allowedFlags ?? [], true);
150+
$options[] = [
151+
'name' => $key,
152+
'flag' => $flag,
153+
'value' => $option['value_label'] ?? '',
154+
'description' => $option['description'] ?? '',
155+
'default' => $option['options']['default'] ?? null
156+
];
157+
}
158+
}
159+
$maxLength = 0;
160+
foreach($options as $key => $opt){
161+
$len = strlen(($opt['flag'] === false ? '' : '-'.$opt['flag'].', ').'--'.$opt['name'].(empty($opt['value']) ? '' : '=<'.$opt['value'].'>'));
162+
$options[$key]['length'] = $len;
163+
if($maxLength < $len){
164+
$maxLength = $len;
165+
}
166+
}
167+
foreach($options as $option){
168+
$padding = str_pad(' ', $maxLength + 2 - $option['length']);
169+
$usage .= " \e[32m".($option['flag'] === false ? '' : '-'.$option['flag'].', ').'--'.$option['name']."\e[0m".(empty($option['value']) ? '' : "=<\e[34m".$option['value']."\e[0m>").
170+
$padding.$option['description'].($option['default'] === null ? '' : " DEFAULT: \e[34m".strval($option['default'])."\e[0m").PHP_EOL;
171+
}
172+
$usage .= PHP_EOL;
173+
}
174+
echo $usage;
175+
}
176+
112177
private function validateOption(string $option, ?string $value): bool {
113178
if($this->allowedOptions === null || (array_is_list($this->allowedOptions) && in_array($option, $this->allowedOptions))){
114179
$this->options[$option] = $value ?? true;
@@ -171,7 +236,9 @@ public function parse(): bool {
171236
}
172237

173238
$endofoptions = false;
174-
while(($arg = array_shift($this->args)) !== null){
239+
$args = $this->args;
240+
array_shift($args);
241+
while(($arg = array_shift($args)) !== null){
175242
if($endofoptions){ // if we have reached end of options, we cast all remaining argvs as arguments
176243
$this->arguments[] = $arg;
177244

@@ -186,12 +253,12 @@ public function parse(): bool {
186253
if($equalPos !== false){ // is it the syntax '--option=value'?
187254
$value = mb_substr($option, $equalPos + 1);
188255
$option = mb_substr($option, 0, $equalPos);
189-
} else if(($this->args[0][0] ?? '-') !== '-'){ // is the option not followed by another option/flag but by arguments
190-
while(($this->args[0][0] ?? '-') !== '-'){
256+
} else if(($args[0][0] ?? '-') !== '-'){ // is the option not followed by another option/flag but by arguments
257+
while(($args[0][0] ?? '-') !== '-'){
191258
/**
192259
* @psalm-suppress PossiblyNullOperand
193260
*/
194-
$value .= array_shift($this->args).' ';
261+
$value .= array_shift($args).' ';
195262
}
196263
$value = rtrim($value, ' ');
197264
}
@@ -222,13 +289,13 @@ public function parse(): bool {
222289
$value = null;
223290
if($chr === '='){ // is it the syntax '-f=value'?
224291
$value = mb_substr($arg, $i + 1);
225-
} else if(($this->args[0][0] ?? '-') !== '-'){ // is the flag not followed by another option/flag but by arguments
292+
} else if(($args[0][0] ?? '-') !== '-'){ // is the flag not followed by another option/flag but by arguments
226293
$value = '';
227-
while(($this->args[0][0] ?? '-') !== '-'){
294+
while(($args[0][0] ?? '-') !== '-'){
228295
/**
229296
* @psalm-suppress PossiblyNullOperand
230297
*/
231-
$value .= array_shift($this->args).' ';
298+
$value .= array_shift($args).' ';
232299
}
233300
$value = rtrim($value, ' ');
234301
}

0 commit comments

Comments
 (0)