PromptHelper   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 184
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 99
dl 0
loc 184
rs 10
c 0
b 0
f 0
wmc 16

7 Methods

Rating   Name   Duplication   Size   Complexity  
A getFromColonyRegistry() 0 22 3
A getDockerHubImage() 0 11 1
A getBindVolume() 0 21 3
A getSubdomain() 0 14 1
B getVersion() 0 35 6
A __construct() 0 5 1
A getServiceName() 0 12 1
1
<?php
2
3
namespace TheAentMachine\Prompt\Helper;
4
5
use Safe\Exceptions\ArrayException;
6
use Safe\Exceptions\StringsException;
7
use Symfony\Component\Console\Exception\InvalidArgumentException;
8
use Symfony\Component\Console\Helper\QuestionHelper;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Console\Question\Question;
12
use TheAentMachine\Aent\Registry\AentItemRegistry;
13
use TheAentMachine\Aent\Registry\ColonyRegistry;
14
use TheAentMachine\Aenthill\Pheromone;
15
use TheAentMachine\Prompt\Input;
16
use TheAentMachine\Prompt\Select;
17
use TheAentMachine\Registry\RegistryClient;
18
use TheAentMachine\Registry\TagsAnalyzer;
19
use TheAentMachine\Service\Volume\BindVolume;
20
use function Safe\mkdir;
21
use function Safe\chown;
22
use function Safe\chgrp;
23
use function Safe\sprintf;
24
25
final class PromptHelper
26
{
27
    /** @var InputInterface */
28
    private $input;
29
30
    /** @var OutputInterface */
31
    private $output;
32
33
    /** @var QuestionHelper */
34
    private $questionHelper;
35
36
    /**
37
     * PromptHelper constructor.
38
     * @param InputInterface $input
39
     * @param OutputInterface $output
40
     * @param QuestionHelper $questionHelper
41
     */
42
    public function __construct(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper)
43
    {
44
        $this->input = $input;
45
        $this->output = $output;
46
        $this->questionHelper = $questionHelper;
47
    }
48
49
    /**
50
     * @param ColonyRegistry $registry
51
     * @param string $text
52
     * @param null|string $helpText
53
     * @return AentItemRegistry
54
     * @throws ArrayException
55
     * @throws StringsException
56
     */
57
    public function getFromColonyRegistry(ColonyRegistry $registry, string $text, ?string $helpText = null): AentItemRegistry
58
    {
59
        $aents = $registry->getAents();
60
        $assoc = [];
61
        foreach ($aents as $aent) {
62
            $assoc[$aent->getName()] = $aent;
63
        }
64
        $items = \array_keys($assoc);
65
        $items[] = 'Custom';
66
        $select = new Select($this->input, $this->output, $this->questionHelper);
67
        $select
68
            ->setText($text)
69
            ->setHelpText($helpText)
70
            ->setCompulsory(true);
71
        $select
72
            ->setItems($items);
73
        $response = $select->run();
74
        if ($response === 'Custom') {
75
            $image = $this->getDockerHubImage();
76
            return new AentItemRegistry($image, $image);
77
        }
78
        return $assoc[$response];
79
    }
80
81
    /**
82
     * @return string
83
     * @throws ArrayException
84
     * @throws StringsException
85
     */
86
    public function getDockerHubImage(): string
87
    {
88
        $dockerHubImageInput = new Input($this->input, $this->output, $this->questionHelper);
89
        $dockerHubImageInput
90
            ->setText("\nThe docker image of your custom aent (without tag)")
91
            ->setCompulsory(true)
92
            ->setValidator(ValidatorHelper::getDockerImageWithoutTagValidator());
93
        $image = $dockerHubImageInput->run();
94
        $version = $this->getVersion($image);
0 ignored issues
show
Bug introduced by
It seems like $image can also be of type null; however, parameter $image of TheAentMachine\Prompt\He...mptHelper::getVersion() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

94
        $version = $this->getVersion(/** @scrutinizer ignore-type */ $image);
Loading history...
95
        $aent = $image . ':' . $version;
96
        return $aent;
97
    }
98
99
    /**
100
     * @param string $image
101
     * @return string
102
     * @throws ArrayException
103
     * @throws StringsException
104
     */
105
    public function getVersion(string $image): string
106
    {
107
        $registryClient = new RegistryClient();
108
        $tags = $registryClient->getImageTagsOnDockerHub($image);
109
        $tagsAnalyzer = new TagsAnalyzer();
110
        $proposedTags = $tagsAnalyzer->filterBestTags($tags);
111
        $default = $proposedTags[0] ?? $tags[0];
112
        $this->output->writeln("\nLet's choose the version of the <info>$image</info> image!");
113
        if (!empty($proposedTags)) {
114
            $this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>');
115
        }
116
        $this->output->writeln('Enter "v" to view all available versions, "?" for help.');
117
        $question = new Question("Version [$default]: ", $default);
118
        $question->setAutocompleterValues($tags);
119
        $question->setValidator(function (string $response) use ($tags, $image) {
120
            $response = \trim($response);
121
            if ($response === 'v') {
122
                $this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $tags) . '</info>');
123
                return 'v';
124
            }
125
            if ($response === '?') {
126
                $this->output->writeln("Please choose the version (i.e. the tag) of the <info>$image</info> image you are about to install. Press 'v' to view the list of available tags.");
127
                return '?';
128
            }
129
            if (!\in_array($response, $tags, true)) {
130
                throw new InvalidArgumentException("Version \"$response\" is invalid.");
131
            }
132
            return $response;
133
        });
134
        do {
135
            $version = $this->questionHelper->ask($this->input, $this->output, $question);
136
        } while ($version === 'v' || $version === '?');
137
        $aent = $image . ':' . $version;
138
        $this->output->writeln("\n👌 Alright, I'm going to use <info>$aent</info>!");
139
        return $version;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $version could return the type boolean|null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
140
    }
141
142
    /**
143
     * @return string
144
     */
145
    public function getServiceName(): string
146
    {
147
        $input = new Input($this->input, $this->output, $this->questionHelper);
148
        $text = "\nYour service name";
149
        $helpText = "The service name is used as an identifier for the container you are creating. It is also bound in Docker internal network DNS and can be used from other containers to reference your container.";
150
        $input
151
            ->setText($text)
152
            ->setHelpText($helpText)
153
            ->setCompulsory(true)
154
            ->setValidator(ValidatorHelper::getAlphaWithAdditionalCharactersValidator(['_', '.', '-']));
155
        $response = $input->run();
156
        return $response ?? '';
157
    }
158
159
    /**
160
     * @param string $text
161
     * @param string $target
162
     * @param null|string $helpText
163
     * @return BindVolume
164
     */
165
    public function getBindVolume(string $text, string $target, ?string $helpText = null): BindVolume
166
    {
167
        $input = new Input($this->input, $this->output, $this->questionHelper);
168
        $input
169
            ->setText($text)
170
            ->setHelpText($helpText)
171
            ->setCompulsory(true)
172
            ->setValidator(function (string $dir) {
173
                $dir = trim($dir, '/') ?: '.';
174
                $rootDir = Pheromone::getContainerProjectDirectory();
175
                $fullDir = $rootDir.'/'.$dir;
176
                if (!file_exists($fullDir)) {
177
                    mkdir($fullDir);
178
                    $containerProjectDirInfo = new \SplFileInfo(\dirname($fullDir));
179
                    chown($fullDir, $containerProjectDirInfo->getOwner());
180
                    chgrp($fullDir, $containerProjectDirInfo->getGroup());
181
                }
182
                return $dir;
183
            });
184
        $source = $input->run() ?? '';
185
        return new BindVolume($source, $target);
186
    }
187
188
    /**
189
     * @param string $serviceName
190
     * @param int $port
191
     * @param string $baseVirtualHost
192
     * @return string
193
     * @throws StringsException
194
     */
195
    public function getSubdomain(string $serviceName, int $port, string $baseVirtualHost): string
196
    {
197
        $input = new Input($this->input, $this->output, $this->questionHelper);
198
        $text = sprintf(
199
            "\nSubdomain for your service <info>%s</info> on port <info>%s</info> (relative to <info>%s</info>)",
200
            $serviceName,
201
            $port,
202
            $baseVirtualHost
203
        );
204
        $input
205
            ->setText($text)
206
            ->setCompulsory(true)
207
            ->setValidator(ValidatorHelper::getAlphaWithAdditionalCharactersValidator(['-']));
208
        return $input->run() ?? '';
209
    }
210
}
211