Passed
Push — master ( 193bee...a6d59a )
by butschster
07:51 queued 01:37
created

Parser::parseArgument()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 36
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1.0023

Importance

Changes 0
Metric Value
eloc 28
c 0
b 0
f 0
dl 0
loc 36
ccs 13
cts 15
cp 0.8667
rs 9.472
cc 1
nc 1
nop 1
crap 1.0023
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
        return match (true) {
80 5
            \str_ends_with($token, '[]?') => new InputArgument(
81 3
                \rtrim($token, '[]?'),
82
                InputArgument::IS_ARRAY,
83
                $description
84
            ),
85 4
            \str_ends_with($token, '[]') => new InputArgument(
86 1
                \rtrim($token, '[]'),
87
                InputArgument::IS_ARRAY | InputArgument::REQUIRED,
88
                $description
89
            ),
90 4
            \str_ends_with($token, '?') => new InputArgument(
91 2
                \rtrim($token, '?'),
92
                InputArgument::OPTIONAL,
93
                $description
94
            ),
95 3
            (bool)\preg_match('/(.+)\[\]\=(.+)/', $token, $matches) => new InputArgument(
96
                $matches[1],
97
                InputArgument::IS_ARRAY,
98
                $description,
99
                \preg_split('/,\s?/', $matches[2])
100
            ),
101 3
            (bool)\preg_match('/(.+)\=(.+)?/', $token, $matches) => new InputArgument(
102 2
                $matches[1],
103
                InputArgument::OPTIONAL,
104
                $description,
105 2
                $matches[2] ?? null
106
            ),
107 3
            default => new InputArgument(
108
                $token,
109
                InputArgument::REQUIRED,
110
                $description
111
            ),
112
        };
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
        return match (true) {
130 5
            \str_ends_with($token, '[]=') => new InputOption(
131 3
                \rtrim($token, '[]='),
132
                $shortcut,
133
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
134
                $description
135
            ),
136 4
            \str_ends_with($token, '=') => new InputOption(
137 1
                \rtrim($token, '='),
138
                $shortcut,
139
                InputOption::VALUE_OPTIONAL,
140
                $description
141
            ),
142 4
            (bool)\preg_match('/(.+)\[\]\=(.+)/', $token, $matches) => new InputOption(
143
                $matches[1],
144
                $shortcut,
145
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
146
                $description,
147
                \preg_split('/,\s?/', $matches[2])
148
            ),
149 4
            (bool)\preg_match('/(.+)\=(.+)/', $token, $matches) => new InputOption(
150 2
                $matches[1],
151
                $shortcut,
152
                InputOption::VALUE_OPTIONAL,
153
                $description,
154 2
                $matches[2]
155
            ),
156 4
            default => new InputOption(
157
                $token,
158
                $shortcut,
159
                InputOption::VALUE_NONE,
160
                $description
161
            ),
162
        };
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