Skip to content

Commit 8d9d46a

Browse files
authored
Merge pull request #18 from smeghead/fix/fix-how-to-handle-options
fix: change how to parse arguments.
2 parents c3d2f9b + 7a40d4f commit 8d9d46a

File tree

6 files changed

+295
-83
lines changed

6 files changed

+295
-83
lines changed

.github/workflows/php.yml

+3
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,6 @@ jobs:
4040

4141
- name: Run PHPStan
4242
run: composer run-script phpstan
43+
44+
- name: Run php-variable-hard-usage
45+
run: composer run-script php-variable-hard-usage

composer.json

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
],
3232
"phpstan": [
3333
"vendor/bin/phpstan analyse"
34+
],
35+
"php-variable-hard-usage": [
36+
"php bin/php-variable-hard-usage check --threshold=300 src/"
3437
]
3538
},
3639
"bin": [

src/Command.php

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Smeghead\PhpVariableHardUsage;
66

7+
use Smeghead\PhpVariableHardUsage\Option\CommandFactory;
8+
79
final class Command
810
{
911
/**
@@ -12,8 +14,8 @@ final class Command
1214
*/
1315
public function run(array $argv): int
1416
{
15-
$factory = new CommandFactory();
16-
$command = $factory->createCommand($argv);
17+
$factory = new CommandFactory($argv);
18+
$command = $factory->create();
1719
return $command->execute();
1820
}
1921
}

src/CommandFactory.php

-81
This file was deleted.

src/Option/CommandFactory.php

+223
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpVariableHardUsage\Option;
6+
7+
use Smeghead\PhpVariableHardUsage\Command\CheckCommand;
8+
use Smeghead\PhpVariableHardUsage\Command\CommandInterface;
9+
use Smeghead\PhpVariableHardUsage\Command\HelpCommand;
10+
use Smeghead\PhpVariableHardUsage\Command\ScopesCommand;
11+
use Smeghead\PhpVariableHardUsage\Command\SingleCommand;
12+
use Smeghead\PhpVariableHardUsage\Command\VersionCommand;
13+
14+
/**
15+
* コマンドライン引数を解析し、適切なコマンドと引数を生成するクラス
16+
*/
17+
final class CommandFactory
18+
{
19+
/** @var array<string> */
20+
private array $argv;
21+
22+
/**
23+
* @param array<string> $argv コマンドライン引数
24+
*/
25+
public function __construct(array $argv)
26+
{
27+
$this->argv = $argv;
28+
}
29+
30+
/**
31+
* コマンドライン引数を解析し、コマンドと引数を返す
32+
*/
33+
public function create(): CommandInterface
34+
{
35+
// 引数がない場合はヘルプコマンド
36+
if (count($this->argv) < 2) {
37+
return new HelpCommand();
38+
}
39+
40+
$command = $this->argv[1];
41+
42+
// ヘルプと バージョン表示は特別処理
43+
if ($command === '--help') {
44+
return new HelpCommand();
45+
}
46+
47+
if ($command === '--version') {
48+
return new VersionCommand();
49+
}
50+
51+
// コマンドに応じた処理
52+
switch ($command) {
53+
case 'single':
54+
return $this->parseSingleCommand();
55+
56+
case 'scopes':
57+
return $this->parseScopesCommand();
58+
59+
case 'check':
60+
return $this->parseCheckCommand();
61+
62+
default:
63+
// 後方互換性のため、引数そのものをファイル名として解釈
64+
return new SingleCommand($command);
65+
}
66+
}
67+
68+
/**
69+
* 単一ファイルコマンドを解析
70+
*/
71+
private function parseSingleCommand(): CommandInterface
72+
{
73+
$args = array_slice($this->argv, 2);
74+
75+
if (empty($args)) {
76+
return new HelpCommand();
77+
}
78+
79+
return new SingleCommand($args[0]);
80+
}
81+
82+
/**
83+
* スコープコマンドを解析
84+
*/
85+
private function parseScopesCommand(): CommandInterface
86+
{
87+
$args = array_slice($this->argv, 2);
88+
89+
if (empty($args)) {
90+
return new HelpCommand();
91+
}
92+
93+
return new ScopesCommand($args);
94+
}
95+
96+
/**
97+
* チェックコマンドを解析
98+
*/
99+
private function parseCheckCommand(): CommandInterface
100+
{
101+
$args = array_slice($this->argv, 2);
102+
103+
if (empty($args)) {
104+
return new HelpCommand();
105+
}
106+
107+
$parsedArgs = $this->parseArguments($args);
108+
109+
if (empty($parsedArgs->paths)) {
110+
return new HelpCommand();
111+
}
112+
113+
$threshold = isset($parsedArgs->options['threshold']) ? intval($parsedArgs->options['threshold']) : null;
114+
115+
return new CheckCommand($parsedArgs->paths, $threshold);
116+
}
117+
118+
/**
119+
* コマンドライン引数を解析して、オプションとパスに分離する
120+
*
121+
* @param array<string> $args
122+
* @return ParsedArguments
123+
*/
124+
private function parseArguments(array $args): ParsedArguments
125+
{
126+
$options = [];
127+
$paths = [];
128+
129+
$i = 0;
130+
while ($i < count($args)) {
131+
$arg = $args[$i];
132+
133+
if ($this->isOptionWithValue($arg, '--threshold', $args, $i)) {
134+
$options['threshold'] = (int)$args[$i + 1];
135+
$i += 2;
136+
} elseif ($this->isOptionWithInlineValue($arg, '--threshold=', $matches)) {
137+
$options['threshold'] = (int)$matches[1];
138+
$i++;
139+
} elseif ($this->isOption($arg)) {
140+
[$name, $value] = $this->parseOption($arg);
141+
$options[$name] = $value;
142+
$i++;
143+
} else {
144+
$paths[] = $arg;
145+
$i++;
146+
}
147+
}
148+
149+
return new ParsedArguments($paths, $options);
150+
}
151+
152+
/**
153+
* 値を持つオプションかどうかを判定
154+
*
155+
* @param string $arg 現在の引数
156+
* @param string $optionName オプション名
157+
* @param array<string> $args 全引数
158+
* @param int $index 現在の位置
159+
* @return bool
160+
*/
161+
private function isOptionWithValue(string $arg, string $optionName, array $args, int $index): bool
162+
{
163+
return $arg === $optionName && isset($args[$index + 1]);
164+
}
165+
166+
/**
167+
* インライン値を持つオプションかどうかを判定
168+
*
169+
* @param string $arg 現在の引数
170+
* @param string $prefix オプションのプレフィックス
171+
* @param null &$matches 正規表現のマッチ結果を格納する変数
172+
* @return bool
173+
*/
174+
private function isOptionWithInlineValue(string $arg, string $prefix, &$matches): bool
175+
{
176+
return preg_match('/^' . preg_quote($prefix, '/') . '(\d+)$/', $arg, $matches) === 1;
177+
}
178+
179+
/**
180+
* オプションかどうかを判定
181+
*
182+
* @param string $arg 現在の引数
183+
* @return bool
184+
*/
185+
private function isOption(string $arg): bool
186+
{
187+
return strpos($arg, '--') === 0;
188+
}
189+
190+
/**
191+
* オプション文字列をパースして名前と値を取得
192+
*
193+
* @param string $option オプション文字列
194+
* @return array{0: string, 1: string|bool} [オプション名, オプション値]
195+
*/
196+
private function parseOption(string $option): array
197+
{
198+
$optName = substr($option, 2);
199+
200+
if (strpos($optName, '=') !== false) {
201+
[$name, $value] = explode('=', $optName, 2);
202+
return [$name, $value];
203+
}
204+
205+
return [$optName, true];
206+
}
207+
}
208+
209+
/**
210+
* パース済みの引数を表すクラス
211+
*/
212+
final class ParsedArguments
213+
{
214+
/**
215+
* @param array<string> $paths パスのリスト
216+
* @param array<string, string|int|bool|null> $options オプションのマップ
217+
*/
218+
public function __construct(
219+
public readonly array $paths,
220+
public readonly array $options
221+
) {
222+
}
223+
}

0 commit comments

Comments
 (0)