77use InvalidArgumentException ;
88use RuntimeException ;
99
10- class Program
10+ final class Program
1111{
1212 protected string $ projectRoot ;
1313
@@ -30,23 +30,6 @@ class Program
3030 'unlock ' => '🔓 ' , 'bell ' => '🔔 ' , 'coffee ' => '☕ ' ,
3131 ];
3232
33- public function __construct (protected string $ binFile , protected string $ userRoot )
34- {
35- if (! is_file ($ binFile )) {
36- throw new InvalidArgumentException ("$ binFile is not a file " );
37- }
38-
39- if (! is_dir ($ userRoot )) {
40- throw new InvalidArgumentException ("$ userRoot is not a directory " );
41- }
42-
43- $ this ->binFile = realpath ($ binFile );
44- $ this ->userRoot = realpath ($ userRoot );
45- $ this ->projectRoot = dirname (__DIR__ );
46-
47- $ this ->configure ();
48- }
49-
5033 public function configure (): void
5134 {
5235 $ this ->composerJson = $ this ->loadComposerJson ($ this ->userRoot );
@@ -72,67 +55,16 @@ public function setName(string $name): static
7255 return $ this ;
7356 }
7457
75- public function getName (): string
76- {
77- return $ this ->name ;
78- }
79-
8058 public function setDescription (string $ description ): static
8159 {
8260 $ this ->description = $ description ;
8361
8462 return $ this ;
8563 }
8664
87- public function getDescription (): string
88- {
89- return $ this ->description ;
90- }
91-
92- protected function loadComposerJson (string $ dir ): mixed
93- {
94- return $ this ->loadJson ($ dir .DIRECTORY_SEPARATOR .'composer.json ' , true );
95- }
96-
97- protected function loadJson (string $ file , bool $ assoc = false ): mixed
98- {
99- if (! is_file ($ file )) {
100- throw new InvalidArgumentException ("$ file is not a file " );
101- }
102-
103- $ file = realpath ($ file );
104- $ data = file_get_contents ($ file );
105-
106- if (! json_validate ($ data )) {
107- throw new RuntimeException ("Invalid `composer.json` file ( $ file) " );
108- }
109-
110- return json_decode ($ data , $ assoc );
111- }
112-
113- /**
114- * @param null|array<string> $argv
115- **/
116- public function run (?int $ argc = null , ?array $ argv = null ): never
117- {
118- extract ($ this ->parseArgs ($ argc , $ argv ));
119- $ binFile = realpath ($ argv0 );
120-
121- if ($ binFile !== $ this ->binFile && basename ($ this ->binFile ) !== basename ($ binFile ) && dirname ($ binFile ) !== dirname (__DIR__ )) {
122- $ this ->sendError ('Cannot run %s from %s ' , $ this ->binFile , $ argv0 );
123- }
124-
125- $ code = $ this ->execute ($ args );
126-
127- exit ($ code );
128- }
129-
130- /**
131- * @param null|array<string> $args
132- **/
13365 public function execute (array $ args ): int
13466 {
135- $ this ->output ("Project: $ this ->name \n\t$ this ->description " .PHP_EOL );
67+ $ this ->output ("Project: { $ this ->name } \n\t{ $ this ->description } " .PHP_EOL );
13668 if (! count ($ args )) {
13769 $ code = $ this ->scan ('❯ ' );
13870 $ this ->output ($ code );
@@ -141,6 +73,11 @@ public function execute(array $args): int
14173 return 0 ;
14274 }
14375
76+ /**
77+ * @return (array|mixed|null)[]
78+ *
79+ * @psalm-return array{argv0: mixed|null, args: array}
80+ */
14481 public function parseArgs (int $ argc , array $ argv ): array
14582 {
14683 /** @var array<string> */
@@ -169,23 +106,15 @@ public function command(string $name, ?callable $action = null): ?callable
169106 $ name = trim ($ name , DIRECTORY_SEPARATOR );
170107
171108 foreach ($ this ->commands as $ pattern => $ action ) {
172- if (preg_match ("/^ $ pattern$/ " , $ name , $ matches )) {
109+ if (preg_match ("/^ { $ pattern} $/ " , $ name , $ matches )) {
173110 return $ action ;
174111 }
175112 }
176113
177114 return null ;
178115 }
179116
180- public function executeCommand (string $ name , array $ args ): mixed
181- {
182- if ($ process = $ this ->command ($ name )) {
183- return $ process (...$ args );
184- }
185- $ this ->sendError ("Command $ name not found " );
186- }
187-
188- public function sendError (string $ message , mixed ...$ args ): never
117+ public function sendError (string $ message , string ...$ args ): never
189118 {
190119 $ this ->output ($ message , ...$ args );
191120 $ this ->end (1 );
@@ -196,12 +125,10 @@ public function output(string $message, mixed ...$args): void
196125 $ this ->print (STDOUT , $ message , ...$ args );
197126 }
198127
199- public function error (string $ message , mixed ...$ args ): void
200- {
201- $ this ->print (STDOUT , $ this ->icon ('error ' )." {$ message }" , ...$ args );
202- }
203-
204- public function print ($ stream , $ message , mixed ...$ args ): void
128+ /**
129+ * @param resource $stream
130+ */
131+ public function print ($ stream , string $ message , mixed ...$ args ): void
205132 {
206133 $ message .= PHP_EOL ;
207134
@@ -212,52 +139,39 @@ public function print($stream, $message, mixed ...$args): void
212139 }
213140 }
214141
215- public function scan (?string $ prompt = null ): string
142+ public function scan (?string $ prompt = null ): string | false
216143 {
217144 return isset ($ prompt ) ? readline ($ prompt ) : trim (fgets (STDIN ));
218145 }
219146
220- public function scanString (? string $ prompt = null ): string
147+ public function end ( int $ code = 0 ): never
221148 {
222- return $ this -> scan ( $ prompt );
149+ exit ( $ code );
223150 }
224151
225- public function scanInt (? string $ prompt = null ): int
152+ public function icon ( string $ name ): string
226153 {
227- return ( int ) $ this ->scan ( $ prompt ) ;
154+ return $ this ->icons [ $ name ] ?? '' ;
228155 }
229156
230- public function scanFloat (? string $ prompt = null ): float
157+ protected function loadComposerJson ( string $ dir ): mixed
231158 {
232- return ( float ) $ this ->scan ( $ prompt );
159+ return $ this ->loadJson ( $ dir . DIRECTORY_SEPARATOR . ' composer.json ' , true );
233160 }
234161
235- public function scanBool (? string $ prompt = null ): bool
162+ protected function loadJson ( string $ file , bool $ assoc = false ): mixed
236163 {
237- $ input = strtolower ($ this ->scan ($ prompt .' (yes/no) ' ));
164+ if (! is_file ($ file )) {
165+ throw new InvalidArgumentException ("{$ file } is not a file " );
166+ }
238167
239- return in_array ( $ input , [ ' yes ' , ' y ' , ' true ' , ' 1 ' ] );
240- }
168+ $ file = realpath ( $ file );
169+ $ data = file_get_contents ( $ file );
241170
242- public function scanChoice (string $ prompt , array $ choices ): string
243- {
244- while (true ) {
245- echo $ prompt .' [ ' .implode ('/ ' , $ choices ).']: ' ;
246- $ input = strtolower ($ this ->scan ());
247- if (in_array ($ input , $ choices )) {
248- return $ input ;
249- }
250- $ this ->sendError ("Invalid choice. Please try again. \n" );
171+ if (! json_validate ($ data )) {
172+ throw new RuntimeException ("Invalid `composer.json` file ( {$ file }) " );
251173 }
252- }
253174
254- public function end (int $ code = 0 ): never
255- {
256- exit ($ code );
257- }
258-
259- public function icon (string $ name ): string
260- {
261- return $ this ->icons [$ name ] ?? '' ;
175+ return json_decode ($ data , $ assoc );
262176 }
263177}
0 commit comments