Passed
Push — master ( f31d6e...f89447 )
by Timm
01:52
created

Cmd::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2.0116

Importance

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