Completed
Push — master ( 638356...2c98c7 )
by Greg
03:00
created

CommandData::setIncludeOptionsInArgs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
namespace Consolidation\AnnotatedCommand;
3
4
use Symfony\Component\Console\Input\ArgvInput;
5
use Symfony\Component\Console\Input\InputInterface;
6
use Symfony\Component\Console\Output\OutputInterface;
7
8
class CommandData
9
{
10
    /** var AnnotationData */
11
    protected $annotationData;
12
    /** var InputInterface */
13
    protected $input;
14
    /** var OutputInterface */
15
    protected $output;
16
    /** var boolean */
17
    protected $usesInputInterface;
18
    /** var boolean */
19
    protected $usesOutputInterface;
20
    /** var boolean */
21
    protected $includeOptionsInArgs;
22
    /** var array */
23
    protected $specialDefaults = [];
24
25
    public function __construct(
26
        AnnotationData $annotationData,
27
        InputInterface $input,
28
        OutputInterface $output,
29
        $usesInputInterface = false,
0 ignored issues
show
Unused Code introduced by
The parameter $usesInputInterface 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...
30
        $usesOutputInterface = false
0 ignored issues
show
Unused Code introduced by
The parameter $usesOutputInterface 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...
31
    ) {
32
        $this->annotationData = $annotationData;
33
        $this->input = $input;
34
        $this->output = $output;
35
        $this->usesInputInterface = false;
36
        $this->usesOutputInterface = false;
37
        $this->includeOptionsInArgs = true;
38
    }
39
40
    /**
41
     * For internal use only; indicates that the function to be called
42
     * should be passed an InputInterface &/or an OutputInterface.
43
     * @param booean $usesInputInterface
44
     * @param boolean $usesOutputInterface
45
     * @return self
46
     */
47
    public function setUseIOInterfaces($usesInputInterface, $usesOutputInterface)
48
    {
49
        $this->usesInputInterface = $usesInputInterface;
50
        $this->usesOutputInterface = $usesOutputInterface;
51
        return $this;
52
    }
53
54
    /**
55
     * For backwards-compatibility mode only: disable addition of
56
     * options on the end of the arguments list.
57
     */
58
    public function setIncludeOptionsInArgs($includeOptionsInArgs)
59
    {
60
        $this->includeOptionsInArgs = $includeOptionsInArgs;
61
        return $this;
62
    }
63
64
    public function annotationData()
65
    {
66
        return $this->annotationData;
67
    }
68
69
    public function input()
70
    {
71
        return $this->input;
72
    }
73
74
    public function output()
75
    {
76
        return $this->output;
77
    }
78
79
    public function arguments()
80
    {
81
        return $this->input->getArguments();
82
    }
83
84
    public function options()
85
    {
86
        // We cannot tell the difference between '--foo' (an option without
87
        // a value) and the absence of '--foo' when the option has an optional
88
        // value, and the current vallue of the option is 'null' using only
89
        // the public methods of InputInterface. We'll try to figure out
90
        // which is which by other means here.
91
        $options = $this->getAdjustedOptions();
92
93
        // Make two conversions here:
94
        // --foo=0 wil convert $value from '0' to 'false' for binary options.
95
        // --foo with $value of 'true' will be forced to 'false' if --no-foo exists.
96
        foreach ($options as $option => $value) {
97
            if ($this->shouldConvertOptionToFalse($options, $option, $value)) {
98
                $options[$option] = false;
99
            }
100
        }
101
102
        return $options;
103
    }
104
105
    /**
106
     * Use 'hasParameterOption()' to attempt to disambiguate option states.
107
     */
108
    protected function getAdjustedOptions()
109
    {
110
        $options = $this->input->getOptions();
111
112
        // If Input isn't an ArgvInput, then return the options as-is.
113
        if (!$this->input instanceof ArgvInput) {
114
            return $options;
115
        }
116
117
        // If we have an ArgvInput, then we can determine if options
118
        // are missing from the command line. If the option value is
119
        // missing from $input, then we will keep the value `null`.
120
        // If it is present, but has no explicit value, then change it its
121
        // value to `true`.
122
        foreach ($options as $option => $value) {
123
            if (($value === null) && ($this->input->hasParameterOption("--$option"))) {
124
                $options[$option] = true;
125
            }
126
        }
127
128
        return $options;
129
    }
130
131
    protected function shouldConvertOptionToFalse($options, $option, $value)
132
    {
133
        // If the value is 'true' (e.g. the option is '--foo'), then convert
134
        // it to false if there is also an option '--no-foo'. n.b. if the
135
        // commandline has '--foo=bar' then $value will not be 'true', and
136
        // --no-foo will be ignored.
137
        if ($value === true) {
138
            // Check if the --no-* option exists. Note that none of the other
139
            // alteration apply in the $value == true case, so we can exit early here.
140
            $negation_key = 'no-' . $option;
141
            return array_key_exists($negation_key, $options) && $options[$negation_key];
142
        }
143
144
        // If the option is '--foo=0', convert the '0' to 'false' when appropriate.
145
        if ($value !== '0') {
146
            return false;
147
        }
148
149
        // The '--foo=0' convertion is only applicable when the default value
150
        // is not in the special defaults list. i.e. you get a literal '0'
151
        // when your default is a string.
152
        return in_array($option, $this->specialDefaults);
153
    }
154
155
    public function cacheSpecialDefaults($definition)
156
    {
157
        foreach ($definition->getOptions() as $option => $inputOption) {
158
            $defaultValue = $inputOption->getDefault();
159
            if (($defaultValue === null) || ($defaultValue === true)) {
160
                $this->specialDefaults[] = $option;
161
            }
162
        }
163
    }
164
165
    public function getArgsWithoutAppName()
166
    {
167
        $args = $this->arguments();
168
169
        // When called via the Application, the first argument
170
        // will be the command name. The Application alters the
171
        // input definition to match, adding a 'command' argument
172
        // to the beginning.
173
        if ($this->input->hasArgument('command')) {
174
            array_shift($args);
175
        }
176
177
        if ($this->usesOutputInterface) {
178
            array_unshift($args, $this->output());
179
        }
180
181
        if ($this->usesInputInterface) {
182
            array_unshift($args, $this->input());
183
        }
184
185
        return $args;
186
    }
187
188
    public function getArgsAndOptions()
189
    {
190
        // Get passthrough args, and add the options on the end.
191
        $args = $this->getArgsWithoutAppName();
192
        if ($this->includeOptionsInArgs) {
193
            $args['options'] = $this->options();
194
        }
195
        return $args;
196
    }
197
}
198