PromptHelper::showCommandInformation()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 10
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 18
rs 9.9332
1
<?php
2
3
namespace Startwind\Forrest\Output;
4
5
use Startwind\Forrest\Command\Command;
6
use Startwind\Forrest\Command\Parameters\Parameter;
7
use Startwind\Forrest\Command\Parameters\ParameterValue;
8
use Startwind\Forrest\Command\Parameters\PasswordParameter;
9
use Startwind\Forrest\Command\Prompt;
10
use Startwind\Forrest\Config\RecentParameterMemory;
11
use Startwind\Forrest\Runner\CommandRunner;
12
use Startwind\Forrest\Util\OutputHelper;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Startwind\Forrest\Output\OutputHelper. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
13
use Symfony\Component\Console\Helper\QuestionHelper;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use Symfony\Component\Console\Question\ChoiceQuestion;
17
use Symfony\Component\Console\Question\Question;
18
19
class PromptHelper
20
{
21
    protected array $runWarning = [
22
        "Be careful. Please only run command that you understand. We only have limited control",
23
        "of repositories that are not owned by this project.",
24
    ];
25
26
    private InputInterface $input;
27
    private OutputInterface $output;
28
29
    private QuestionHelper $questionHelper;
30
31
    private RecentParameterMemory $memory;
32
33
    public function __construct(
34
        InputInterface        $input,
35
        OutputInterface       $output,
36
        QuestionHelper        $questionHelper,
37
        RecentParameterMemory $memory
38
    )
39
    {
40
        $this->input = $input;
41
        $this->output = $output;
42
        $this->questionHelper = $questionHelper;
43
        $this->memory = $memory;
44
    }
45
46
    public function askForPrompt(Command $command, array $predefinedParameters = []): Prompt
47
    {
48
        if ($command->isParameterMissing($predefinedParameters)) {
49
            $this->showCommandInformation($this->output, $command);
50
        }
51
52
        $parameterValues = $this->askForParameterValues($command, $predefinedParameters);
53
54
        return new Prompt($command->getPrompt(), $parameterValues);
55
    }
56
57
    public function showFinalPrompt(Prompt $prompt): void
58
    {
59
        $this->output->writeln('');
60
        $this->output->writeln('  Final prompt: ');
61
        OutputHelper::writeInfoBox($this->output, CommandRunner::stringToMultilinePrompt($prompt->getSecurePrompt()));
62
    }
63
64
    private function askForParameterValues(Command $command, array $predefinedParameters = []): array
65
    {
66
        $values = [];
67
68
        $commandIdentifier = $command->getFullyQualifiedIdentifier();
69
70
        foreach ($command->getParameters() as $identifier => $parameter) {
71
72
            if (array_key_exists($identifier, $predefinedParameters)) {
73
                $values[] = new ParameterValue($identifier, $predefinedParameters[$identifier], $parameter->getType());
74
                continue;
75
            }
76
77
            $fullParameterIdentifier = $commandIdentifier . ':' . $identifier;
78
79
            $additional = $this->getAdditionalInfo($fullParameterIdentifier, $parameter);
80
81
            if ($parameter->getName()) {
82
                $name = $identifier . ' (' . $parameter->getName() . ')';
83
            } else {
84
                $name = $identifier;
85
            }
86
87
            $valid = false;
88
89
            while (!$valid) {
90
                if ($parameter->hasValues()) {
91
                    $value = $this->askForEnum('  Select value for ' . $name . $additional['string'] . ": \n", $parameter->getValues());
92
                } else {
93
                    $question = new Question('  Select value for ' . $name . $additional['string'] . ": ", $additional['value']);
94
                    if ($parameter instanceof PasswordParameter) {
95
                        $question->setHidden(true);
96
                        $question->setHiddenFallback(false);
97
                    }
98
                    $value = $this->questionHelper->ask($this->input, $this->output, $question);
99
                }
100
101
                $validationResult = $parameter->validate($value);
102
103
                $valid = $validationResult->isValid();
104
105
                if (!$valid) {
106
                    $this->output->writeln('  <error>' . $validationResult->getValidationMessage() . '</error>');
107
                }
108
            }
109
110
            if ($value) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $value does not seem to be defined for all execution paths leading up to this point.
Loading history...
111
                $value = $parameter->getPrefix() . $value . $parameter->getSuffix();
112
            }
113
114
            $values[] = new ParameterValue($identifier, $value, $parameter->getType());
115
116
            if ($value && !($parameter instanceof PasswordParameter)) {
117
                $this->memory->addParameter($fullParameterIdentifier, $value);
118
            }
119
        }
120
121
        return $values;
122
    }
123
124
    private function askForEnum(string $question, array $values): string
125
    {
126
        if (array_is_list($values)) {
127
            $value = $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion($question, $values));
128
        } else {
129
            $key = $this->questionHelper->ask($this->input, $this->output, new ChoiceQuestion($question, array_keys($values)));
130
            $value = $values[$key];
131
        }
132
133
        if ($value == Parameter::ENUM_CUSTOM || $value == Parameter::ENUM_CUSTOM_KEY) {
134
            $value = $this->questionHelper->ask($this->input, $this->output, new Question('Please enter a custom value: '));
135
        }
136
137
        return $value;
138
    }
139
140
    /**
141
     * Handle default and recent values for the current parameter.
142
     */
143
    private function getAdditionalInfo(string $fullParameterIdentifier, Parameter $parameter): array
144
    {
145
        $options = [];
146
147
        $defaultValue = '';
148
149
        if ($parameter->getDefaultValue()) {
150
            $options['default'] = 'default: "' . $parameter->getDefaultValue() . '"';
151
            $defaultValue = $parameter->getDefaultValue();
152
        }
153
154
        if (!$parameter->isDefaultForced()) {
155
            if ($this->memory->hasParameter($fullParameterIdentifier)) {
156
                $recentValue = $this->memory->getParameter($fullParameterIdentifier);
157
                $options['default'] = 'recent: "' . $recentValue . '"';
158
                $defaultValue = $recentValue;
159
            }
160
        }
161
162
        if ($parameter->isOptional()) {
163
            $options['optional'] = 'optional';
164
        }
165
166
        if (count($options) > 0) {
167
            $defaultString = ' [' . implode(', ', $options) . ']';
168
        } else {
169
            $defaultString = '';
170
        }
171
172
        return [
173
            'string' => $defaultString,
174
            'value' => $defaultValue
175
        ];
176
    }
177
178
    private function showCommandInformation(OutputInterface $output, Command $command): void
179
    {
180
        OutputHelper::writeWarningBox($this->output, $this->runWarning);
181
182
        $commands = CommandRunner::stringToMultilinePrompt($command->getPrompt());
183
184
        if (count($commands) > 1) {
185
            $plural = 's';
186
        } else {
187
            $plural = '';
188
        }
189
190
        $output->writeln('  Command' . $plural . ' to be run:');
191
        $output->writeln('');
192
193
        OutputHelper::writeInfoBox($output, $commands);
194
195
        $output->writeln('');
196
    }
197
}
198