Completed
Pull Request — master (#36)
by Jitendra
03:16
created

OutputHelper::stringifyArgs()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the PHP-CLI package.
5
 *
6
 * (c) Jitendra Adhikari <[email protected]>
7
 *     <https://github.com/adhocore>
8
 *
9
 * Licensed under MIT license.
10
 */
11
12
namespace Ahc\Cli\Helper;
13
14
use Ahc\Cli\Exception;
15
use Ahc\Cli\Input\Argument;
16
use Ahc\Cli\Input\Command;
17
use Ahc\Cli\Input\Option;
18
use Ahc\Cli\Input\Parameter;
19
use Ahc\Cli\Output\Writer;
20
21
/**
22
 * This helper helps you by showing you help information :).
23
 *
24
 * @author  Jitendra Adhikari <[email protected]>
25
 * @license MIT
26
 *
27
 * @link    https://github.com/adhocore/cli
28
 */
29
class OutputHelper
30
{
31
    /** @var Writer */
32
    protected $writer;
33
34
    /** @var int Max width of command name */
35
    protected $maxCmdName;
36
37
    public function __construct(Writer $writer = null)
38
    {
39
        $this->writer = $writer ?? new Writer;
40
    }
41
42
    /**
43
     * Print stack trace and error msg of an exception.
44
     *
45
     * @param \Throwable $e
46
     *
47
     * @return void
48
     */
49
    public function printTrace(\Throwable $e)
50
    {
51
        $eClass = \get_class($e);
52
53
        $this->writer->colors(
54
            "{$eClass} <red>{$e->getMessage()}</end><eol/>" .
55
            "(thrown in <yellow>{$e->getFile()}</end><white>:{$e->getLine()})</end>"
56
        );
57
58
        // Internal exception traces are not printed.
59
        if ($e instanceof Exception) {
60
            return;
61
        }
62
63
        $traceStr = '<eol/><eol/><bold>Stack Trace:</end><eol/><eol/>';
64
65
        foreach ($e->getTrace() as $i => $trace) {
66
            $trace += ['class' => '', 'type' => '', 'function' => '', 'file' => '', 'line' => '', 'args' => []];
67
            $symbol = $trace['class'] . $trace['type'] . $trace['function'];
68
            $args   = $this->stringifyArgs($trace['args']);
69
70
            $traceStr .= "  <comment>$i)</end> <red>$symbol</end><comment>($args)</end>";
71
            if ('' !== $trace['file']) {
72
                $file      = \realpath($trace['file']);
73
                $traceStr .= "<eol/>     <yellow>at $file</end><white>:{$trace['line']}</end><eol/>";
74
            }
75
        }
76
77
        $this->writer->colors($traceStr);
78
    }
79
80
    protected function stringifyArgs(array $args)
81
    {
82
        $holder = [];
83
84
        foreach ($args as $arg) {
85
            $holder[] = $this->stringifyArg($arg);
86
        }
87
88
        return \implode(', ', $holder);
89
    }
90
91
    protected function stringifyArg($arg)
92
    {
93
        if (\is_scalar($arg)) {
94
            return \var_export($arg, true);
95
        }
96
97
        if (\is_object($arg)) {
98
            return \method_exists($arg, '__toString') ? (string) $arg : \get_class($arg);
99
        }
100
101
        if (\is_array($arg)) {
102
            return '[' . $this->stringifyArgs($arg) . ']';
103
        }
104
105
        return \gettype($arg);
106
    }
107
108
    /**
109
     * @param Argument[] $arguments
110
     * @param string     $header
111
     * @param string     $footer
112
     *
113
     * @return self
114
     */
115
    public function showArgumentsHelp(array $arguments, string $header = '', string $footer = ''): self
116
    {
117
        $this->showHelp('Arguments', $arguments, $header, $footer);
118
119
        return $this;
120
    }
121
122
    /**
123
     * @param Option[] $options
124
     * @param string   $header
125
     * @param string   $footer
126
     *
127
     * @return self
128
     */
129
    public function showOptionsHelp(array $options, string $header = '', string $footer = ''): self
130
    {
131
        $this->showHelp('Options', $options, $header, $footer);
132
133
        return $this;
134
    }
135
136
    /**
137
     * @param Command[] $commands
138
     * @param string    $header
139
     * @param string    $footer
140
     *
141
     * @return self
142
     */
143
    public function showCommandsHelp(array $commands, string $header = '', string $footer = ''): self
144
    {
145
        $this->maxCmdName = $commands ? \max(\array_map(function (Command $cmd) {
146
            return \strlen($cmd->name());
147
        }, $commands)) : 0;
148
149
        $this->showHelp('Commands', $commands, $header, $footer);
150
151
        return $this;
152
    }
153
154
    /**
155
     * Show help with headers and footers.
156
     *
157
     * @param string $for
158
     * @param array  $items
159
     * @param string $header
160
     * @param string $footer
161
     *
162
     * @return void
163
     */
164
    protected function showHelp(string $for, array $items, string $header = '', string $footer = '')
165
    {
166
        if ($header) {
167
            $this->writer->bold($header, true);
168
        }
169
170
        $this->writer->eol()->boldGreen($for . ':', true);
171
172
        if (empty($items)) {
173
            $this->writer->bold('  (n/a)', true);
174
175
            return;
176
        }
177
178
        $space = 4;
179
        foreach ($this->sortItems($items, $padLen) as $item) {
180
            $name = $this->getName($item);
181
            $this->writer->bold('  ' . \str_pad($name, $padLen + $space));
182
            $this->writer->comment($item->desc(), true);
183
        }
184
185
        if ($footer) {
186
            $this->writer->eol()->yellow($footer, true);
187
        }
188
    }
189
190
    /**
191
     * Sort items by name. As a side effect sets max length of all names.
192
     *
193
     * @param Parameter[]|Command[] $items
194
     * @param int                   $max
195
     *
196
     * @return array
197
     */
198
    protected function sortItems(array $items, &$max = 0): array
199
    {
200
        $max = \max(\array_map(function ($item) {
201
            return \strlen($this->getName($item));
202
        }, $items));
203
204
        \uasort($items, function ($a, $b) {
205
            /* @var Parameter $b */
206
            /* @var Parameter $a */
207
            return $a->name() <=> $b->name();
208
        });
209
210
        return $items;
211
    }
212
213
    /**
214
     * Prepare name for different items.
215
     *
216
     * @param Parameter|Command $item
217
     *
218
     * @return string
219
     */
220
    protected function getName($item): string
221
    {
222
        $name = $item->name();
223
224
        if ($item instanceof Command) {
225
            return \trim(\str_pad($name, $this->maxCmdName) . ' ' . $item->alias());
226
        }
227
228
        return $this->label($item);
229
    }
230
231
    /**
232
     * Get parameter label for humans.
233
     *
234
     * @param Parameter $item
235
     *
236
     * @return string
237
     */
238
    protected function label(Parameter $item)
239
    {
240
        $name = $item->name();
241
242
        if ($item instanceof Option) {
243
            $name = $item->short() . '|' . $item->long();
244
        }
245
246
        $variad = $item->variadic() ? '...' : '';
247
248
        if ($item->required()) {
249
            return "<$name$variad>";
250
        }
251
252
        return "[$name$variad]";
253
    }
254
}
255