Completed
Push — master ( b2ebb1...0b3922 )
by Michał
03:28
created

ArgvToCollections::fill()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 36
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 36
rs 8.439
cc 6
eloc 15
nc 6
nop 3
1
<?php namespace nyx\console\input\formats\lexers;
2
3
// Internal dependencies
4
use nyx\console\input\parameter\values;
5
use nyx\console\input\parameter\definitions;
6
use nyx\console\input\formats\tokens;
7
use nyx\console\input;
8
9
/**
10
 * Argv to Collections Lexer
11
 *
12
 * Takes an Argv Tokens instance and populates the given Arguments and Options collections based on the
13
 * data contained in it.
14
 *
15
 * @version     0.1.0
16
 * @author      Michal Chojnacki <[email protected]>
17
 * @copyright   2012-2017 Nyx Dev Team
18
 * @link        https://github.com/unyx/nyx
19
 */
20
class ArgvToCollections
21
{
22
    /**
23
     * Fill the given Input Arguments and Options based on their definitions and the Argv input given.
24
     *
25
     * @param   values\Arguments    $arguments  The Input Arguments to fill.
26
     * @param   values\Options      $options    The Input Options to fill.
27
     * @param   tokens\Argv         $input      The Argv tokens to be parsed.
28
     */
29
    public function fill(values\Arguments $arguments, values\Options $options, tokens\Argv $input)
30
    {
31
        $isParsingOptions = true;
32
33
        // Grab all tokens. Can't iterate directly over the Collection as we need to easily determine
34
        // which tokens are left to be processed (thus array_shift() below).
35
        $input = $input->all();
36
37
        // Loop through the input given.
38
        while (null !== $token = array_shift($input)) {
39
40
            if ($isParsingOptions) {
41
                // When '--' is present as a token, it forces us to treat
42
                // everything following it as arguments.
43
                if ('--' === $token) {
44
                    $isParsingOptions = false;
45
                    continue;
46
                }
47
48
                // Full options (token beginning with two hyphens).
49
                if (0 === strpos($token, '--')) {
50
                    $this->handleLongOption($token, $options, $input);
51
                    continue;
52
                }
53
54
                // Shortcuts (token beginning with exactly one hyphen).
55
                if ('-' === $token[0]) {
56
                    $this->handleShortOption($token, $options, $input);
57
                    continue;
58
                }
59
            }
60
61
            // In any other case treat the token as an argument.
62
            $arguments->push($token);
63
        }
64
    }
65
66
    /**
67
     * Parses the given token as a long option and adds it to the Options set.
68
     *
69
     * @param   string          $token      The token to parse.
70
     * @param   values\Options  $options    The collection of Options being filled.
71
     * @param   array&          $input      A reference to the argv input array.
1 ignored issue
show
Documentation introduced by
The doc-type array& could not be parsed: Unknown type name "array&" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
72
     */
73
    protected function handleLongOption(string $token, values\Options $options, array& $input)
74
    {
75
        // Remove the two starting hyphens.
76
        $name  = substr($token, 2);
77
        $value = null;
78
79
        // If the token contains a value for the option, we need to split the token accordingly.
80
        if (false !== $pos = strpos($name, '=')) {
81
            $value = substr($name, $pos + 1);
82
            $name  = substr($name, 0, $pos);
83
        }
84
85
        // If the option didn't have a value assigned using the 'equals' sign, the value may be contained in
86
        // the next token, *if* the Option accepts values to begin with.
87
        // Note: It's possible that no Definition for the given Option even exists, but we're deferring
88
        // Exception throwing to parameters\Options::set() to avoid some duplicate code.
89
        /* @var input\Option $definition */
90
        if (!isset($value) && $definition = $options->definitions()->get($name) and $definition->hasValue()) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
91
            // $input[0][0] is the first character of the first token in the $input array.
92
            // Ensure the token does not begin with a hyphen, which would indicate it's another Option,
93
            // and not a value for the Option we are processing.
94
            if ('-' !== $input[0][0]) {
95
                $value = array_shift($input);
96
            }
97
        }
98
99
        $options->set($name, $value);
100
    }
101
102
    /**
103
     * Parses the given token as a short option and adds it to the Options set.
104
     *
105
     * @param   string          $token      The token to parse.
106
     * @param   values\Options  $options    The collection of Options being filled.
107
     * @param   array&          $input      A reference to the argv input array.
1 ignored issue
show
Documentation introduced by
The doc-type array& could not be parsed: Unknown type name "array&" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
108
     */
109
    protected function handleShortOption($token, values\Options $options, array& $input)
0 ignored issues
show
Unused Code introduced by
The parameter $input is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
110
    {
111
        // Remove the starting hyphen.
112
        // If it's just a hyphen and nothing else, ignore it since it's most likely a mistype.
113
        if (empty($shortcut = substr($token, 1))) {
114
            return;
115
        }
116
117
        foreach ($this->resolve($shortcut, $options) as $name => $value) {
118
            $options->set($name, $value);
119
        }
120
    }
121
122
    /**
123
     * Takes a short option or short option set (without the starting hyphen) and resolves it to full options
124
     * and their values, depending on the Definitions given.
125
     *
126
     * @param   string          $shortcut       The short option(s) to resolve.
127
     * @param   values\Options  $options        The Input Options being filled.
128
     * @return  array                           A map of full option $names => $values.
129
     */
130
    protected function resolve(string $shortcut, values\Options $options) : array
131
    {
132
        /* @var $definitions definitions\Options */
133
        $definitions = $options->definitions();
134
135
        // We can return right here if the shortcut is a single character.
136
        if (1 === $length = strlen($shortcut)) {
137
            return [$definitions->getByShortcut($shortcut)->getName() => null];
138
        }
139
140
        // We have more than one character. However, if the first character points to an option that accepts values,
141
        // we will treat all characters afterwards as a value for said option.
142
        if ($definition = $definitions->getByShortcut($shortcut[0]) and $definition->hasValue()) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
143
            // First, remove the shortcut from the string to leave us only with the value. Also, if the actual value
144
            // starts with "=", we're going to remove that character (ie. the two first characters instead of just the
145
            // shortcut) to cover bad syntax.
146
            $value = substr($shortcut, strpos($shortcut, '=') === 1 ? 2 : 1);
147
148
            return [$definition->getName() => $value];
149
        }
150
151
        // At this point consider the whole string as a set of different options.
152
        return $this->resolveOptionsSet($shortcut, $definitions);
153
    }
154
155
    /**
156
     * Takes a set of short options contained in a string and resolves them to full options and their
157
     * values, depending on the Definitions given.
158
     *
159
     * @param   string                  $shortcut       The short option(s) to resolve.
160
     * @param   definitions\Options     $definitions    The Options Definition.
161
     * @return  array                                   An array of full option $names => $values.
162
     */
163
    protected function resolveOptionsSet(string $shortcut, definitions\Options $definitions) : array
164
    {
165
        $length = strlen($shortcut);
166
        $result = [];
167
168
        // Loop through the string, character by character.
169
        for ($i = 0; $i < $length; $i++) {
170
            $definition = $definitions->getByShortcut($shortcut[$i]);
171
172
            // The last shortcut in a set may have a value appended afterwards.
173
            if ($definition->hasValue()) {
174
                $result[$definition->getName()] = $i === $length - 1 ? null : substr($shortcut, $i + 1);
175
                break;
176
            }
177
178
            $result[$definition->getName()] = true;
179
        }
180
181
        return $result;
182
    }
183
}
184