16
16
*/
17
17
final class CommandFactory
18
18
{
19
- /** @var array<string> */
20
- private array $ argv ;
19
+ /** @var list<string> */
20
+ private const array SUB_COMMANDS = [
21
+ 'single ' ,
22
+ 'scopes ' ,
23
+ 'check ' ,
24
+ ];
21
25
22
26
/**
27
+ * @param array<string, string|bool> $options オプション
23
28
* @param array<string> $argv コマンドライン引数
24
29
*/
25
- public function __construct (array $ argv )
30
+ public function __construct (private readonly array $ options , private readonly array $ argv )
26
31
{
27
- $ this ->argv = $ argv ;
28
32
}
29
33
30
34
/**
31
35
* コマンドライン引数を解析し、コマンドと引数を返す
32
36
*/
33
37
public function create (): CommandInterface
34
38
{
35
- // 引数がない場合はヘルプコマンド
36
- if (count ($ this ->argv ) < 2 ) {
37
- return new HelpCommand ();
38
- }
39
-
40
- $ command = $ this ->argv [1 ];
41
-
42
39
// ヘルプと バージョン表示は特別処理
43
- if ($ command === ' -- help ' ) {
40
+ if (array_key_exists ( ' help ', $ this -> options ) ) {
44
41
return new HelpCommand ();
45
42
}
46
43
47
- if ($ command === ' -- version ' ) {
44
+ if (array_key_exists ( ' version ', $ this -> options ) ) {
48
45
return new VersionCommand ();
49
46
}
50
47
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 );
48
+ if (count ($ this ->argv ) === 0 ) {
49
+ return new HelpCommand ();
50
+ }
51
+
52
+ $ paths = $ this ->argv ;
53
+ if (in_array ($ this ->argv [0 ], self ::SUB_COMMANDS , true )) {
54
+ $ subCommand = $ this ->argv [0 ];
55
+ $ paths = array_slice ($ this ->argv , 1 );
56
+ // コマンドに応じた処理
57
+ switch ($ subCommand ) {
58
+ case 'single ' :
59
+ return $ this ->parseSingleCommand ($ paths );
60
+ case 'scopes ' :
61
+ return $ this ->parseScopesCommand ($ paths );
62
+ case 'check ' :
63
+ return $ this ->parseCheckCommand ($ paths );
64
+ }
65
65
}
66
+ return new SingleCommand ($ paths [0 ]);
66
67
}
67
68
68
69
/**
69
70
* 単一ファイルコマンドを解析
71
+ *
72
+ * @param list<string> $paths
70
73
*/
71
- private function parseSingleCommand (): CommandInterface
74
+ private function parseSingleCommand (array $ paths ): CommandInterface
72
75
{
73
- $ args = array_slice ($ this ->argv , 2 );
74
-
75
- if (empty ($ args )) {
76
+ if (empty ($ paths )) {
76
77
return new HelpCommand ();
77
78
}
78
79
79
- return new SingleCommand ($ args [0 ]);
80
+ return new SingleCommand ($ paths [0 ]);
80
81
}
81
82
82
83
/**
83
84
* スコープコマンドを解析
85
+ * @param list<string> $paths
84
86
*/
85
- private function parseScopesCommand (): CommandInterface
87
+ private function parseScopesCommand (array $ paths ): CommandInterface
86
88
{
87
- $ args = array_slice ($ this ->argv , 2 );
88
-
89
- if (empty ($ args )) {
89
+ if (empty ($ paths )) {
90
90
return new HelpCommand ();
91
91
}
92
92
93
- return new ScopesCommand ($ args );
93
+ return new ScopesCommand ($ paths );
94
94
}
95
95
96
96
/**
97
97
* チェックコマンドを解析
98
+ * @param list<string> $paths
98
99
*/
99
- private function parseCheckCommand (): CommandInterface
100
+ private function parseCheckCommand (array $ paths ): CommandInterface
100
101
{
101
- $ args = array_slice ($ this ->argv , 2 );
102
-
103
- if (empty ($ args )) {
102
+ if (empty ($ paths )) {
104
103
return new HelpCommand ();
105
104
}
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 ];
105
+
106
+ $ threshold = $ this ->options ['threshold ' ] ?? null ;
107
+ if (isset ($ threshold )) {
108
+ $ threshold = (int ) $ threshold ;
203
109
}
204
110
205
- return [ $ optName , true ] ;
206
- }
111
+ return new CheckCommand ( $ paths , $ threshold ) ;
112
+ }
207
113
}
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