Completed
Push — master ( 9573eb...833af1 )
by Amine
11s
created

Command::run()   C

Complexity

Conditions 8
Paths 26

Size

Total Lines 32
Code Lines 21

Duplication

Lines 5
Ratio 15.63 %

Importance

Changes 0
Metric Value
dl 5
loc 32
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 21
nc 26
nop 3
1
<?php namespace Tarsana\Command;
2
3
use Tarsana\Command\Commands\HelpCommand;
4
use Tarsana\Command\Commands\InteractiveCommand;
5
use Tarsana\Command\Commands\VersionCommand;
6
use Tarsana\Command\Console\Console;
7
use Tarsana\Command\Console\ExceptionPrinter;
8
use Tarsana\Command\Interfaces\Console\ConsoleInterface;
9
use Tarsana\Command\Interfaces\Template\TemplateLoaderInterface;
10
use Tarsana\Command\SubCommand;
11
use Tarsana\Command\Template\TemplateLoader;
12
use Tarsana\IO\Filesystem;
13
use Tarsana\IO\FilesystemInterface;
14
use Tarsana\Syntax\Exceptions\ParseException;
15
use Tarsana\Syntax\Factory as S;
16
use Tarsana\Syntax\Text as T;
17
18
class Command {
19
20
    protected $name;
21
    protected $version;
22
    protected $description;
23
24
    protected $syntax;
25
    protected $descriptions;
26
27
    protected $options;
28
    protected $args;
29
30
    protected $action;
31
    protected $commands;
32
33
    protected $console;
34
    protected $fs;
35
    protected $templatesLoader;
36
37
    public static function create(callable $action = null) {
38
        $command = new Command;
39
        if (null !== $action)
40
            $command->action($action);
41
        return $command;
42
    }
43
44
    public function __construct()
45
    {
46
        $this->commands([])
47
             ->name('Unknown')
48
             ->version('1.0.0')
49
             ->description('...')
50
             ->descriptions([])
51
             ->options([])
52
             ->console(new Console)
53
             ->fs(new Filesystem('.'))
54
             ->setupSubCommands()
55
             ->init();
56
    }
57
58
    /**
59
     * name getter and setter.
60
     *
61
     * @param  string
62
     * @return mixed
63
     */
64 View Code Duplication
    public function name(string $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
65
    {
66
        if (null === $value) {
67
            return $this->name;
68
        }
69
        $this->name = $value;
70
        return $this;
71
    }
72
73
    /**
74
     * version getter and setter.
75
     *
76
     * @param  string
77
     * @return mixed
78
     */
79
    public function version(string $value = null)
80
    {
81
        if (null === $value) {
82
            return $this->version;
83
        }
84
        $this->version = $value;
85
        return $this;
86
    }
87
88
    /**
89
     * description getter and setter.
90
     *
91
     * @param  string
92
     * @return mixed
93
     */
94 View Code Duplication
    public function description(string $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
95
    {
96
        if (null === $value) {
97
            return $this->description;
98
        }
99
        $this->description = $value;
100
        return $this;
101
    }
102
103
    /**
104
     * descriptions getter and setter.
105
     *
106
     * @param  string
107
     * @return mixed
108
     */
109
    public function descriptions(array $value = null)
110
    {
111
        if (null === $value) {
112
            return $this->descriptions;
113
        }
114
        $this->descriptions = $value;
115
        return $this;
116
    }
117
118
    /**
119
     * syntax getter and setter.
120
     *
121
     * @param  string|null $syntax
122
     * @return Syntax|self
123
     */
124
    public function syntax(string $syntax = null)
125
    {
126
        if (null === $syntax)
127
            return $this->syntax;
128
129
        $this->syntax = S::syntax()->parse("{{$syntax}| }");
130
        return $this;
131
    }
132
133
    /**
134
     * options getter and setter.
135
     *
136
     * @param  array
137
     * @return mixed
138
     */
139
    public function options(array $options = null)
140
    {
141
        if (null === $options) {
142
            return $this->options;
143
        }
144
145
        $this->options = [];
146
        foreach($options as $option)
147
            $this->options[$option] = false;
148
149
        return $this;
150
    }
151
152
    /**
153
     * option getter.
154
     *
155
     * @param  string
156
     * @return mixed
157
     */
158
    public function option(string $name)
159
    {
160
        if (!array_key_exists($name, $this->options))
161
            throw new \InvalidArgumentException("Unknown option '{$name}'");
162
        return $this->options[$name];
163
    }
164
165
    /**
166
     * args getter and setter.
167
     *
168
     * @param  stdClass
169
     * @return mixed
170
     */
171
    public function args(\stdClass $value = null)
172
    {
173
        if (null === $value) {
174
            return $this->args;
175
        }
176
        $this->args = $value;
177
        return $this;
178
    }
179
180
    /**
181
     * console getter and setter.
182
     *
183
     * @param  ConsoleInterface
184
     * @return mixed
185
     */
186 View Code Duplication
    public function console(ConsoleInterface $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
187
    {
188
        if (null === $value) {
189
            return $this->console;
190
        }
191
        $this->console = $value;
192
        foreach ($this->commands as $name => $command) {
193
            $command->console = $value;
194
        }
195
        return $this;
196
    }
197
198
    /**
199
     * fs getter and setter.
200
     *
201
     * @param  Tarsana\IO\Filesystem|string
202
     * @return mixed
203
     */
204
    public function fs($value = null)
205
    {
206
        if (null === $value) {
207
            return $this->fs;
208
        }
209
        if (is_string($value))
210
            $value = new Filesystem($value);
211
        $this->fs = $value;
212
        foreach ($this->commands as $name => $command) {
213
            $command->fs = $value;
214
        }
215
        return $this;
216
    }
217
218
    /**
219
     * templatesLoader getter and setter.
220
     *
221
     * @param  Tarsana\Command\Interfaces\Template\TemplateLoaderInterface
222
     * @return mixed
223
     */
224 View Code Duplication
    public function templatesLoader(TemplateLoaderInterface $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
225
    {
226
        if (null === $value) {
227
            return $this->templatesLoader;
228
        }
229
        $this->templatesLoader = $value;
230
        foreach ($this->commands as $name => $command) {
231
            $command->templatesLoader = $value;
232
        }
233
        return $this;
234
    }
235
236 View Code Duplication
    public function templatesPath(string $path, string $cachePath = null) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237
        $this->templatesLoader = new TemplateLoader($path, $cachePath);
238
        foreach ($this->commands as $name => $command) {
239
            $command->templatesLoader = $this->templatesLoader();
240
        }
241
        return $this;
242
    }
243
244
    public function template(string $name) {
245
        if (null === $this->templatesLoader)
246
            throw new \Exception("Please initialize the templates loader before trying to load templates!");
247
        return $this->templatesLoader->load($name);
248
    }
249
250
    /**
251
     * action getter and setter.
252
     *
253
     * @param  callable
254
     * @return mixed
255
     */
256 View Code Duplication
    public function action(callable $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
257
    {
258
        if (null === $value) {
259
            return $this->action;
260
        }
261
        $this->action = $value;
262
        return $this;
263
    }
264
265
    /**
266
     * commands getter and setter.
267
     *
268
     * @param  array
269
     * @return mixed
270
     */
271 View Code Duplication
    public function commands(array $value = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
272
    {
273
        if (null === $value) {
274
            return $this->commands;
275
        }
276
        $this->commands = [];
277
        foreach ($value as $name => $command) {
278
            $this->command($name, $command);
279
        }
280
        return $this;
281
    }
282
283
    public function command(string $name, Command $command = null)
284
    {
285
        if (null === $command) {
286
            if (!array_key_exists($name, $this->commands))
287
                throw new \InvalidArgumentException("subcommand '{$name}' not found!");
288
            return $this->commands[$name];
289
        }
290
        $this->commands[$name] = $command;
291
        return $this;
292
    }
293
294
    public function hasCommand(string $name) : bool
295
    {
296
        return array_key_exists($name, $this->commands);
297
    }
298
299
    protected function setupSubCommands()
300
    {
301
        return $this->command('--help', new HelpCommand($this))
302
             ->command('--version', new VersionCommand($this))
303
             ->command('-i', new InteractiveCommand($this));
304
    }
305
306
    public function describe(string $name, string $description = null)
307
    {
308
        if (null === $description)
309
            return array_key_exists($name, $this->descriptions)
310
                ? $this->descriptions[$name] : '';
311
        if (substr($name, 0, 2) == '--' && array_key_exists($name, $this->options())) {
312
            $this->descriptions[$name] = $description;
313
            return $this;
314
        }
315
        try {
316
            $this->syntax->field($name);
317
            // throws exception if field is missing
318
            $this->descriptions[$name] = $description;
319
            return $this;
320
        } catch (\Exception $e) {
321
            throw new \InvalidArgumentException("Unknown field '{$name}'");
322
        }
323
    }
324
325
    public function run(array $args = null, array $options = [], bool $rawArgs = true)
326
    {
327
        try {
328
            $this->clear();
329
330
            if ($rawArgs) {
331
                if (null === $args) {
332
                    $args = $GLOBALS['argv'];
333
                    array_shift($args);
334
                }
335
336
                if (!empty($args) && array_key_exists($args[0], $this->commands)) {
337
                    $name = $args[0];
338
                    array_shift($args);
339
                    return $this->command($name)->run($args);
340
                }
341
342
                $this->parseArguments($args);
343
            } else {
344
                $this->args = (object) $args;
345 View Code Duplication
                foreach ($options as $name) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
346
                    if (!array_key_exists($name, $this->options))
347
                        throw new \Exception("Unknown option '{$name}'");
348
                    $this->options[$name] = true;
349
                }
350
            }
351
352
            return $this->fire();
353
        } catch (\Exception $e) {
354
            $this->handleError($e);
355
        }
356
    }
357
358
    protected function fire()
359
    {
360
        return (null === $this->action)
361
            ? $this->execute()
362
            : ($this->action)($this);
363
    }
364
365
    protected function clear()
366
    {
367
        $this->args = null;
368
        foreach($this->options as $name => $value) {
369
            $this->options[$name] = false;
370
        }
371
    }
372
373
    protected function parseArguments(array $args)
374
    {
375
        if (null === $this->syntax) {
376
            $this->args = null;
377
            return;
378
        }
379
380
        $arguments = [];
381 View Code Duplication
        foreach ($args as &$arg) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
382
            if (array_key_exists($arg, $this->options))
383
                $this->options[$arg] = true;
384
            else
385
                $arguments[] = $arg;
386
        }
387
        $arguments = T::join($arguments, ' ');
388
        $this->args = $this->syntax->parse($arguments);
389
    }
390
391
    protected function handleError(\Exception $e) {
392
        $output = (new ExceptionPrinter)->print($e);
393
        $this->console()->error($output);
394
    }
395
396
    protected function init() {}
397
    protected function execute() {}
398
399
}
400