Issues (21)

src/Adapter/YamlAdapter.php (2 issues)

Labels
Severity
1
<?php
2
3
namespace Startwind\Forrest\Adapter;
4
5
use GuzzleHttp\Client;
6
use Startwind\Forrest\Adapter\Loader\HttpAwareLoader;
7
use Startwind\Forrest\Adapter\Loader\HttpFileLoader;
8
use Startwind\Forrest\Adapter\Loader\Loader;
9
use Startwind\Forrest\Adapter\Loader\LoaderFactory;
10
use Startwind\Forrest\Adapter\Loader\LocalFileLoader;
11
use Startwind\Forrest\Adapter\Loader\WritableLoader;
12
use Startwind\Forrest\Command\Command;
13
use Startwind\Forrest\Command\CommandFactory;
14
use Startwind\Forrest\Command\Parameters\ParameterFactory;
15
use Symfony\Component\Yaml\Yaml;
16
17
class YamlAdapter extends BasicAdapter implements ClientAwareAdapter, ListAwareAdapter, EditableAdapter
18
{
19
    public const TYPE = 'yaml';
20
21
    public const YAML_FIELD_COMMANDS = 'commands';
22
    public const YAML_FIELD_PROMPT = 'prompt';
23
    public const YAML_FIELD_NAME = 'name';
24
    public const YAML_FIELD_DESCRIPTION = 'description';
25
26
    public function __construct(private readonly Loader $loader)
27
    {
28
    }
29
30
    /**
31
     * @inheritDoc
32
     */
33
    public function getType(): string
34
    {
35
        return self::TYPE;
36
    }
37
38
    private function getConfig(): array
39
    {
40
        $content = $this->loader->load();
41
        if (!$content) {
42
            return [];
43
        }
44
45
        return Yaml::parse($content);
46
    }
47
48
    /**
49
     * @inheritDoc
50
     */
51
    public function getCommands(bool $withParameters = true): array
52
    {
53
        $config = $this->getConfig();
54
55
        $commands = [];
56
57
        if (!array_key_exists(self::YAML_FIELD_COMMANDS, $config)) {
58
            throw new \RuntimeException('The given YAML file does not contain a section named "' . self::YAML_FIELD_COMMANDS . '".');
59
        }
60
61
        if (is_null($config[self::YAML_FIELD_COMMANDS])) {
62
            return [];
63
        }
64
65
        foreach ($config[self::YAML_FIELD_COMMANDS] as $identifier => $commandConfig) {
66
            if (!array_key_exists(self::YAML_FIELD_PROMPT, $commandConfig)) {
67
                throw new \RuntimeException('The mandatory field ' . self::YAML_FIELD_PROMPT . ' is not set for identifier "' . $identifier . '".');
68
            }
69
            if (!array_key_exists(self::YAML_FIELD_DESCRIPTION, $commandConfig)) {
70
                throw new \RuntimeException('The mandatory field ' . self::YAML_FIELD_DESCRIPTION . ' is not set for identifier "' . $identifier . '".');
71
            }
72
73
            $commands[$commandConfig[self::YAML_FIELD_NAME]] = CommandFactory::fromArray($commandConfig, $withParameters);
74
        }
75
76
        return $commands;
77
    }
78
79
    /**
80
     * @inheritDoc
81
     */
82
    public static function fromConfigArray(array $config, Client $client): Adapter
83
    {
84
        if (array_key_exists('file', $config)) {
85
            $yamlFile = $config['file'];
86
            $adapterConfig = ['config' => ['file' => $yamlFile]];
87
            if (str_contains($yamlFile, '://')) {
88
                $adapterConfig['type'] = 'httpFile';
89
            } else {
90
                $adapterConfig['type'] = 'localFile';
91
            }
92
        } elseif (array_key_exists('loader', $config)) {
93
            $adapterConfig = $config['loader'];
94
        } else {
95
            throw new \RuntimeException('Configuration not applicable.');
96
        }
97
98
        $loader = LoaderFactory::create($adapterConfig, $client);
99
100
        $adapter = new self($loader);
101
        $adapter->setClient($client);
102
103
        return $adapter;
104
    }
105
106
    /**
107
     * @inheritDoc
108
     */
109
    public function isEditable(): bool
110
    {
111
        return $this->loader instanceof WritableLoader;
112
    }
113
114
    /**
115
     * @inheritDoc
116
     */
117
    public function addCommand(Command $command): void
118
    {
119
        if (!$this->loader instanceof WritableLoader) {
120
            throw new \RuntimeException('This repository is not writable.');
121
        }
122
123
        $config = Yaml::parse($this->loader->load());
124
125
        foreach ($config[self::YAML_FIELD_COMMANDS] as $commandConfig) {
126
            if ($commandConfig[self::YAML_FIELD_NAME] == $command->getName()) {
127
                throw new \RuntimeException('A command with the name "' . $command->getName() . '" already exists. Please choose another one or delete the old command first.');
128
            }
129
        }
130
131
        $commandArray = [
132
            self::YAML_FIELD_NAME => $command->getName(),
133
            self::YAML_FIELD_DESCRIPTION => $command->getDescription(),
134
            self::YAML_FIELD_PROMPT => $command->getPrompt(),
135
        ];
136
137
        $commandName = $this->convertNameToIdentifier($command->getName());
138
139
        $config[YamlAdapter::YAML_FIELD_COMMANDS][$commandName] = $commandArray;
140
141
        $this->loader->write(Yaml::dump($config, 4));
0 ignored issues
show
The method write() does not exist on Startwind\Forrest\Adapter\Loader\Loader. It seems like you code against a sub-type of said class. However, the method does not exist in Startwind\Forrest\Adapter\Loader\HttpFileLoader or Startwind\Forrest\Adapte...ecorator\CacheDecorator. Are you sure you never get one of those? ( Ignorable by Annotation )

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

141
        $this->loader->/** @scrutinizer ignore-call */ 
142
                       write(Yaml::dump($config, 4));
Loading history...
142
    }
143
144
    /**
145
     * @inheritDoc
146
     */
147
    public function removeCommand(string $commandName): void
148
    {
149
        if (!($this->loader instanceof WritableLoader)) {
150
            throw new \RuntimeException('This repository is not editable.');
151
        }
152
153
        $config = Yaml::parse($this->loader->load());
154
155
        $changed = false;
156
157
        foreach ($config[self::YAML_FIELD_COMMANDS] as $key => $commandConfig) {
158
            if ($commandConfig[self::YAML_FIELD_NAME] == $commandName) {
159
                unset($config[self::YAML_FIELD_COMMANDS][$key]);
160
                $changed = true;
161
            }
162
        }
163
164
        if (!$changed) {
165
            throw  new \RuntimeException('No command with nane "' . $commandName . '" found.');
166
        }
167
168
        $this->loader->write(Yaml::dump($config, 4));
169
    }
170
171
    /**
172
     * Return a valid identifier based on the command name.
173
     */
174
    private function convertNameToIdentifier(string $name): string
175
    {
176
        return strtolower(str_replace(' ', '_', $name));
177
    }
178
179
    /**
180
     * @inheritDoc
181
     */
182
    public function setClient(Client $client): void
183
    {
184
        if ($this->loader instanceof HttpAwareLoader) {
185
            $this->loader->setClient($client);
0 ignored issues
show
The method setClient() does not exist on Startwind\Forrest\Adapter\Loader\Loader. It seems like you code against a sub-type of Startwind\Forrest\Adapter\Loader\Loader such as Startwind\Forrest\Adapter\Loader\HttpFileLoader or Startwind\Forrest\Adapte...der\PrivateGitHubLoader. ( Ignorable by Annotation )

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

185
            $this->loader->/** @scrutinizer ignore-call */ 
186
                           setClient($client);
Loading history...
186
        }
187
    }
188
189
    public function getCommand(string $identifier): Command
190
    {
191
        $commands = $this->getCommands();
192
193
        if (!array_key_exists($identifier, $commands)) {
194
            throw new \RuntimeException('No command with name ' . $identifier . ' found.');
195
        }
196
197
        return $commands[$identifier];
198
    }
199
200
    /**
201
     * @inheritDoc
202
     */
203
    public function assertHealth(): void
204
    {
205
        $this->loader->assertHealth();
206
    }
207
}
208