Passed
Push — master ( b95edc...42d472 )
by Timm
02:04
created

Cmd::help()   B

Complexity

Conditions 10
Paths 6

Size

Total Lines 88
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 52
c 1
b 0
f 0
nc 6
nop 0
dl 0
loc 88
rs 7.1806

How to fix   Long Method    Complexity   

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:

1
<?php
2
3
namespace Stefaminator\Cli;
4
5
use Exception;
6
use GetOptionKit\OptionCollection;
7
use GetOptionKit\OptionResult;
8
use ReflectionFunction;
9
use RuntimeException;
10
11
12
class Cmd {
13
14
    /**
15
     * @var Cmd
16
     */
17
    public $parent;
18
19
    /**
20
     * @var string
21
     */
22
    public $cmd;
23
24
    /**
25
     * @var string
26
     */
27
    public $descr;
28
29
    /**
30
     * @var array
31
     */
32
    public $optionSpecs;
33
34
    /**
35
     * @var OptionCollection
36
     */
37
    private $optionCollection;
38
39
    /**
40
     * @var OptionResult|null
41
     */
42
    public $optionResult;
43
44
    /**
45
     * @var Exception
46
     */
47
    public $optionParseException;
48
49
    /**
50
     * @var string[]
51
     */
52
    public $arguments = [];
53
54
    /**
55
     * @var Cmd[]
56
     */
57
    private $subcommands = [];
58
59
    /**
60
     * @var callable|null
61
     */
62
    private $callable;
63
64
65
    public function __construct(string $cmd) {
66
        $this->cmd = $cmd;
67
        if ($cmd !== 'help') {
68
            $this->addSubCmd(
69
                self::extend('help')
70
                    ->setDescription('Displays help for this command.')
71
                    ->setCallable(static function(Cmd $cmd) {
72
                        $cmd->parent->help();
73
                    })
74
            );
75
        }
76
    }
77
78
    public function addOption(string $specString, array $config): self {
79
80
        $this->optionSpecs[$specString] = $config;
81
82
        return $this;
83
    }
84
85
    public function addSubCmd(Cmd $cmd): self {
86
87
        $cmd->parent = $this;
88
        $this->subcommands[$cmd->cmd] = $cmd;
89
90
        return $this;
91
    }
92
93
    public function setDescription(string $descr): self {
94
95
        $this->descr = $descr;
96
97
        return $this;
98
    }
99
100
    /**
101
     * @param callable $callable
102
     * @return $this
103
     */
104
    public function setCallable(callable $callable): self {
105
106
        try {
107
            $this->callable = $this->validateCallable($callable);
108
109
        } catch (Exception $e) {
110
            echo __METHOD__ . ' has been called with invalid callable: ' . $e->getMessage() . "\n";
111
        }
112
113
114
        return $this;
115
    }
116
117
    public function existsSubCmd(string $cmd): bool {
118
        return array_key_exists($cmd, $this->subcommands);
119
    }
120
121
    public function getSubCmd(string $cmd): ?Cmd {
122
        if ($this->existsSubCmd($cmd)) {
123
            return $this->subcommands[$cmd];
124
        }
125
        return null;
126
    }
127
128
    public function getMethodName(): string {
129
        $cmd = $this;
130
        $pwd = [];
131
132
        while ($cmd !== null) {
133
            $pwd[] = $cmd->parent !== null ? $cmd->cmd : 'cmd';
134
            $cmd = $cmd->parent;
135
        }
136
137
        $pwd = array_reverse($pwd);
138
139
        $pwd_str = '';
140
        foreach ($pwd as $p) {
141
            $pwd_str .= ucfirst(strtolower($p));
142
        }
143
144
        return lcfirst($pwd_str);
145
    }
146
147
    public function getCallable(): ?callable {
148
        return $this->callable;
149
    }
150
151
    public function getOptionCollection(): OptionCollection {
152
153
        if($this->optionCollection !== null) {
154
            return $this->optionCollection;
155
        }
156
157
        $specs = (array)$this->optionSpecs;
158
159
        $collection = new OptionCollection();
160
161
        foreach ($specs as $k => $v) {
162
            $opt = $collection->add($k, $v['description']);
163
            if (array_key_exists('isa', $v)) {
164
                $opt->isa($v['isa']);
165
            }
166
            if (array_key_exists('default', $v)) {
167
                $opt->defaultValue($v['default']);
168
            }
169
        }
170
171
        $this->optionCollection = $collection;
172
        return $this->optionCollection;
173
    }
174
175
    public function handleOptionParseException(): void {
176
177
        if($this->optionParseException === null) {
178
            return;
179
        }
180
181
        App::eol();
182
        App::echo('Uups, something went wrong!', Color::FOREGROUND_COLOR_RED);
183
        App::eol();
184
        App::echo($this->optionParseException->getMessage(), Color::FOREGROUND_COLOR_RED);
185
        App::eol();
186
187
        $this->help();
188
189
        exit();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
190
    }
191
192
    public function help(): void {
193
194
195
        $help = <<<EOT
196
197
              o       
198
           ` /_\ '    
199
          - (o o) -   
200
----------ooO--(_)--Ooo----------
201
          Need help?
202
---------------------------------  
203
EOT;
204
205
206
        App::echo($help, Color::FOREGROUND_COLOR_YELLOW);
207
208
        App::eol();
209
210
        $oc = $this->getOptionCollection();
211
        $has_options = !empty($oc->options);
212
213
        $has_subcommands = !empty($this->subcommands);
214
215
        App::eol();
216
        App::echo('Usage: ', Color::FOREGROUND_COLOR_YELLOW);
217
        App::eol();
218
219
        App::echo(
220
            '  ' .
221
            ($this->parent !== null ? $this->cmd : 'command') .
222
            ($has_options ? ' [options]' : '') .
223
            ($has_subcommands ? ' [command]' : '')
224
        );
225
226
        App::eol();
227
228
229
230
        if ($has_options) {
231
232
            App::eol();
233
            App::echo('Options: ', Color::FOREGROUND_COLOR_YELLOW);
234
            App::eol();
235
236
            foreach ($oc->options as $option) {
237
238
                $s = '    ';
239
                if(!empty($option->short)) {
240
                    $s = '-' . $option->short . ', ';
241
                }
242
                $s .= '--' . $option->long;
243
244
                $s = '  ' . str_pad($s, 20, ' ');
245
                App::echo($s, Color::FOREGROUND_COLOR_GREEN);
246
247
                $s = ' ' . $option->desc;
248
                App::echo($s);
249
250
                if ($option->defaultValue) {
251
                    $s = ' [default: ' . $option->defaultValue . ']';
252
                    App::echo($s, Color::FOREGROUND_COLOR_YELLOW);
253
                }
254
255
                App::eol();
256
257
            }
258
259
            App::eol();
260
        }
261
262
        if($has_subcommands) {
263
264
            App::eol();
265
            App::echo('Available commands: ', Color::FOREGROUND_COLOR_YELLOW);
266
            App::eol();
267
268
            foreach ($this->subcommands as $cmd) {
269
270
                $s = '  ' . str_pad($cmd->cmd, 20, ' ');
271
                App::echo($s, Color::FOREGROUND_COLOR_GREEN);
272
273
                $s = ' ' . $cmd->descr;
274
                App::echo($s);
275
276
                App::eol();
277
            }
278
279
            App::eol();
280
        }
281
    }
282
283
    public static function extend(string $cmd): Cmd {
284
        return new class($cmd) extends Cmd {};
285
    }
286
287
    public static function root(): Cmd {
288
        return self::extend('__root');
289
    }
290
291
292
    /**
293
     * @param callable $callable
294
     * @return callable
295
     * @throws Exception
296
     */
297
    private function validateCallable(callable $callable): callable {
298
299
        $check = new ReflectionFunction($callable);
300
        $parameters = $check->getParameters();
301
302
        if (count($parameters) !== 1) {
303
            throw new RuntimeException('Invalid number of Parameters. Should be 1.');
304
        }
305
306
        $type = $parameters[0]->getType();
307
308
        if ($type === null) {
309
            throw new RuntimeException('Named type of Parameter 1 should be "' . __CLASS__ . '".');
310
        }
311
312
        /** @noinspection PhpPossiblePolymorphicInvocationInspection */
313
        $tname = $type->getName();
314
315
        if ($tname !== __CLASS__) {
316
            throw new RuntimeException('Named type of Parameter 1 should be "' . __CLASS__ . '".');
317
        }
318
319
        return $callable;
320
    }
321
}