Passed
Push — master ( a2340d...9d7d2b )
by butschster
06:32
created

Parser   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Test Coverage

Coverage 98.98%

Importance

Changes 0
Metric Value
wmc 13
eloc 81
dl 0
loc 165
ccs 97
cts 98
cp 0.9898
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A parseParameters() 0 14 3
A parse() 0 11 3
A parseName() 0 7 2
A extractDescription() 0 7 2
A parseOption() 0 43 2
A parseArgument() 0 36 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Console\Signature;
6
7
use InvalidArgumentException;
8
use Symfony\Component\Console\Input\InputArgument;
9
use Symfony\Component\Console\Input\InputOption;
10
11
/**
12
 * Console signature parser.
13
 * @internal
14
 */
15
final class Parser
16
{
17
    /**
18
     * Parse the given console command definition into an array.
19
     *
20
     * @throws InvalidArgumentException
21
     */
22 7
    public function parse(string $signature): Result
23
    {
24 7
        $name = $this->parseName($signature);
25
26 7
        if (\preg_match_all('/\{\s*(.*?)\s*\}/', $signature, $matches)) {
27 6
            if (\count($matches[1])) {
28 6
                return new Result($name, ...$this->parseParameters($matches[1]));
0 ignored issues
show
Bug introduced by
$this->parseParameters($matches[1]) is expanded, but the parameter $arguments of Spiral\Console\Signature\Result::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

28
                return new Result($name, /** @scrutinizer ignore-type */ ...$this->parseParameters($matches[1]));
Loading history...
29
            }
30
        }
31
32 1
        return new Result($name);
33
    }
34
35
    /**
36
     * Extract the name of the command from the expression.
37
     *
38
     * @throws InvalidArgumentException
39
     */
40 7
    private function parseName(string $signature): string
41
    {
42 7
        if (!\preg_match('/\S+/', $signature, $matches)) {
43
            throw new InvalidArgumentException('Unable to determine command name from signature.');
44
        }
45
46 7
        return $matches[0];
47
    }
48
49
    /**
50
     * Extract all the parameters from the tokens.
51
     *
52
     * @return array{0: InputArgument[], 1: InputOption[]}
53
     */
54 6
    private function parseParameters(array $tokens): array
55
    {
56 6
        $arguments = [];
57 6
        $options = [];
58
59 6
        foreach ($tokens as $token) {
60 6
            if (\preg_match('/-{2,}(.*)/', $token, $matches)) {
61 5
                $options[] = $this->parseOption($matches[1]);
62
            } else {
63 5
                $arguments[] = $this->parseArgument($token);
64
            }
65
        }
66
67 6
        return [$arguments, $options];
68
    }
69
70
    /**
71
     * Parse an argument expression.
72
     *
73
     * @noRector \Rector\Php56\Rector\FunctionLike\AddDefaultValueForUndefinedVariableRector
74
     */
75 5
    private function parseArgument(string $token): InputArgument
76
    {
77 5
        [$token, $description] = $this->extractDescription($token);
78
79 5
        return match (true) {
80 5
            \str_ends_with($token, '[]?') => new InputArgument(
81 5
                \rtrim($token, '[]?'),
82 5
                InputArgument::IS_ARRAY,
83 5
                $description
84 5
            ),
85 5
            \str_ends_with($token, '[]') => new InputArgument(
86 5
                \rtrim($token, '[]'),
87 5
                InputArgument::IS_ARRAY | InputArgument::REQUIRED,
88 5
                $description
89 5
            ),
90 5
            \str_ends_with($token, '?') => new InputArgument(
91 5
                \rtrim($token, '?'),
92 5
                InputArgument::OPTIONAL,
93 5
                $description
94 5
            ),
95 5
            (bool)\preg_match('/(.+)\[\]\=(.+)/', $token, $matches) => new InputArgument(
96 5
                $matches[1],
97 5
                InputArgument::IS_ARRAY,
98 5
                $description,
99 5
                \preg_split('/,\s?/', $matches[2])
100 5
            ),
101 5
            (bool)\preg_match('/(.+)\=(.+)?/', $token, $matches) => new InputArgument(
102 5
                $matches[1],
103 5
                InputArgument::OPTIONAL,
104 5
                $description,
105 5
                $matches[2] ?? null
106 5
            ),
107 5
            default => new InputArgument(
108 5
                $token,
109 5
                InputArgument::REQUIRED,
110 5
                $description
111 5
            ),
112 5
        };
113
    }
114
115
    /**
116
     * Parse an option expression.
117
     */
118 5
    private function parseOption(string $token): InputOption
119
    {
120 5
        [$token, $description] = $this->extractDescription($token);
121
122 5
        $matches = \preg_split('/\s*\|\s*/', $token, 2);
123 5
        $shortcut = null;
124
125 5
        if (isset($matches[1])) {
126 5
            [$shortcut, $token] = $matches;
127
        }
128
129 5
        return match (true) {
130 5
            \str_ends_with($token, '[]=') => new InputOption(
131 5
                \rtrim($token, '[]='),
132 5
                $shortcut,
133 5
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
134 5
                $description
135 5
            ),
136 5
            \str_ends_with($token, '=') => new InputOption(
137 5
                \rtrim($token, '='),
138 5
                $shortcut,
139 5
                InputOption::VALUE_OPTIONAL,
140 5
                $description
141 5
            ),
142 5
            (bool)\preg_match('/(.+)\[\]\=(.+)/', $token, $matches) => new InputOption(
143 5
                $matches[1],
144 5
                $shortcut,
145 5
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
146 5
                $description,
147 5
                \preg_split('/,\s?/', $matches[2])
148 5
            ),
149 5
            (bool)\preg_match('/(.+)\=(.+)/', $token, $matches) => new InputOption(
150 5
                $matches[1],
151 5
                $shortcut,
152 5
                InputOption::VALUE_OPTIONAL,
153 5
                $description,
154 5
                $matches[2]
155 5
            ),
156 5
            default => new InputOption(
157 5
                $token,
158 5
                $shortcut,
159 5
                InputOption::VALUE_NONE,
160 5
                $description
161 5
            ),
162 5
        };
163
    }
164
165
    /**
166
     * Parse the token into its token and description segments.
167
     *
168
     * @return array{
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{ at position 2 could not be parsed: the token is null at position 2.
Loading history...
169
     *     0: non-empty-string,
170
     *     1: string
171
     * }
172
     */
173 6
    private function extractDescription(string $token): array
174
    {
175 6
        $token = \trim($token);
176
177 6
        $parts = \array_map('trim', \explode(':', $token, 2));
178
179 6
        return \count($parts) === 2 ? $parts : [$token, ''];
180
    }
181
}
182