88use MaplePHP \Blunder \Handlers \CliHandler ;
99use MaplePHP \Core \Middlewares \CliStatusError ;
1010use MaplePHP \Core \Router \RouterDispatcher ;
11+ use MaplePHP \Core \Routing \DefaultCommand ;
1112use MaplePHP \Emitron \Contracts \KernelInterface ;
1213use MaplePHP \Http \Stream ;
14+ use MaplePHP \Prompts \Command ;
15+ use MaplePHP \Prompts \Themes \Blocks ;
1316use Psr \Http \Message \ServerRequestInterface ;
1417use MaplePHP \Emitron \Contracts \DispatchConfigInterface ;
1518use MaplePHP \Emitron \DispatchConfig ;
1922use MaplePHP \Http \Uri ;
2023use MaplePHP \Core \Middlewares \CheckAllowedProps ;
2124use MaplePHP \Unitary \Console \Middlewares \{AddCommandMiddleware ,
22- CliInitMiddleware ,
23- ConfigPropsMiddleware ,
24- LocalMiddleware };
25+ CliInitMiddleware ,
26+ ConfigPropsMiddleware ,
27+ LocalMiddleware
28+ };
2529
2630final class CliKernel extends AbstractKernel
2731{
28- protected array $ middlewares = [
29- AddCommandMiddleware::class,
30- ConfigPropsMiddleware::class,
31- CheckAllowedProps::class,
32- LocalMiddleware::class,
33- CliInitMiddleware::class,
34- CliStatusError::class,
35- ];
36-
37-
38- public function __construct (string $ dir )
39- {
40- parent ::__construct ($ dir );
41- // Default config
42- Kernel::setRouterFilePath ($ dir . "/routers/console.php " );
43- $ this ->stream = new Stream (Stream::STDERR );
44- }
32+ protected array $ middlewares = [
33+ AddCommandMiddleware::class,
34+ ConfigPropsMiddleware::class,
35+ CheckAllowedProps::class,
36+ LocalMiddleware::class,
37+ CliInitMiddleware::class,
38+ CliStatusError::class,
39+ ];
40+
41+
42+ public function __construct (string $ dir )
43+ {
44+ parent ::__construct ($ dir );
45+ // Default config
46+ Kernel::setRouterFilePath ($ dir . "/routers/console.php " );
47+ $ this ->stream = new Stream (Stream::STDERR );
48+ }
4549
4650 /**
4751 * Initialize the HTTP kernel with default framework configuration.
@@ -62,43 +66,154 @@ public function init(): self
6266 ->withErrorHandler (new CliHandler ());
6367 }
6468
65- /**
66- * @param array $parts
67- * @return Kernel
68- * @throws \Exception
69- */
70- public function boot (array $ parts ): KernelInterface
71- {
72- $ env = new Environment ();
73- $ request = new ServerRequest (new Uri ($ env ->getUriParts ($ parts )), $ env );
74- $ config = $ this ->dispatch ($ request );
75- $ kernel = $ this ->load ($ request , $ config );
76- $ kernel ->run ($ request , $ this ->stream );
77- return $ kernel ;
78- }
79-
80- /**
81- * @param ServerRequestInterface $request
82- * @return DispatchConfigInterface
83- * @throws \Exception
84- */
85- private function dispatch (ServerRequestInterface $ request ): DispatchConfigInterface
86- {
87- $ config = new DispatchConfig ();
88-
89- return $ config
90- ->setRouter (function ($ routerFile ) use ($ request ) {
91-
92- $ router = new RouterDispatcher ($ request );
93- $ router ->setDispatchPath ($ request ->getCliKeyword ());
94- //$router = new Router($request->getCliKeyword(), $request->getCliArgs());
95- if (!is_file ($ routerFile )) {
96- throw new Exception ('The routes file ( ' . $ routerFile . ') is missing. ' );
97- }
98- require_once $ routerFile ;
99- require_once App::get ()->coreDir () . "/Router/console.php " ;
100- return $ router ;
101- })
102- ->setProp ('exitCode ' , 0 );
103- }
69+ /**
70+ * Boot the CLI app
71+ *
72+ * @param array $parts
73+ * @return Kernel
74+ * @throws \Exception
75+ */
76+ public function boot (array $ parts ): KernelInterface
77+ {
78+ $ env = new Environment ();
79+ $ request = new ServerRequest (new Uri ($ env ->getUriParts ($ parts )), $ env );
80+ $ config = $ this ->dispatch ($ request );
81+ $ kernel = $ this ->load ($ request , $ config );
82+
83+ $ commandName = $ request ->getCliKeyword ();
84+
85+ if (!$ commandName ) {
86+ $ this ->renderHelp ();
87+ exit (0 );
88+ }
89+
90+ $ commandClass = $ this ->resolve ($ commandName );
91+ if (!$ commandClass ) {
92+ $ command = new Command ($ this ->stream );
93+ $ command ->error ("\nUnknown command: ' $ commandName' \n" );
94+ exit (1 );
95+ }
96+
97+ $ kernel ->run ($ request , $ this ->stream , function ($ classInst , $ response ) {
98+ if ($ classInst instanceof DefaultCommand) {
99+ $ exitCode = $ classInst ->handle ();
100+ if ($ exitCode === 1 ) {
101+ exit (0 );
102+ }
103+ }
104+ });
105+ return $ kernel ;
106+ }
107+
108+ /**
109+ * Access the router dispatcher
110+ *
111+ * @param ServerRequestInterface $request
112+ * @return DispatchConfigInterface
113+ * @throws \Exception
114+ */
115+ private function dispatch (ServerRequestInterface $ request ): DispatchConfigInterface
116+ {
117+ $ config = new DispatchConfig ();
118+ return $ config
119+ ->setRouter (function ($ routerFile ) use ($ request ) {
120+
121+ $ commandName = $ request ->getCliKeyword ();
122+
123+ $ router = new RouterDispatcher ($ request );
124+ $ router ->setDispatchCommand ($ commandName );
125+
126+ if (!is_file (App::get ()->coreDir () . "/Router/console.php " )) {
127+ throw new \RuntimeException ('The CORE routes file ( ' . $ routerFile . ') is missing. ' );
128+ }
129+
130+ if (is_file ($ routerFile )) {
131+ require_once $ routerFile ;
132+ }
133+
134+ require_once App::get ()->coreDir () . "/Router/console.php " ;
135+ return $ router ;
136+ })
137+ ->setProp ('exitCode ' , 0 );
138+ }
139+
140+ /**
141+ * Get command if exits or return null to flag as unknown command
142+ *
143+ * @param string $name
144+ * @return string|null
145+ */
146+ private function resolve (string $ name ): ?string
147+ {
148+ foreach (RouterDispatcher::getCommands () as $ commandClass ) {
149+ [$ commandName ] = $ commandClass ;
150+
151+ if ($ commandName === $ name ) {
152+ return $ commandClass [0 ];
153+ }
154+ }
155+ return null ;
156+ }
157+
158+ /**
159+ * List all available commands
160+ *
161+ * @return void
162+ */
163+ private function renderHelp (): void
164+ {
165+ $ command = new Command ($ this ->stream );
166+ $ blocks = new Blocks ($ command );
167+
168+ $ blocks ->addHeadline ("\n--- MaplePHP Help --- " , "green " );
169+ $ blocks ->addSection ("Usage " , function (Blocks $ inst ) {
170+ return $ inst
171+ ->addExamples (
172+ "./maple [type] [options] " ,
173+ "You can always trigger a command and with options "
174+ )->addExamples (
175+ "./maple serve --help " ,
176+ "You can trigger a dedicated help for each command. "
177+ );
178+ });
179+
180+ $ blocks ->addSection ("Available commands " , function (Blocks $ inst ) {
181+ foreach ($ this ->getParentCommands () as $ name => $ desc ) {
182+ $ inst = $ inst
183+ ->addOption (
184+ "./maple $ name " ,
185+ $ desc
186+ );
187+ }
188+ return $ inst ;
189+ });
190+
191+ $ blocks ->addSpace ();
192+ }
193+
194+ /**
195+ * Will filter out all child command and only return the main command
196+ *
197+ * @return array
198+ */
199+ protected function getParentCommands (): array
200+ {
201+ $ filtered = [];
202+ foreach (RouterDispatcher::getCommands () as $ commandClass ) {
203+ [$ commandName , $ className ] = $ commandClass ;
204+ $ desc = is_a ($ className [0 ], DefaultCommand::class, true )
205+ ? $ className [0 ]::description ()
206+ : null ;
207+
208+ $ commandNames = explode (': ' , $ commandName );
209+ $ commandNameFirst = array_shift ($ commandNames );
210+ $ commandNamesNext = implode (': ' , $ commandNames );
211+ $ filtered [$ commandNameFirst ][$ commandNamesNext ] = $ desc ;
212+ }
213+
214+ return array_map (function ($ item ) {
215+ ksort ($ item );
216+ return reset ($ item );
217+ }, $ filtered );
218+ }
104219}
0 commit comments