Passed
Push — master ( e80b48...a06265 )
by Théo
02:43
created

Info::showPharGlobalInfo()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 80
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 52
dl 0
loc 80
rs 8.1138
c 0
b 0
f 0
cc 7
nc 8
nop 3

1 Method

Rating   Name   Duplication   Size   Complexity  
A Info::render() 0 22 5

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box\Console\Command;
16
17
use function array_filter;
18
use function array_flip;
19
use Assert\Assertion;
20
use DirectoryIterator;
21
use function is_array;
22
use KevinGH\Box\Console\PharInfoRenderer;
23
use function KevinGH\Box\FileSystem\remove;
24
use function KevinGH\Box\format_size;
25
use function KevinGH\Box\get_phar_compression_algorithms;
26
use KevinGH\Box\PharInfo\PharInfo;
27
use Phar;
28
use PharData;
29
use PharFileInfo;
30
use function realpath;
31
use function sprintf;
32
use function str_repeat;
33
use function str_replace;
34
use Symfony\Component\Console\Input\InputArgument;
35
use Symfony\Component\Console\Input\InputInterface;
36
use Symfony\Component\Console\Input\InputOption;
37
use Symfony\Component\Console\Output\OutputInterface;
38
use Symfony\Component\Console\Style\SymfonyStyle;
39
use Throwable;
40
41
/**
42
 * @private
43
 */
44
final class Info extends Command
45
{
46
    use CreateTemporaryPharFile;
47
48
    private const PHAR_ARG = 'phar';
49
    private const LIST_OPT = 'list';
50
    private const METADATA_OPT = 'metadata';
51
    private const MODE_OPT = 'mode';
52
    private const DEPTH_OPT = 'depth';
53
54
    private static $FILE_ALGORITHMS;
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function __construct(?string $name = null)
60
    {
61
        parent::__construct($name);
62
63
        if (null === self::$FILE_ALGORITHMS) {
64
            self::$FILE_ALGORITHMS = array_flip(array_filter(get_phar_compression_algorithms()));
65
        }
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    protected function configure(): void
72
    {
73
        $this->setName('info');
74
        $this->setDescription(
75
            '🔍  Displays information about the PHAR extension or file'
76
        );
77
        $this->setHelp(
78
            <<<'HELP'
79
The <info>%command.name%</info> command will display information about the Phar extension,
80
or the Phar file if specified.
81
82
If the <info>phar</info> argument <comment>(the PHAR file path)</comment> is provided, information
83
about the PHAR file itself will be displayed.
84
85
If the <info>--list|-l</info> option is used, the contents of the PHAR file will
86
be listed. By default, the list is shown as an indented tree. You may
87
instead choose to view a flat listing, by setting the <info>--mode|-m</info> option
88
to <comment>flat</comment>.
89
HELP
90
        );
91
        $this->addArgument(
92
            self::PHAR_ARG,
93
            InputArgument::OPTIONAL,
94
            'The Phar file.'
95
        );
96
        $this->addOption(
97
            self::LIST_OPT,
98
            'l',
99
            InputOption::VALUE_NONE,
100
            'List the contents of the Phar?'
101
        );
102
        $this->addOption(
103
            self::METADATA_OPT,
104
            null,
105
            InputOption::VALUE_NONE,
106
            'Display metadata?'
107
        );
108
        $this->addOption(
109
            self::MODE_OPT,
110
            'm',
111
            InputOption::VALUE_REQUIRED,
112
            'The listing mode. (default: indent, options: indent, flat)',
113
            'indent'
114
        );
115
        $this->addOption(
116
            self::DEPTH_OPT,
117
            'd',
118
            InputOption::VALUE_REQUIRED,
119
            'The depth of the tree displayed',
120
            -1
121
        );
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function execute(InputInterface $input, OutputInterface $output): int
128
    {
129
        $io = new SymfonyStyle($input, $output);
130
        $io->newLine();
131
132
        if (null === ($file = $input->getArgument(self::PHAR_ARG))) {
133
            return $this->showGlobalInfo($output, $io);
134
        }
135
136
        $file = realpath($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type string[]; however, parameter $path of realpath() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

136
        $file = realpath(/** @scrutinizer ignore-type */ $file);
Loading history...
137
138
        if (false === $file) {
139
            $io->error(
140
                sprintf(
141
                    'The file "%s" could not be found.',
142
                    $input->getArgument(self::PHAR_ARG)
0 ignored issues
show
Bug introduced by
It seems like $input->getArgument(self::PHAR_ARG) can also be of type string[]; however, parameter $args of sprintf() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

142
                    /** @scrutinizer ignore-type */ $input->getArgument(self::PHAR_ARG)
Loading history...
143
                )
144
            );
145
146
            return 1;
147
        }
148
149
        $tmpFile = $this->createTemporaryPhar($file);
150
151
        try {
152
            return $this->showInfo($tmpFile, $file, $input, $output, $io);
153
        } finally {
154
            if ($file !== $tmpFile) {
155
                remove($tmpFile);
156
            }
157
        }
158
    }
159
160
    public function showInfo(string $file, string $originalFile, InputInterface $input, OutputInterface $output, SymfonyStyle $io): int
161
    {
162
        $depth = (int) $input->getOption(self::DEPTH_OPT);
163
164
        Assertion::greaterOrEqualThan($depth, -1, 'Expected the depth to be a positive integer or -1, got "%d"');
165
166
        try {
167
            $pharInfo = new PharInfo($file);
168
169
            return $this->showPharInfo(
170
                $pharInfo,
171
                $input->getOption(self::LIST_OPT),
172
                $depth,
173
                'indent' === $input->getOption(self::MODE_OPT),
174
                $output,
175
                $io
176
            );
177
        } catch (Throwable $throwable) {
178
            if ($output->isDebug()) {
179
                throw $throwable;
180
            }
181
182
            $io->error(
183
                sprintf(
184
                    'Could not read the file "%s".',
185
                    $originalFile
186
                )
187
            );
188
189
            return 1;
190
        }
191
    }
192
193
    private function showGlobalInfo(OutputInterface $output, SymfonyStyle $io): int
194
    {
195
        $this->render(
196
            $output,
197
            [
198
                'API Version' => Phar::apiVersion(),
199
                'Supported Compression' => Phar::getSupportedCompression(),
200
                'Supported Signatures' => Phar::getSupportedSignatures(),
201
            ]
202
        );
203
204
        $io->newLine();
205
        $io->comment('Get a PHAR details by giving its path as an argument.');
206
207
        return 0;
208
    }
209
210
    private function showPharInfo(
211
        PharInfo $pharInfo,
212
        bool $content,
213
        int $depth,
214
        bool $indent,
215
        OutputInterface $output,
216
        SymfonyStyle $io
217
    ): int {
218
        $this->showPharMeta($pharInfo, $io);
219
220
        if ($content) {
221
            $this->renderContents(
222
                $output,
223
                $pharInfo->getPhar(),
224
                0,
225
                $depth,
226
                $indent ? 0 : false,
227
                $pharInfo->getRoot(),
228
                $pharInfo->getPhar(),
229
                $pharInfo->getRoot()
230
            );
231
        } else {
232
            $io->comment('Use the <info>--list|-l</info> option to list the content of the PHAR.');
233
        }
234
235
        return 0;
236
    }
237
238
    private function showPharMeta(PharInfo $pharInfo, SymfonyStyle $io): void
239
    {
240
        $io->writeln(
241
            sprintf(
242
                '<comment>API Version:</comment> %s',
243
                $pharInfo->getVersion()
244
            )
245
        );
246
247
        $io->newLine();
248
249
        PharInfoRenderer::renderCompression($pharInfo, $io);
250
251
        $io->newLine();
252
253
        PharInfoRenderer::renderSignature($pharInfo, $io);
254
255
        $io->newLine();
256
257
        PharInfoRenderer::renderMetadata($pharInfo, $io);
258
259
        $io->newLine();
260
261
        PharInfoRenderer::renderContentsSummary($pharInfo, $io);
262
    }
263
264
    private function render(OutputInterface $output, array $attributes): void
265
    {
266
        $out = false;
267
268
        foreach ($attributes as $name => $value) {
269
            if ($out) {
270
                $output->writeln('');
271
            }
272
273
            $output->write("<comment>$name:</comment>");
274
275
            if (is_array($value)) {
276
                $output->writeln('');
277
278
                foreach ($value as $v) {
279
                    $output->writeln("  - $v");
280
                }
281
            } else {
282
                $output->writeln(" $value");
283
            }
284
285
            $out = true;
286
        }
287
    }
288
289
    /**
290
     * @param iterable|PharFileInfo[] $list
291
     * @param false|int               $indent Nbr of indent or `false`
292
     * @param Phar|PharData           $phar
293
     */
294
    private function renderContents(
295
        OutputInterface $output,
296
        iterable $list,
297
        int $depth,
298
        int $maxDepth,
299
        $indent,
300
        string $base,
301
        $phar,
302
        string $root
303
    ): void {
304
        if (-1 !== $maxDepth && $depth > $maxDepth) {
305
            return;
306
        }
307
308
        foreach ($list as $item) {
309
            $item = $phar[str_replace($root, '', $item->getPathname())];
310
311
            if (false !== $indent) {
312
                $output->write(str_repeat(' ', $indent));
313
314
                $path = $item->getFilename();
315
316
                if ($item->isDir()) {
317
                    $path .= '/';
318
                }
319
            } else {
320
                $path = str_replace($base, '', $item->getPathname());
321
            }
322
323
            if ($item->isDir()) {
324
                if (false !== $indent) {
325
                    $output->writeln("<info>$path</info>");
326
                }
327
            } else {
328
                $compression = '<fg=red>[NONE]</fg=red>';
329
330
                foreach (self::$FILE_ALGORITHMS as $code => $name) {
331
                    if ($item->isCompressed($code)) {
332
                        $compression = "<fg=cyan>[$name]</fg=cyan>";
333
                        break;
334
                    }
335
                }
336
337
                $fileSize = format_size($item->getCompressedSize());
338
339
                $output->writeln(
340
                    sprintf(
341
                        '%s %s - %s',
342
                        $path,
343
                        $compression,
344
                        $fileSize
345
                    )
346
                );
347
            }
348
349
            if ($item->isDir()) {
350
                $this->renderContents(
351
                    $output,
352
                    new DirectoryIterator($item->getPathname()),
353
                    $depth + 1,
354
                    $maxDepth,
355
                    false === $indent ? $indent : $indent + 2,
356
                    $base,
357
                    $phar,
358
                    $root
359
                );
360
            }
361
        }
362
    }
363
}
364