Completed
Push — master ( cbe94a...9757e4 )
by Amine
01:43
created

Command::commands()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 11
Ratio 100 %

Importance

Changes 0
Metric Value
dl 11
loc 11
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 7
nc 3
nop 1
1
<?php namespace Tarsana\Command;
2
3
use Tarsana\Command\Commands\HelpCommand;
4
use Tarsana\Command\Commands\VersionCommand;
5
use Tarsana\Command\Console\Console;
6
use Tarsana\Command\Console\ExceptionPrinter;
7
use Tarsana\Command\Interfaces\Console\ConsoleInterface;
8
use Tarsana\Command\Interfaces\Template\TemplateLoaderInterface;
9
use Tarsana\Command\SubCommand;
10
use Tarsana\Command\Template\TemplateLoader;
11
use Tarsana\IO\Filesystem;
12
use Tarsana\IO\FilesystemInterface;
13
use Tarsana\Syntax\Exceptions\ParseException;
14
use Tarsana\Syntax\Factory as S;
15
use Tarsana\Syntax\Text as T;
16
17
class Command {
18
19
    protected $name;
20
    protected $version;
21
    protected $description;
22
23
    protected $syntax;
24
    protected $descriptions;
25
26
    protected $options;
27
    protected $args;
28
29
    protected $action;
30
    protected $commands;
31
32
    protected $console;
33
    protected $fs;
34
    protected $templatesLoader;
35
36
    public static function create(callable $action = null) {
37
        $command = new Command;
38
        if (null !== $action)
39
            $command->action($action);
40
        return $command;
41
    }
42
43
    public function __construct()
44
    {
45
        $this->commands([])
46
             ->setupSubCommands()
47
             ->name('Unknown')
48
             ->version('1.0.0')
49
             ->description('...')
50
             ->descriptions([])
51
             ->options([])
52
             ->console(new Console)
53
             ->fs(new Filesystem('.'))
54
             ->init();
55
    }
56
57
    /**
58
     * name getter and setter.
59
     *
60
     * @param  string
61
     * @return mixed
62
     */
63 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...
64
    {
65
        if (null === $value) {
66
            return $this->name;
67
        }
68
        $this->name = $value;
69
        return $this;
70
    }
71
72
    /**
73
     * version getter and setter.
74
     *
75
     * @param  string
76
     * @return mixed
77
     */
78
    public function version(string $value = null)
79
    {
80
        if (null === $value) {
81
            return $this->version;
82
        }
83
        $this->version = $value;
84
        return $this;
85
    }
86
87
    /**
88
     * description getter and setter.
89
     *
90
     * @param  string
91
     * @return mixed
92
     */
93 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...
94
    {
95
        if (null === $value) {
96
            return $this->description;
97
        }
98
        $this->description = $value;
99
        return $this;
100
    }
101
102
    /**
103
     * descriptions getter and setter.
104
     *
105
     * @param  string
106
     * @return mixed
107
     */
108
    public function descriptions(array $value = null)
109
    {
110
        if (null === $value) {
111
            return $this->descriptions;
112
        }
113
        $this->descriptions = $value;
114
        return $this;
115
    }
116
117
    /**
118
     * syntax getter and setter.
119
     *
120
     * @param  string|null $syntax
121
     * @return Syntax|self
122
     */
123
    public function syntax(string $syntax = null)
124
    {
125
        if (null === $syntax)
126
            return $this->syntax;
127
128
        $this->syntax = S::syntax()->parse("{{$syntax}| }");
129
        return $this;
130
    }
131
132
    /**
133
     * options getter and setter.
134
     *
135
     * @param  array
136
     * @return mixed
137
     */
138
    public function options(array $options = null)
139
    {
140
        if (null === $options) {
141
            return $this->options;
142
        }
143
144
        $this->options = [];
145
        foreach($options as $option)
146
            $this->options[$option] = false;
147
148
        return $this;
149
    }
150
151
    /**
152
     * option getter.
153
     *
154
     * @param  string
155
     * @return mixed
156
     */
157
    public function option(string $name)
158
    {
159
        if (!array_key_exists($name, $this->options))
160
            throw new \InvalidArgumentException("Unknown option '{$name}'");
161
        return $this->options[$name];
162
    }
163
164
    /**
165
     * args getter and setter.
166
     *
167
     * @param  stdClass
168
     * @return mixed
169
     */
170
    public function args(\stdClass $value = null)
171
    {
172
        if (null === $value) {
173
            return $this->args;
174
        }
175
        $this->args = $value;
176
        return $this;
177
    }
178
179
    /**
180
     * console getter and setter.
181
     *
182
     * @param  ConsoleInterface
183
     * @return mixed
184
     */
185 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...
186
    {
187
        if (null === $value) {
188
            return $this->console;
189
        }
190
        $this->console = $value;
191
        foreach ($this->commands as $name => $command) {
192
            $command->console = $value;
193
        }
194
        return $this;
195
    }
196
197
    /**
198
     * fs getter and setter.
199
     *
200
     * @param  Tarsana\IO\Filesystem|string
201
     * @return mixed
202
     */
203
    public function fs($value = null)
204
    {
205
        if (null === $value) {
206
            return $this->fs;
207
        }
208
        if (is_string($value))
209
            $value = new Filesystem($value);
210
        $this->fs = $value;
211
        foreach ($this->commands as $name => $command) {
212
            $command->fs = $value;
213
        }
214
        return $this;
215
    }
216
217
    /**
218
     * templatesLoader getter and setter.
219
     *
220
     * @param  Tarsana\Command\Interfaces\Template\TemplateLoaderInterface
221
     * @return mixed
222
     */
223 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...
224
    {
225
        if (null === $value) {
226
            return $this->templatesLoader;
227
        }
228
        $this->templatesLoader = $value;
229
        foreach ($this->commands as $name => $command) {
230
            $command->templatesLoader = $value;
231
        }
232
        return $this;
233
    }
234
235 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...
236
        $this->templatesLoader = new TemplateLoader($path, $cachePath);
237
        foreach ($this->commands as $name => $command) {
238
            $command->templatesLoader = $this->templatesLoader();
239
        }
240
        return $this;
241
    }
242
243
    public function template(string $name) {
244
        if (null === $this->templatesLoader)
245
            throw new \Exception("Please initialize the templates loader before trying to load templates!");
246
        return $this->templatesLoader->load($name);
247
    }
248
249
    /**
250
     * action getter and setter.
251
     *
252
     * @param  callable
253
     * @return mixed
254
     */
255 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...
256
    {
257
        if (null === $value) {
258
            return $this->action;
259
        }
260
        $this->action = $value;
261
        return $this;
262
    }
263
264
    /**
265
     * commands getter and setter.
266
     *
267
     * @param  array
268
     * @return mixed
269
     */
270 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...
271
    {
272
        if (null === $value) {
273
            return $this->commands;
274
        }
275
        $this->commands = [];
276
        foreach ($value as $name => $command) {
277
            $this->command($name, $command);
278
        }
279
        return $this;
280
    }
281
282
    public function command(string $name, Command $command = null)
283
    {
284
        if (null === $command) {
285
            if (!array_key_exists($name, $this->commands))
286
                throw new \InvalidArgumentException("subcommand '{$name}' not found!");
287
            return $this->commands[$name];
288
        }
289
        $this->commands[$name] = $command;
290
        return $this;
291
    }
292
293
    protected function setupSubCommands()
294
    {
295
        return $this->command('--help', new HelpCommand($this))
296
             ->command('--version', new VersionCommand($this));
297
    }
298
299
    public function describe(string $name, string $description = null)
300
    {
301
        if (null === $description)
302
            return array_key_exists($name, $this->descriptions)
303
                ? $this->descriptions[$name] : '';
304
        if (substr($name, 0, 2) == '--' && array_key_exists($name, $this->options())) {
305
            $this->descriptions[$name] = $description;
306
            return $this;
307
        }
308
        try {
309
            $this->syntax->field($name);
310
            // throws exception if field is missing
311
            $this->descriptions[$name] = $description;
312
            return $this;
313
        } catch (\Exception $e) {
314
            throw new \InvalidArgumentException("Unknown field '{$name}'");
315
        }
316
    }
317
318
    public function run(array $args = null, array $options = [], bool $rawArgs = true)
0 ignored issues
show
Coding Style introduced by
run uses the super-global variable $GLOBALS which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
319
    {
320
        try {
321
            $this->clear();
322
323
            if ($rawArgs) {
324
                if (null === $args) {
325
                    $args = $GLOBALS['argv'];
326
                    array_shift($args);
327
                }
328
329
                if (!empty($args) && array_key_exists($args[0], $this->commands)) {
330
                    $name = $args[0];
331
                    array_shift($args);
332
                    return $this->command($name)->run($args);
333
                }
334
335
                $this->parseArguments($args);
336
            } else {
337
                $this->args = (object) $args;
338 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...
339
                    if (!array_key_exists($name, $this->options))
340
                        throw new \Exception("Unknown option '{$name}'");
341
                    $this->options[$name] = true;
342
                }
343
            }
344
345
            if (null === $this->action)
346
                $this->execute();
347
            else
348
                ($this->action)($this);
349
        } catch (\Exception $e) {
350
            $this->handleError($e);
351
        }
352
    }
353
354
    public function clear()
355
    {
356
        $this->args = null;
357
        foreach($this->options as $name => $value) {
358
            $this->options[$name] = false;
359
        }
360
    }
361
362
    public function parseArguments(array $args)
363
    {
364
        if (null === $this->syntax) {
365
            $this->args = null;
366
            return;
367
        }
368
369
        $arguments = [];
370 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...
371
            if (array_key_exists($arg, $this->options))
372
                $this->options[$arg] = true;
373
            else
374
                $arguments[] = $arg;
375
        }
376
        $arguments = T::join($arguments, ' ');
377
        $this->args = $this->syntax->parse($arguments);
378
    }
379
380
    protected function handleError(\Exception $e) {
381
        $output = (new ExceptionPrinter)->print($e);
382
        $this->console()->error($output);
383
    }
384
385
    protected function init() {}
386
    protected function execute() {}
387
388
}
389