22
33namespace T3Docs \GuidesExtension \Command ;
44
5- use phpDocumentor \Guides \Cli \Command \Run ;
6- use Symfony \Component \Console \Application ;
5+ use League \Tactician \CommandBus ;
6+ use Monolog \Handler \ErrorLogHandler ;
7+ use Monolog \Handler \StreamHandler ;
8+ use Monolog \Logger ;
9+ use phpDocumentor \Guides \Cli \Command \ProgressBarSubscriber ;
10+ use phpDocumentor \Guides \Cli \Command \SettingsBuilder ;
11+ use phpDocumentor \Guides \Cli \Internal \RunCommand ;
12+ use phpDocumentor \Guides \Cli \Logger \SpyProcessor ;
13+ use Psr \Log \LogLevel ;
714use Symfony \Component \Console \Command \Command ;
8- use Symfony \Component \Console \Input \ArrayInput ;
915use Symfony \Component \Console \Input \InputArgument ;
10- use Symfony \Component \Console \Input \InputDefinition ;
1116use Symfony \Component \Console \Input \InputInterface ;
1217use Symfony \Component \Console \Input \InputOption ;
18+ use Symfony \Component \Console \Output \ConsoleOutputInterface ;
1319use Symfony \Component \Console \Output \OutputInterface ;
20+ use Symfony \Component \EventDispatcher \EventDispatcher ;
1421use Symfony \Component \Finder \Finder ;
15- use Symfony \Component \Process \Process ;
1622use T3Docs \Typo3DocsTheme \Settings \Typo3DocsInputSettings ;
1723
1824final class RunDecorator extends Command
@@ -36,29 +42,66 @@ final class RunDecorator extends Command
3642 'README.md ' => 'md ' ,
3743 ];
3844
39- private Run $ innerCommand ;
40- public function __construct (Run $ innerCommand , private readonly Typo3DocsInputSettings $ inputSettings )
45+ public function __construct (
46+ private readonly Typo3DocsInputSettings $ inputSettings ,
47+ private readonly SettingsBuilder $ settingsBuilder ,
48+ private readonly CommandBus $ commandBus ,
49+ private readonly EventDispatcher $ eventDispatcher ,
50+ private readonly Logger $ logger ,
51+ private readonly ProgressBarSubscriber $ progressBarSubscriber ,
52+ ) {
53+ parent ::__construct ('run ' );
54+ }
55+
56+ protected function configure (): void
4157 {
42- parent ::__construct ($ innerCommand ->getName ());
43- $ this ->innerCommand = $ innerCommand ;
58+ $ this ->settingsBuilder ->configureCommand ($ this );
59+
60+ $ this ->addOption (
61+ 'log-path ' ,
62+ null ,
63+ InputOption::VALUE_REQUIRED ,
64+ 'Write rendering log to this path ' ,
65+ );
66+ $ this ->addOption (
67+ 'fail-on-log ' ,
68+ null ,
69+ InputOption::VALUE_NONE ,
70+ 'If set, returns a non-zero exit code as soon as any warnings/errors occur ' ,
71+ );
72+
73+ $ this ->addOption (
74+ 'fail-on-error ' ,
75+ null ,
76+ InputOption::VALUE_NONE ,
77+ 'If set, returns a non-zero exit code as soon as any errors occur ' ,
78+ );
79+
80+ $ this ->addOption (
81+ 'progress ' ,
82+ null ,
83+ InputOption::VALUE_NEGATABLE ,
84+ 'Whether to show a progress bar ' ,
85+ true ,
86+ );
4487
45- $ this ->innerCommand -> addOption (
88+ $ this ->addOption (
4689 'localization ' ,
4790 null ,
4891 InputArgument::OPTIONAL ,
4992 'Render a specific localization (for example "de_DE", "ru_RU", ...) ' ,
5093 );
5194
5295 // This option is evaluated in the PostProjectNodeCreated event in packages/typo3-docs-theme/src/EventListeners/AddThemeSettingsToProjectNode.php
53- $ this ->innerCommand -> addOption (
96+ $ this ->addOption (
5497 'minimal-test ' ,
5598 null ,
5699 InputOption::VALUE_NONE ,
57100 'Apply preset for minimal testing (format=singlepage) ' ,
58101 );
59-
60102 }
61103
104+
62105 protected function execute (InputInterface $ input , OutputInterface $ output ): int
63106 {
64107 $ options = [];
@@ -71,25 +114,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int
71114 }
72115
73116 $ arguments = $ input ->getArguments ();
117+ $ guessedInput = [];
74118 if ($ arguments ['input ' ] === null ) {
75119 $ guessedInput = $ this ->guessInput (self ::DEFAULT_INPUT_DIRECTORY , $ output , false );
76- } else {
77- $ guessedInput = [] ;
120+ $ input -> setArgument ( ' input ' , $ guessedInput [ ' input ' ]);
121+ $ input -> setOption ( ' input-format ' , $ guessedInput [ ' --input-format ' ] ?? null ) ;
78122 }
79123
80124 if (!isset ($ options ['--output ' ])) {
81- $ options [ ' -- output '] = getcwd () . '/ ' . self ::DEFAULT_OUTPUT_DIRECTORY ;
125+ $ input -> setOption ( ' output ', getcwd () . '/ ' . self ::DEFAULT_OUTPUT_DIRECTORY ) ;
82126 }
83127
84- $ input = new ArrayInput (
85- [
86- ...$ arguments ,
87- ...$ options ,
88- ...$ guessedInput ,
89- ],
90- $ this ->getDefinition ()
91- );
92-
93128 // Propagate all input settings to be used within events
94129 // through the Typo3DocsInputSettings singleton.
95130 $ this ->inputSettings ->setInput ($ input );
@@ -107,7 +142,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
107142 $ output ->writeln (sprintf ("<info>DEBUG</info> Using parameters: \n%s " , $ readableOutput ));
108143 }
109144
110- $ baseExecution = $ this ->innerCommand -> execute ($ input , $ output );
145+ $ baseExecution = $ this ->internalRun ($ input , $ output );
111146
112147 // When a localization is being rendered, no other sub-localizations
113148 // are allowed, the execution will end here.
@@ -211,95 +246,7 @@ public function renderSingleLocalization(string $availableLocalization, array $b
211246 $ output ->writeln (sprintf ("<info>DEBUG</info> Using parameters: \n%s " , $ readableOutput ));
212247 }
213248
214- $ processArguments = array_merge (['env ' , 'php ' , $ _SERVER ['PHP_SELF ' ]], $ this ->retrieveLocalizationArgumentsFromCurrentArguments ($ input ));
215-
216- $ process = new Process ($ processArguments );
217- $ output ->writeln (sprintf ('<info>SUB-PROCESS:</info> %s ' , $ process ->getCommandLine ()));
218- $ hasErrors = false ;
219- $ result = $ process ->run (function ($ type , $ buffer ) use ($ output , &$ hasErrors ): void {
220- if ($ type === Process::ERR ) {
221- $ output ->write ('<error> ' . $ buffer . '</error> ' );
222- $ hasErrors = true ;
223- } else {
224- $ output ->write ($ buffer );
225- }
226- });
227-
228- if ($ hasErrors ) {
229- return Command::FAILURE ;
230- }
231-
232- return Command::SUCCESS ;
233- }
234-
235- /** @return mixed[] */
236- public function retrieveLocalizationArgumentsFromCurrentArguments (InputInterface $ input ): array
237- {
238- $ arguments = $ input ->getArguments ();
239- $ options = $ input ->getOptions ();
240-
241- $ shellCommands = [];
242- foreach ($ options as $ option => $ value ) {
243- if (is_bool ($ value ) && $ value ) {
244- $ shellCommands [] = "-- $ option " ;
245- } elseif (is_string ($ value )) {
246- $ shellCommands [] = "-- $ option= " . $ value ;
247- }
248- }
249-
250- // Localizations are rendered as a sub-process. There the progress bar
251- // disturbs the output that is returned. We only want normal and error output then.
252- $ shellCommands [] = '--no-progress ' ;
253-
254- foreach ($ arguments as $ argument ) {
255- if (is_string ($ argument )) {
256- $ shellCommands [] = $ argument ;
257- }
258- }
259-
260- return $ shellCommands ;
261- }
262-
263- public function getDescription (): string
264- {
265- return $ this ->innerCommand ->getDescription ();
266- }
267-
268- public function getHelp (): string
269- {
270- return $ this ->innerCommand ->getHelp ();
271- }
272-
273- public function setApplication (Application $ application = null ): void
274- {
275- parent ::setApplication ($ application );
276- $ this ->innerCommand ->setApplication ($ application );
277- }
278-
279- /** @return mixed[] */
280- public function getUsages (): array
281- {
282- return $ this ->innerCommand ->getUsages ();
283- }
284-
285- public function getNativeDefinition (): InputDefinition
286- {
287- return $ this ->innerCommand ->getNativeDefinition ();
288- }
289-
290- public function getSynopsis (bool $ short = false ): string
291- {
292- return $ this ->innerCommand ->getSynopsis ($ short );
293- }
294-
295- public function getDefinition (): InputDefinition
296- {
297- return $ this ->innerCommand ->getDefinition ();
298- }
299-
300- public function mergeApplicationDefinition (bool $ mergeArgs = true ): void
301- {
302- $ this ->innerCommand ->mergeApplicationDefinition ($ mergeArgs );
249+ return $ this ->internalRun ($ input , $ output );
303250 }
304251
305252 /** @return array<string, string> */
@@ -363,4 +310,53 @@ private function guessInput(string $inputBaseDirectory, OutputInterface $output,
363310
364311 return [];
365312 }
313+
314+ private function internalRun (InputInterface $ input , OutputInterface $ output ): int
315+ {
316+ $ this ->settingsBuilder ->overrideWithInput ($ input );
317+ $ projectNode = $ this ->settingsBuilder ->createProjectNode ();
318+ $ settings = $ this ->settingsBuilder ->getSettings ();
319+
320+ $ logPath = $ settings ->getLogPath ();
321+ if ($ logPath === 'php://stder ' ) {
322+ $ this ->logger ->setHandlers ([new ErrorLogHandler (ErrorLogHandler::OPERATING_SYSTEM , Logger::WARNING )]);
323+ } else {
324+ $ this ->logger ->setHandlers ([new StreamHandler ($ logPath . '/warning.log ' , Logger::WARNING ), new StreamHandler ($ logPath . '/error.log ' , Logger::ERROR )]);
325+ }
326+
327+ if ($ settings ->isFailOnError ()) {
328+ $ spyProcessor = new SpyProcessor ($ settings ->getFailOnError () ?? LogLevel::WARNING );
329+ $ this ->logger ->pushProcessor ($ spyProcessor );
330+ }
331+
332+ if ($ output instanceof ConsoleOutputInterface && $ settings ->isShowProgressBar ()) {
333+ $ this ->progressBarSubscriber ->subscribe ($ output , $ this ->eventDispatcher );
334+ }
335+
336+ $ documents = $ this ->commandBus ->handle (
337+ new RunCommand ($ settings , $ projectNode , $ input ),
338+ );
339+
340+ $ outputFormats = $ settings ->getOutputFormats ();
341+ $ outputDir = $ settings ->getOutput ();
342+ if ($ output ->isQuiet () === false ) {
343+ $ lastFormat = '' ;
344+
345+ if (count ($ outputFormats ) > 1 ) {
346+ $ lastFormat = (count ($ outputFormats ) > 2 ? ', ' : '' ) . ' and ' . strtoupper ((string ) array_pop ($ outputFormats ));
347+ }
348+
349+ $ formatsText = strtoupper (implode (', ' , $ outputFormats )) . $ lastFormat ;
350+
351+ $ output ->writeln (
352+ 'Successfully placed ' . (is_countable ($ documents ) ? count ($ documents ) : 0 ) . ' rendered ' . $ formatsText . ' files into ' . $ outputDir ,
353+ );
354+ }
355+
356+ if ($ settings ->isFailOnError () && $ spyProcessor ->hasBeenCalled ()) {
357+ return Command::FAILURE ;
358+ }
359+
360+ return Command::SUCCESS ;
361+ }
366362}
0 commit comments