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() |
|
|
|
|
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() |
|
|
|
|
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
|
|
|
|
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.