Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
34.48% covered (danger)
34.48%
30 / 87
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractRenamespacerCommand
34.48% covered (danger)
34.48%
30 / 87
20.00% covered (danger)
20.00%
1 / 5
200.77
0.00% covered (danger)
0.00%
0 / 1
 configure
100.00% covered (success)
100.00%
30 / 30
100.00% covered (success)
100.00%
1 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
42
 initialize
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
20
 getConsoleLogger
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
156
 createConfig
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Log level, filesystem
4 */
5
6namespace BrianHenryIE\Strauss\Console\Commands;
7
8use BrianHenryIE\Strauss\Composer\Extra\StraussConfig;
9use BrianHenryIE\Strauss\Composer\ProjectComposerPackage;
10use BrianHenryIE\Strauss\Helpers\FileSystem;
11use BrianHenryIE\Strauss\Helpers\Log\PadColonColumnsLogProcessor;
12use BrianHenryIE\Strauss\Helpers\Log\RelativeFilepathLogProcessor;
13use BrianHenryIE\Strauss\Helpers\ReadOnlyFileSystem;
14use Composer\InstalledVersions;
15use Elazar\Flystream\FilesystemRegistry;
16use Monolog\Handler\PsrHandler;
17use Monolog\Logger;
18use Monolog\Processor\PsrLogMessageProcessor;
19use Psr\Log\LoggerAwareTrait;
20use Symfony\Component\Console\Command\Command;
21use Symfony\Component\Console\Input\InputInterface;
22use Symfony\Component\Console\Input\InputOption;
23use Symfony\Component\Console\Output\OutputInterface;
24use League\Flysystem\Config;
25use League\Flysystem\Local\LocalFilesystemAdapter;
26use Psr\Log\LoggerInterface;
27use Psr\Log\LogLevel;
28use Psr\Log\NullLogger;
29use Symfony\Component\Console\Logger\ConsoleLogger;
30
31abstract class AbstractRenamespacerCommand extends Command
32{
33    use LoggerAwareTrait;
34
35    /** No trailing slash */
36    protected string $workingDir;
37
38    /** @var FileSystem */
39    protected Filesystem $filesystem;
40    protected ProjectComposerPackage $projectComposerPackage;
41
42    protected StraussConfig $config;
43
44    /**
45     * Set name and description, call parent class to add dry-run, verbosity options.
46     *
47     * @used-by \Symfony\Component\Console\Command\Command::__construct
48     * @override {@see \Symfony\Component\Console\Command\Command::configure()} empty method.
49     *
50     * @return void
51     */
52    protected function configure()
53    {
54        $this->addOption(
55            'dry-run',
56            null,
57            InputOption::VALUE_OPTIONAL,
58            'Do not actually make any changes',
59            false
60        );
61
62        $this->addOption(
63            'info',
64            null,
65            InputOption::VALUE_OPTIONAL,
66            'output level',
67            false
68        );
69
70        $this->addOption(
71            'debug',
72            null,
73            InputOption::VALUE_OPTIONAL,
74            'output level',
75            false
76        );
77
78        /** @var string $installedSymfonyVersion */
79        $installedSymfonyVersion = InstalledVersions::getVersion('symfony/console');
80
81        if (version_compare($installedSymfonyVersion, '7.2', '<')) {
82            $this->addOption(
83                'silent',
84                's',
85                InputOption::VALUE_OPTIONAL,
86                'output level',
87                false
88            );
89        }
90    }
91
92    protected function execute(InputInterface $input, OutputInterface $output): int
93    {
94        if (!isset($this->config)) {
95            $this->config = $this->createConfig($input);
96        }
97
98        if ($this->config->isDryRun()) {
99            $this->filesystem =
100                new FileSystem(
101                    new ReadOnlyFileSystem(
102                        $this->filesystem->getAdapter(),
103                        Filesystem::makePathNormalizer($this->workingDir)
104                    ),
105                    [],
106                    null,
107                    null,
108                    $this->workingDir
109                );
110
111            /** @var FilesystemRegistry $registry */
112            $registry = \Elazar\Flystream\ServiceLocator::get(\Elazar\Flystream\FilesystemRegistry::class);
113
114            // Register a file stream mem:// to handle file operations by third party libraries.
115            // This exception handling probably doesn't matter in real life but does in unit tests.
116            try {
117                $registry->get('mem');
118            } catch (\Exception $e) {
119                $registry->register('mem', $this->filesystem);
120            }
121        }
122
123        $logger = new Logger('logger');
124        $logger->pushProcessor(new PsrLogMessageProcessor());
125        $logger->pushProcessor(new RelativeFilepathLogProcessor($this->filesystem));
126        $logger->pushProcessor(new PadColonColumnsLogProcessor());
127        if (isset($this->logger) && !($this->logger instanceof ConsoleLogger)) {
128            $logger->pushHandler(new PsrHandler($this->logger));
129        }
130        $logger->pushHandler(new PsrHandler($this->getConsoleLogger($input, $output)));
131        $this->setLogger($logger);
132
133        return Command::SUCCESS;
134    }
135
136    /**
137     * Symfony hook that runs before execute(). Sets working directory, filesystem and logger.
138     */
139    protected function initialize(InputInterface $input, OutputInterface $output): void
140    {
141        $this->workingDir = getcwd() . '';
142
143        if (!isset($this->filesystem)) {
144            $localFilesystemAdapter = new LocalFilesystemAdapter(
145                FileSystem::getFsRoot($this->workingDir),
146                null,
147                LOCK_EX,
148                LocalFilesystemAdapter::SKIP_LINKS
149            );
150
151            $this->filesystem = new FileSystem(
152                $localFilesystemAdapter,
153                [
154                        Config::OPTION_DIRECTORY_VISIBILITY => 'public',
155                    ],
156                Filesystem::makePathNormalizer($this->workingDir),
157                null,
158                $this->workingDir
159            );
160        }
161
162        if (method_exists($this, 'setLogger') && !isset($this->logger)) {
163            $this->setLogger($this->getConsoleLogger($input, $output));
164        }
165    }
166
167    /**
168     * Build a logger honoring optional --info/--debug/--silent flags if present.
169     */
170    protected function getConsoleLogger(InputInterface $input, OutputInterface $output): LoggerInterface
171    {
172        // If a subclass has a config and it is a dry-run, increase verbosity
173        $isDryRun = property_exists($this, 'config') && isset($this->config) && method_exists($this->config, 'isDryRun') && $this->config->isDryRun();
174
175        // Who would want to dry-run without output?
176        if (!$isDryRun && $input->hasOption('silent') && $input->getOption('silent') !== false) {
177            return new NullLogger();
178        }
179
180        $logLevel = [LogLevel::NOTICE => OutputInterface::VERBOSITY_NORMAL];
181
182        if ($input->hasOption('info') && $input->getOption('info') !== false) {
183            $logLevel[LogLevel::INFO] = OutputInterface::VERBOSITY_NORMAL;
184        }
185
186        if ($isDryRun || ($input->hasOption('debug') && $input->getOption('debug') !== false)) {
187            $logLevel[LogLevel::INFO] = OutputInterface::VERBOSITY_NORMAL;
188            $logLevel[LogLevel::DEBUG] = OutputInterface::VERBOSITY_NORMAL;
189        }
190
191        return new ConsoleLogger($output, $logLevel);
192    }
193
194
195    protected function createConfig(InputInterface $input): StraussConfig
196    {
197        return new StraussConfig();
198    }
199}