Passed
Push — 0.8.x ( 54626d...382f48 )
by Alexander
06:05 queued 02:55
created

TextDescriptor   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 319
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 139
dl 0
loc 319
rs 5.04
c 1
b 0
f 0
wmc 57

8 Methods

Rating   Name   Duplication   Size   Complexity  
A getColumnWidth() 0 17 5
B describeOption() 0 34 10
A writeText() 0 5 4
B describeDefinition() 0 40 10
B describeCommand() 0 38 7
C describeApplication() 0 68 15
A getCommandAliases() 0 10 2
A describeArgument() 0 17 4

How to fix   Complexity   

Complex Class

Complex classes like TextDescriptor often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TextDescriptor, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2023 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Components\Console\Description;
24
25
use Syscodes\Components\Console\Application;
26
use Syscodes\Components\Console\Helper\Helper;
27
use Syscodes\Components\Console\Command\Command;
28
use Syscodes\Components\Console\Input\InputOption;
29
use Syscodes\Components\Console\Input\InputArgument;
30
use Syscodes\Components\Console\Input\InputDefinition;
31
32
/**
33
 * Text descriptor.
34
 */
35
class TextDescriptor extends Descriptor
36
{
37
    /**
38
     * The output interface implementation.
39
     * 
40
     * @var \Syscodes\Components\Contracts\Console\Output $output
41
     */
42
    protected $output;
43
44
    /**
45
     * Describes an InputArgument instance.
46
     * 
47
     * @param  \Syscodes\Components\Console\Input\InputArgument  $argument  The argument implemented
48
     * @param  array  $options  The options of the console
49
     * 
50
     * @return void
51
     */
52
    protected function describeArgument(InputArgument $argument, array $options = [])
53
    {
54
        if (null !== $argument->getDefault() && ( ! is_array($argument->getDefault()) || count($argument->getDefault()))) {
55
            $default = sprintf(' [<note>default: %s</>] ', $argument->getDefault());
56
        } else {
57
            $default = '';
58
        }
59
60
        $totalWidth = $options['total_width'] ?? Helper::width($argument->getName());
61
        $spacingWidth = $totalWidth - strlen($argument->getName());
62
63
        $this->writeText(sprintf('  <info>%s</>  %s%s%s',
64
            $argument->getName(),
65
            str_repeat(' ', $spacingWidth),
66
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
67
            $default
68
        ), $options);
69
    }
70
71
     /**
72
     * Describes an InputOption instance.
73
     * 
74
     * @param  \Syscodes\Components\Console\Input\InputOption  $option  The option implemented
75
     * @param  array  $options  The options of the console
76
     * 
77
     * @return void
78
     */
79
    protected function describeOption(InputOption $option, array $options = [])
80
    {
81
        if ($option->isAcceptValue() && null !== $option->getDefault() && ( ! is_array($option->getDefault()) || count($option->getDefault()))) {
82
            $default = sprintf(' [<note>default: %s</>] ', $option->getDefault());
83
        } else {
84
            $default = '';
85
        }
86
87
        $value = '';
88
89
        if ($option->isAcceptValue()) {
90
            $value = '='.strtoupper($option->getName());
91
92
            if ($option->isValueOptional()) {
93
                $value = '['.$value.']';
94
            }
95
        }
96
        
97
        $totalWidth = $options['total_width'] ?? 20;
98
99
        $synopsis = sprintf('%s%s',
100
            $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : '    ',
101
            sprintf($option->isNegatable() ? '--%1$s|--no-%1$s' : '--%1$s%2$s', $option->getName(), $value)
102
        );
103
104
        $spacingWidth = $totalWidth - Helper::width($synopsis);
105
106
        $this->writeText(sprintf('  <fg=green>%s</>  %s%s%s%s',
107
            $synopsis,
108
            str_repeat(' ', $spacingWidth),
109
            preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $spacingWidth + 4), $option->getDescription()),
110
            $default,
111
            $option->isArray() ? '<info> (multiple values allowed)</>' : ''
112
        ), $options);
113
    }
114
115
    /**
116
     * Describes an InputDefinition instance.
117
     * 
118
     * @param  \Syscodes\Components\Console\Input\InputDefinition  $definition  The definition implemented
119
     * @param  array  $options  The options of the console
120
     * 
121
     * @return void
122
     */
123
    protected function describeDefinition(InputDefinition $definition, array $options = [])
124
    {
125
        $totalWidth = 20;
126
        
127
        foreach ($definition->getArguments() as $argument) {
128
            $totalWidth = max($totalWidth, Helper::width($argument->getName()));
129
        }
130
131
        if ($definition->getArguments()) {
132
            $this->writeText('<comment>Arguments:</>', $options);
133
            $this->writeText("\n");
134
135
            foreach ($definition->getArguments() as $argument) {
136
                $this->describeArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
137
                $this->writeText("\n");
138
            }
139
        }
140
141
        if ($definition->getArguments() && $definition->getOptions()) {
142
            $this->writeText("\n");
143
        }
144
145
        if ($definition->getOptions()) {
146
            $laterOptions = [];
147
148
            $this->writeText('<comment>Options:</>', $options);
149
150
            foreach ($definition->getOptions() as $option) {
151
                if (\strlen($option->getShortcut() ?? '') > 1) {
152
                    $laterOptions[] = $option;
153
                    continue;
154
                }
155
156
                $this->writeText("\n");
157
                $this->describeOption($option, array_merge($options, ['total_width' => $totalWidth]));
158
            }
159
160
            foreach ($laterOptions as $option) {
161
                $this->writeText("\n");
162
                $this->describeOption($option, array_merge($options, ['total_width' => $totalWidth]));
163
            }
164
        }
165
    }
166
167
    /**
168
     * Describes an Command instance.
169
     * 
170
     * @param  \Syscodes\Components\Console\Command\Command  $command  The command implemented
171
     * @param  array  $options  The options of the console
172
     * 
173
     * @return void
174
     */
175
    protected function describeCommand(Command $command, array $options = [])
176
    {
177
        $command->mergeApplicationDefinition(false);
178
179
        $this->writeText($command->getApplication()->getConsoleVersion());
180
        $this->writeText("\n\n");
181
182
        if ($description = $command->getDescription()) {
183
            $this->writeText('<comment>Description:</>', $options);
184
            $this->writeText("\n");
185
            $this->writeText('  '.$description);
186
            $this->writeText("\n\n");
187
        }
188
        
189
        $this->writeText('<comment>Usage:</>', $options);
190
        
191
        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
192
            $this->writeText("\n");
193
            $this->writeText('  '.$usage, $options);
194
        }
195
        
196
        $this->writeText("\n\n");
197
198
        $definition = $command->getDefinition();
199
        
200
        if ($definition->getOptions() || $definition->getArguments()) {
201
            $this->describeDefinition($definition, $options);
202
            $this->writeText("\n");
203
        }
204
205
        $help = $command->getProccesedHelp();
206
207
        if ($help && $help !== $description) {
208
            $this->writeText("\n");
209
            $this->writeText('<comment>Help:</>', $options);
210
            $this->writeText("\n");
211
            $this->writeText('  '.str_replace("\n", "\n  ", $help), $options);
212
            $this->writeText("\n");
213
        }
214
    }
215
216
    /**
217
     * Describes an Application instance.
218
     * 
219
     * @param  \Syscodes\Components\Console\Application  $application  The application implemented
220
     * @param  array  $options  The options of the console
221
     * 
222
     * @return void
223
     */
224
    protected function describeApplication(Application $application, array $options = [])
225
    {
226
        $describedNamespace = $options['namespace'] ?? null;
227
        $description = new ApplicationDescription($application, $describedNamespace);
228
        
229
        if (isset($options['raw_text']) && $options['raw_text']) {
230
            $width = ($description->getCommands());
231
            
232
            foreach ($description->getCommands() as $command) {
233
                $this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
234
                $this->writeText("\n");
235
            }
236
        } else {
237
            if ('' != $help = $application->getHelp()) {
238
                $this->writeText("$help\n\n", $options);
239
            }
240
            
241
            $this->writeText("<comment>Usage:</>\n", $options);
242
            $this->writeText("  command [options] [arguments]\n\n", $options);
243
244
            $this->describeDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
245
246
            $this->writeText("\n");
247
            $this->writeText("\n");
248
            
249
            $commands = $description->getCommands();
250
            $namespaces = $description->getNamespaces();
251
            
252
            if ($describedNamespace && $namespaces) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $namespaces of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
253
                // make sure all alias commands are included when describing a specific namespace
254
                $describedNamespaceInfo = reset($namespaces);
255
                
256
                foreach ($describedNamespaceInfo['commands'] as $name) {
257
                    $commands[$name] = $description->getCommand($name);
258
                }
259
            }
260
            
261
            // calculate max. width based on available commands per namespace
262
            $width = $this->getColumnWidth(array_merge(...array_values(array_map(fn ($namespace) => array_intersect($namespace['commands'], array_keys($commands)), array_values($namespaces)))));
263
            
264
            if ($describedNamespace) {
265
                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
266
            } else {
267
                $this->writeText('<comment>Available commands:</comment>', $options);
268
            }
269
            
270
            foreach ($namespaces as $namespace) {
271
                $namespace['commands'] = array_filter($namespace['commands'], fn ($name) => isset($commands[$name]));
272
                
273
                if ( ! $namespace['commands']) {
274
                    continue;
275
                }
276
                
277
                if ( ! $describedNamespace && ApplicationDescription::G_NAMESPACE !== $namespace['id']) {
278
                    $this->writeText("\n");
279
                    $this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
280
                }
281
                
282
                foreach ($namespace['commands'] as $name) {
283
                    $this->writeText("\n");
284
                    $spacingWidth = $width - Helper::width($name);
285
                    $command = $commands[$name];
286
                    $commandAliases = $name === $command->getName() ? $this->getCommandAliases($command) : '';
287
                    $this->writeText(sprintf('  <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
288
                }
289
            }
290
            
291
            $this->writeText("\n");
292
        }
293
    }
294
295
    /**
296
     * Writes a message to the output.
297
     * 
298
     * @param  string  $content  The message to output
299
     * @param  array  $options  The option of bitmask
300
     * 
301
     * @return string
302
     */
303
    private function writeText(string $content, array $options = [])
304
    {
305
        $this->write(
306
            isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
307
            isset($options['raw_output']) ? ! $options['raw_output'] : true
308
        );
309
    }
310
    
311
    /**
312
     * Formats command aliases to show them in the command description.
313
     * 
314
     * @param  \Syscodes\Components\Console\Command\Command  $command
315
     * 
316
     * @return string
317
     */
318
    private function getCommandAliases(Command $command): string
319
    {
320
        $text = '';
321
        $aliases = $command->getAliases();
322
        
323
        if ($aliases) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $aliases of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
324
            $text = '['.implode('|', $aliases).'] ';
325
        }
326
        
327
        return $text;
328
    }
329
330
    /**
331
     * Get the column width.
332
     * 
333
     * @param  array  $commands
334
     * 
335
     * @return int
336
     */
337
    private function getColumnWidth(array $commands): int
338
    {
339
        $widths = [];
340
        
341
        foreach ($commands as $command) {
342
            if ($command instanceof Command) {
343
                $widths[] = Helper::width($command->getName());
344
                
345
                foreach ($command->getAliases() as $alias) {
346
                    $widths[] = Helper::width($alias);
347
                }
348
            } else {
349
                $widths[] = Helper::width($command);
350
            }
351
        }
352
        
353
        return $widths ? max($widths) + 2 : 0;
354
    }
355
}