Completed
Branch 6.0 (d30585)
by yun
04:17
created

Descriptor   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 302
Duplicated Lines 5.3 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
dl 16
loc 302
rs 3.6
c 0
b 0
f 0
ccs 0
cts 149
cp 0
wmc 60
lcom 1
cbo 7

11 Methods

Rating   Name   Duplication   Size   Complexity  
B describe() 0 24 6
A write() 0 4 2
A describeInputArgument() 8 17 4
B describeInputOption() 8 28 9
B describeInputDefinition() 0 38 10
A describeCommand() 0 28 5
C describeConsole() 0 51 11
A writeText() 0 5 4
A formatDefaultValue() 0 4 1
A getColumnWidth() 0 13 4
A calculateTotalWidthForOptions() 0 17 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Descriptor 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 Descriptor, and based on these observations, apply Extract Interface, too.

1
<?php
2
// +----------------------------------------------------------------------
3
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
// +----------------------------------------------------------------------
5
// | Copyright (c) 2006~2015 http://thinkphp.cn All rights reserved.
6
// +----------------------------------------------------------------------
7
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
// +----------------------------------------------------------------------
9
// | Author: yunwuxin <[email protected]>
10
// +----------------------------------------------------------------------
11
12
namespace think\console\output;
13
14
use think\Console;
15
use think\console\Command;
16
use think\console\input\Argument as InputArgument;
17
use think\console\input\Definition as InputDefinition;
18
use think\console\input\Option as InputOption;
19
use think\console\Output;
20
use think\console\output\descriptor\Console as ConsoleDescription;
21
22
class Descriptor
23
{
24
25
    /**
26
     * @var Output
27
     */
28
    protected $output;
29
30
    /**
31
     * {@inheritdoc}
32
     */
33
    public function describe(Output $output, $object, array $options = [])
34
    {
35
        $this->output = $output;
36
37
        switch (true) {
38
            case $object instanceof InputArgument:
39
                $this->describeInputArgument($object, $options);
40
                break;
41
            case $object instanceof InputOption:
42
                $this->describeInputOption($object, $options);
43
                break;
44
            case $object instanceof InputDefinition:
45
                $this->describeInputDefinition($object, $options);
46
                break;
47
            case $object instanceof Command:
48
                $this->describeCommand($object, $options);
49
                break;
50
            case $object instanceof Console:
51
                $this->describeConsole($object, $options);
52
                break;
53
            default:
54
                throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object)));
55
        }
56
    }
57
58
    /**
59
     * 输出内容
60
     * @param string $content
61
     * @param bool   $decorated
62
     */
63
    protected function write($content, $decorated = false)
64
    {
65
        $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW);
66
    }
67
68
    /**
69
     * 描述参数
70
     * @param InputArgument $argument
71
     * @param array         $options
72
     * @return string|mixed
73
     */
74
    protected function describeInputArgument(InputArgument $argument, array $options = [])
75
    {
76 View Code Duplication
        if (null !== $argument->getDefault()
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
77
            && (!is_array($argument->getDefault())
78
                || count($argument->getDefault()))
79
        ) {
80
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
81
        } else {
82
            $default = '';
83
        }
84
85
        $totalWidth   = $options['total_width'] ?? strlen($argument->getName());
86
        $spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
87
88
        $this->writeText(sprintf("  <info>%s</info>%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
89
            preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options);
90
    }
91
92
    /**
93
     * 描述选项
94
     * @param InputOption $option
95
     * @param array       $options
96
     * @return string|mixed
97
     */
98
    protected function describeInputOption(InputOption $option, array $options = [])
99
    {
100 View Code Duplication
        if ($option->acceptValue() && null !== $option->getDefault()
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101
            && (!is_array($option->getDefault())
102
                || count($option->getDefault()))
103
        ) {
104
            $default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
105
        } else {
106
            $default = '';
107
        }
108
109
        $value = '';
110
        if ($option->acceptValue()) {
111
            $value = '=' . strtoupper($option->getName());
112
113
            if ($option->isValueOptional()) {
114
                $value = '[' . $value . ']';
115
            }
116
        }
117
118
        $totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
119
        $synopsis   = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : '    ', sprintf('--%s%s', $option->getName(), $value));
120
121
        $spacingWidth = $totalWidth - strlen($synopsis) + 2;
122
123
        $this->writeText(sprintf("  <info>%s</info>%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + <info> + </info> + 2 spaces
124
            preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''), $options);
125
    }
126
127
    /**
128
     * 描述输入
129
     * @param InputDefinition $definition
130
     * @param array           $options
131
     * @return string|mixed
132
     */
133
    protected function describeInputDefinition(InputDefinition $definition, array $options = [])
134
    {
135
        $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
136
        foreach ($definition->getArguments() as $argument) {
137
            $totalWidth = max($totalWidth, strlen($argument->getName()));
138
        }
139
140
        if ($definition->getArguments()) {
141
            $this->writeText('<comment>Arguments:</comment>', $options);
142
            $this->writeText("\n");
143
            foreach ($definition->getArguments() as $argument) {
144
                $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
145
                $this->writeText("\n");
146
            }
147
        }
148
149
        if ($definition->getArguments() && $definition->getOptions()) {
150
            $this->writeText("\n");
151
        }
152
153
        if ($definition->getOptions()) {
154
            $laterOptions = [];
155
156
            $this->writeText('<comment>Options:</comment>', $options);
157
            foreach ($definition->getOptions() as $option) {
158
                if (strlen($option->getShortcut()) > 1) {
159
                    $laterOptions[] = $option;
160
                    continue;
161
                }
162
                $this->writeText("\n");
163
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
164
            }
165
            foreach ($laterOptions as $option) {
166
                $this->writeText("\n");
167
                $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
168
            }
169
        }
170
    }
171
172
    /**
173
     * 描述指令
174
     * @param Command $command
175
     * @param array   $options
176
     * @return string|mixed
177
     */
178
    protected function describeCommand(Command $command, array $options = [])
179
    {
180
        $command->getSynopsis(true);
181
        $command->getSynopsis(false);
182
        $command->mergeConsoleDefinition(false);
183
184
        $this->writeText('<comment>Usage:</comment>', $options);
185
        foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
186
            $this->writeText("\n");
187
            $this->writeText('  ' . $usage, $options);
188
        }
189
        $this->writeText("\n");
190
191
        $definition = $command->getNativeDefinition();
192
        if ($definition->getOptions() || $definition->getArguments()) {
193
            $this->writeText("\n");
194
            $this->describeInputDefinition($definition, $options);
195
            $this->writeText("\n");
196
        }
197
198
        if ($help = $command->getProcessedHelp()) {
199
            $this->writeText("\n");
200
            $this->writeText('<comment>Help:</comment>', $options);
201
            $this->writeText("\n");
202
            $this->writeText(' ' . str_replace("\n", "\n ", $help), $options);
203
            $this->writeText("\n");
204
        }
205
    }
206
207
    /**
208
     * 描述控制台
209
     * @param Console $console
210
     * @param array   $options
211
     * @return string|mixed
212
     */
213
    protected function describeConsole(Console $console, array $options = [])
214
    {
215
        $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null;
216
        $description        = new ConsoleDescription($console, $describedNamespace);
217
218
        if (isset($options['raw_text']) && $options['raw_text']) {
219
            $width = $this->getColumnWidth($description->getNamespaces());
220
221
            foreach ($description->getCommands() as $command) {
222
                $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options);
223
                $this->writeText("\n");
224
            }
225
        } else {
226
            if ('' != $help = $console->getHelp()) {
227
                $this->writeText("$help\n\n", $options);
228
            }
229
230
            $this->writeText("<comment>Usage:</comment>\n", $options);
231
            $this->writeText("  command [options] [arguments]\n\n", $options);
232
233
            $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options);
234
235
            $this->writeText("\n");
236
            $this->writeText("\n");
237
238
            $width = $this->getColumnWidth($description->getNamespaces());
239
240
            if ($describedNamespace) {
241
                $this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
242
            } else {
243
                $this->writeText('<comment>Available commands:</comment>', $options);
244
            }
245
246
            // add commands by namespace
247
            foreach ($description->getNamespaces() as $namespace) {
248
                if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
249
                    $this->writeText("\n");
250
                    $this->writeText(' <comment>' . $namespace['id'] . '</comment>', $options);
251
                }
252
253
                foreach ($namespace['commands'] as $name) {
254
                    $this->writeText("\n");
255
                    $spacingWidth = $width - strlen($name);
256
                    $this->writeText(sprintf("  <info>%s</info>%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)
257
                            ->getDescription()), $options);
258
                }
259
            }
260
261
            $this->writeText("\n");
262
        }
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268
    private function writeText($content, array $options = [])
269
    {
270
        $this->write(isset($options['raw_text'])
271
            && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true);
272
    }
273
274
    /**
275
     * 格式化
276
     * @param mixed $default
277
     * @return string
278
     */
279
    private function formatDefaultValue($default)
280
    {
281
        return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
282
    }
283
284
    /**
285
     * @param Namespaces[] $namespaces
286
     * @return int
287
     */
288
    private function getColumnWidth(array $namespaces)
289
    {
290
        $width = 0;
291
        foreach ($namespaces as $namespace) {
292
            foreach ($namespace['commands'] as $name) {
293
                if (strlen($name) > $width) {
294
                    $width = strlen($name);
295
                }
296
            }
297
        }
298
299
        return $width + 2;
300
    }
301
302
    /**
303
     * @param InputOption[] $options
304
     * @return int
305
     */
306
    private function calculateTotalWidthForOptions($options)
307
    {
308
        $totalWidth = 0;
309
        foreach ($options as $option) {
310
            $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + --
311
312
            if ($option->acceptValue()) {
313
                $valueLength = 1 + strlen($option->getName()); // = + value
314
                $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
315
316
                $nameLength += $valueLength;
317
            }
318
            $totalWidth = max($totalWidth, $nameLength);
319
        }
320
321
        return $totalWidth;
322
    }
323
}
324