Passed
Push — master ( 911f86...49d6e4 )
by Timm
01:55
created

Cmd   A

Complexity

Total Complexity 2

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Test Coverage

Coverage 91.11%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 85
c 3
b 0
f 0
dl 0
loc 253
ccs 82
cts 90
cp 0.9111
rs 10
wmc 2

20 Methods

Rating   Name   Duplication   Size   Complexity  
A getMethodName() 0 17 4
A getProvidedOption() 0 5 2
A addOption() 0 5 1
root() 0 2 ?
extend() 0 2 ?
A getCallable() 0 2 1
A getSubCmd() 0 5 2
A hasProvidedOption() 0 2 2
A setCallable() 0 11 2
A hp$0 ➔ root() 0 2 1
A validateCallable() 0 23 4
A addSubCmd() 0 6 1
A hp$0 ➔ extend() 0 2 1
A getOptionCollection() 0 22 5
A __construct() 0 2 1
A handleOptionParseException() 0 15 2
A addArgument() 0 13 3
A hasSubCmd() 0 2 1
A help() 0 2 1
A setDescription() 0 5 1
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 array
51
     */
52
    public $argumentSpecs = [];
53
54
    /**
55
     * @var string[]
56
     */
57
    public $arguments = [];
58
59
    /**
60
     * @var Cmd[]
61
     */
62
    public $subcommands = [];
63
64
    /**
65
     * @var callable|null
66
     */
67
    private $callable;
68
69
70 20
    public function __construct(string $cmd) {
71 20
        $this->cmd = $cmd;
72
//        if ($cmd !== 'help') {
73
//            $this->addSubCmd(
74
//                self::extend('help')
75
//                    ->setDescription('Displays help for this command.')
76
//                    ->setCallable(static function (Cmd $cmd) {
77
//                        $cmd->parent->help();
78
//                    })
79
//            );
80
//        }
81 20
    }
82
83 20
    public function addOption(string $specString, array $config): self {
84
85 20
        $this->optionSpecs[$specString] = $config;
86
87 20
        return $this;
88
    }
89
90 15
    public function addArgument(string $specString, array $config): self {
91
92 15
        foreach ($this->argumentSpecs as $k => $v) {
93 15
            if (array_key_exists('multiple', $v)) {
94
                unset($this->argumentSpecs[$k]['multiple']);
95
            }
96
        }
97
98 15
        $config['index'] = count($this->argumentSpecs);
99
100 15
        $this->argumentSpecs[$specString] = $config;
101
102 15
        return $this;
103
    }
104
105 15
    public function addSubCmd(Cmd $cmd): self {
106
107 15
        $cmd->parent = $this;
108 15
        $this->subcommands[$cmd->cmd] = $cmd;
109
110 15
        return $this;
111
    }
112
113 15
    public function setDescription(string $descr): self {
114
115 15
        $this->descr = $descr;
116
117 15
        return $this;
118
    }
119
120
    /**
121
     * @param callable $callable
122
     * @return $this
123
     */
124 20
    public function setCallable(callable $callable): self {
125
126
        try {
127 20
            $this->callable = $this->validateCallable($callable);
128
129
        } catch (Exception $e) {
130
            echo __METHOD__ . ' has been called with invalid callable: ' . $e->getMessage() . "\n";
131
        }
132
133
134 20
        return $this;
135
    }
136
137 16
    public function hasSubCmd(string $cmd): bool {
138 16
        return array_key_exists($cmd, $this->subcommands);
139
    }
140
141 2
    public function hasProvidedOption(string $key): bool {
142 2
        return $this->optionResult !== null && $this->optionResult->has($key);
143
    }
144
145 2
    public function getProvidedOption(string $key) {
146 2
        if ($this->optionResult !== null) {
147 2
            return $this->optionResult->get($key);
148
        }
149
        return null;
150
    }
151
152 10
    public function getSubCmd(string $cmd): ?Cmd {
153 10
        if ($this->hasSubCmd($cmd)) {
154 10
            return $this->subcommands[$cmd];
155
        }
156
        return null;
157
    }
158
159 8
    public function getMethodName(): string {
160 8
        $cmd = $this;
161 8
        $pwd = [];
162
163 8
        while ($cmd !== null) {
164 8
            $pwd[] = $cmd->parent !== null ? $cmd->cmd : 'cmd';
165 8
            $cmd = $cmd->parent;
166
        }
167
168 8
        $pwd = array_reverse($pwd);
169
170 8
        $pwd_str = '';
171 8
        foreach ($pwd as $p) {
172 8
            $pwd_str .= ucfirst(strtolower($p));
173
        }
174
175 8
        return lcfirst($pwd_str);
176
    }
177
178 4
    public function getCallable(): ?callable {
179 4
        return $this->callable;
180
    }
181
182 20
    public function getOptionCollection(): OptionCollection {
183
184 20
        if ($this->optionCollection !== null) {
185 3
            return $this->optionCollection;
186
        }
187
188 20
        $specs = (array)$this->optionSpecs;
189
190 20
        $collection = new OptionCollection();
191
192 20
        foreach ($specs as $k => $v) {
193 20
            $opt = $collection->add($k, $v['description']);
194 20
            if (array_key_exists('isa', $v)) {
195 10
                $opt->isa($v['isa']);
196
            }
197 20
            if (array_key_exists('default', $v)) {
198 8
                $opt->defaultValue($v['default']);
199
            }
200
        }
201
202 20
        $this->optionCollection = $collection;
203 20
        return $this->optionCollection;
204
    }
205
206 5
    public function handleOptionParseException(): bool {
207
208 5
        if ($this->optionParseException === null) {
209 4
            return false;
210
        }
211
212 1
        App::eol();
213 1
        App::echo('Uups, something went wrong!', Color::FOREGROUND_COLOR_RED);
214 1
        App::eol();
215 1
        App::echo($this->optionParseException->getMessage(), Color::FOREGROUND_COLOR_RED);
216 1
        App::eol();
217
218 1
        $this->help();
219
220 1
        return true;
221
    }
222
223 3
    public function help(): void {
224 3
        (new HelpRunner($this))->run();
225 3
    }
226
227
228
    /**
229
     * @param callable $callable
230
     * @return callable
231
     * @throws Exception
232
     */
233 20
    private function validateCallable(callable $callable): callable {
234
235 20
        $check = new ReflectionFunction($callable);
236 20
        $parameters = $check->getParameters();
237
238 20
        if (count($parameters) !== 1) {
239
            throw new RuntimeException('Invalid number of Parameters. Should be 1.');
240
        }
241
242 20
        $type = $parameters[0]->getType();
243
244 20
        if ($type === null) {
245
            throw new RuntimeException('Named type of Parameter 1 should be "' . __CLASS__ . '".');
246
        }
247
248
        /** @noinspection PhpPossiblePolymorphicInvocationInspection */
249 20
        $tname = $type->getName();
250
251 20
        if ($tname !== __CLASS__) {
252
            throw new RuntimeException('Named type of Parameter 1 should be "' . __CLASS__ . '".');
253
        }
254
255 20
        return $callable;
256
    }
257
258
    public static function extend(string $cmd): Cmd {
259
        return new class($cmd) extends Cmd {
260
        };
261
    }
262
263 20
    public static function root(): Cmd {
264 20
        return self::extend('__root');
265
    }
266
}