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

Info::renderContents()   C

Complexity

Conditions 12
Paths 32

Size

Total Lines 65
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 37
dl 0
loc 65
rs 6.9666
c 0
b 0
f 0
cc 12
nc 32
nop 8

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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