Passed
Push — master ( 28756c...9f9dc7 )
by Timm
01:55
created

Cmd::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 2
ccs 2
cts 2
cp 1
crap 1
rs 10
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
    public function getAllProvidedOptions(): array {
153
        $r = [];
154
        if ($this->optionResult !== null) {
155
            $keys = array_keys($this->optionResult->keys);
156
            foreach($keys as $key) {
157
                $r[$key] = $this->getProvidedOption($key);
158
            }
159
        }
160
        return $r;
161
    }
162
163
    public function getAllProvidedArguments(): array {
164
        return $this->arguments;
165
    }
166
167 10
    public function getSubCmd(string $cmd): ?Cmd {
168 10
        if ($this->hasSubCmd($cmd)) {
169 10
            return $this->subcommands[$cmd];
170
        }
171
        return null;
172
    }
173
174 8
    public function getMethodName(): string {
175 8
        $cmd = $this;
176 8
        $pwd = [];
177
178 8
        while ($cmd !== null) {
179 8
            $pwd[] = $cmd->parent !== null ? $cmd->cmd : 'cmd';
180 8
            $cmd = $cmd->parent;
181
        }
182
183 8
        $pwd = array_reverse($pwd);
184
185 8
        $pwd_str = '';
186 8
        foreach ($pwd as $p) {
187 8
            $pwd_str .= ucfirst(strtolower($p));
188
        }
189
190 8
        return lcfirst($pwd_str);
191
    }
192
193 4
    public function getCallable(): ?callable {
194 4
        return $this->callable;
195
    }
196
197 20
    public function getOptionCollection(): OptionCollection {
198
199 20
        if ($this->optionCollection !== null) {
200 3
            return $this->optionCollection;
201
        }
202
203 20
        $specs = (array)$this->optionSpecs;
204
205 20
        $collection = new OptionCollection();
206
207 20
        foreach ($specs as $k => $v) {
208 20
            $opt = $collection->add($k, $v['description']);
209 20
            if (array_key_exists('isa', $v)) {
210 10
                $opt->isa($v['isa']);
211
            }
212 20
            if (array_key_exists('default', $v)) {
213 8
                $opt->defaultValue($v['default']);
214
            }
215
        }
216
217 20
        $this->optionCollection = $collection;
218 20
        return $this->optionCollection;
219
    }
220
221 5
    public function handleOptionParseException(): bool {
222
223 5
        if ($this->optionParseException === null) {
224 4
            return false;
225
        }
226
227 1
        App::eol();
228 1
        App::echo('Uups, something went wrong!', Color::FOREGROUND_COLOR_RED);
229 1
        App::eol();
230 1
        App::echo($this->optionParseException->getMessage(), Color::FOREGROUND_COLOR_RED);
231 1
        App::eol();
232
233 1
        $this->help();
234
235 1
        return true;
236
    }
237
238 3
    public function help(): void {
239 3
        (new HelpRunner($this))->run();
240 3
    }
241
242
243
    /**
244
     * @param callable $callable
245
     * @return callable
246
     * @throws Exception
247
     */
248 20
    private function validateCallable(callable $callable): callable {
249
250 20
        $check = new ReflectionFunction($callable);
251 20
        $parameters = $check->getParameters();
252
253 20
        if (count($parameters) !== 1) {
254
            throw new RuntimeException('Invalid number of Parameters. Should be 1.');
255
        }
256
257 20
        $type = $parameters[0]->getType();
258
259 20
        if ($type === null) {
260
            throw new RuntimeException('Named type of Parameter 1 should be "' . __CLASS__ . '".');
261
        }
262
263
        /** @noinspection PhpPossiblePolymorphicInvocationInspection */
264 20
        $tname = $type->getName();
265
266 20
        if ($tname !== __CLASS__) {
267
            throw new RuntimeException('Named type of Parameter 1 should be "' . __CLASS__ . '".');
268
        }
269
270 20
        return $callable;
271
    }
272
273
    public static function extend(string $cmd): Cmd {
274
        return new class($cmd) extends Cmd {
275
        };
276
    }
277
278 20
    public static function root(): Cmd {
279 20
        return self::extend('__root');
280
    }
281
}