Passed
Push — master ( 738022...b95edc )
by Timm
01:55
created

Cmd::getCallable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
1
<?php
2
3
namespace Stefaminator\Cli;
4
5
use Exception;
6
use GetOptionKit\OptionResult;
7
use ReflectionFunction;
8
use RuntimeException;
9
10
11
class Cmd {
12
13
    /**
14
     * @var Cmd
15
     */
16
    public $parent;
17
18
    /**
19
     * @var string
20
     */
21
    public $cmd;
22
23
    /**
24
     * @var array
25
     */
26
    public $params;
27
28
    /**
29
     * @var OptionResult
30
     */
31
    public $options;
32
33
    /**
34
     * @var string[]
35
     */
36
    public $arguments;
37
38
    /**
39
     * @var array
40
     */
41
    private $cmds = [];
42
43
    /**
44
     * @var Exception
45
     */
46
    public $optionParseException;
47
48
    /**
49
     * @var callable|null
50
     */
51
    private $callable;
52
53
54
    public function __construct(string $cmd) {
55
        $this->cmd = $cmd;
56
        if ($cmd !== 'help') {
57
            $this->addSubCmd(
58
                self::extend('help')
59
                    ->setCallable(static function(Cmd $cmd) {
60
                        $cmd->parent->help();
61
                    })
62
            );
63
        }
64
    }
65
66
    public function addParam(string $specString, array $config): self {
67
        $this->params[$specString] = $config;
68
        return $this;
69
    }
70
71
    public function addSubCmd(Cmd $cmd): self {
72
73
//        if (!array_key_exists($cmd->cmd, $this->cmds)) {
74
            $cmd->parent = $this;
75
            $this->cmds[$cmd->cmd] = $cmd;
76
//        }
77
78
        return $this;
79
    }
80
81
    /**
82
     * @param callable $callable
83
     * @return $this
84
     */
85
    public function setCallable(callable $callable): self {
86
87
        try {
88
            $this->callable = $this->validateCallable($callable);
89
90
        } catch (Exception $e) {
91
            echo __METHOD__ . ' has been called with invalid callable: ' . $e->getMessage() . "\n";
92
        }
93
94
95
        return $this;
96
    }
97
98
    public function existsSubCmd(string $cmd): bool {
99
        return array_key_exists($cmd, $this->cmds);
100
    }
101
102
    public function getSubCmd(string $cmd): ?Cmd {
103
        if ($this->existsSubCmd($cmd)) {
104
            return $this->cmds[$cmd];
105
        }
106
        return null;
107
    }
108
109
    public function getMethodName(): string {
110
        $cmd = $this;
111
        $pwd = [];
112
113
        while ($cmd !== null) {
114
            $pwd[] = $cmd->parent !== null ? $cmd->cmd : 'cmd';
115
            $cmd = $cmd->parent;
116
        }
117
118
        $pwd = array_reverse($pwd);
119
120
        $pwd_str = '';
121
        foreach ($pwd as $p) {
122
            $pwd_str .= ucfirst(strtolower($p));
123
        }
124
125
        return lcfirst($pwd_str);
126
    }
127
128
    public function getCallable(): ?callable {
129
        return $this->callable;
130
    }
131
132
    public function handleOptionParseException(): void {
133
134
        if($this->optionParseException === null) {
135
            return;
136
        }
137
138
        App::eol();
139
        App::echo('Uups, something went wrong!', Color::FOREGROUND_COLOR_RED);
140
        App::echo($this->optionParseException->getMessage(), Color::FOREGROUND_COLOR_RED);
141
142
        $this->help();
143
144
        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...
145
    }
146
147
    public function help(): void {
148
149
150
        $help = <<<EOT
151
152
              o       
153
           ` /_\ '    
154
          - (o o) -   
155
----------ooO--(_)--Ooo----------
156
            help?
157
---------------------------------  
158
EOT;
159
160
161
        App::echo($help, Color::FOREGROUND_COLOR_YELLOW);
162
163
        App::eol();
164
165
        if (!empty($this->params)) {
166
167
            App::eol();
168
            App::echo('Parameters: ', Color::FOREGROUND_COLOR_GREEN);
169
            App::eol();
170
171
            foreach ($this->params as $k => $v) {
172
173
                $line = '  ' . str_pad($k, 20, ' ');
174
                $line .= ' ' . $v['description'] . App::EOL;
175
176
                App::echo($line, Color::FOREGROUND_COLOR_GREEN);
177
178
            }
179
        }
180
    }
181
182
    public static function extend(string $cmd): Cmd {
183
        return new class($cmd) extends Cmd {};
184
    }
185
186
    public static function root(): Cmd {
187
        return self::extend('__root');
188
    }
189
190
191
    /**
192
     * @param callable $callable
193
     * @return callable
194
     * @throws Exception
195
     */
196
    private function validateCallable(callable $callable): callable {
197
198
        $check = new ReflectionFunction($callable);
199
        $parameters = $check->getParameters();
200
201
        if (count($parameters) !== 1) {
202
            throw new RuntimeException('Invalid number of Parameters. Should be 1.');
203
        }
204
205
        $type = $parameters[0]->getType();
206
207
        if ($type === null) {
208
            throw new RuntimeException('Named type of Parameter 1 should be "' . __CLASS__ . '".');
209
        }
210
211
        $tname = $type->getName();
212
213
        if ($tname !== __CLASS__) {
214
            throw new RuntimeException('Named type of Parameter 1 should be "' . __CLASS__ . '".');
215
        }
216
217
        return $callable;
218
    }
219
}