Passed
Push — develop ( 26f314...550a04 )
by Eric
01:53
created

Generator::generatePhpEnum()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 22
nc 10
nop 2
dl 0
loc 36
ccs 24
cts 24
cp 1
crap 5
rs 9.2568
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Esi\Mimey.
7
 *
8
 * (c) Eric Sizemore <[email protected]>
9
 * (c) Ricardo Boss <[email protected]>
10
 * (c) Ralph Khattar <[email protected]>
11
 *
12
 * This source file is subject to the MIT license. For the full copyright,
13
 * license information, and credits/acknowledgements, please view the LICENSE
14
 * and README files that were distributed with this source code.
15
 */
16
/**
17
 * Esi\Mimey is a fork of Elephox\Mimey (https://github.com/elephox-dev/mimey) which is:
18
 *     Copyright (c) 2022 Ricardo Boss
19
 * Elephox\Mimey is a fork of ralouphie/mimey (https://github.com/ralouphie/mimey) which is:
20
 *     Copyright (c) 2016 Ralph Khattar
21
 */
22
23
namespace Esi\Mimey\Mapping;
24
25
use Esi\Mimey\Interface\MimeTypeInterface;
26
use JsonException;
27
use RuntimeException;
28
29
use function array_filter;
30
use function array_unique;
31
use function array_values;
32
use function explode;
33
use function file_get_contents;
34
use function json_encode;
35
use function preg_replace;
36
use function sprintf;
37
use function str_pad;
38
use function str_replace;
39
use function trim;
40
use function ucfirst;
41
use function ucwords;
42
43
use const JSON_PRETTY_PRINT;
44
use const JSON_THROW_ON_ERROR;
45
use const STR_PAD_LEFT;
46
47
/**
48
 * Generates a mapping for use in the MimeTypes class.
49
 *
50
 * Reads text in the format of httpd's mime.types and generates a PHP array containing the mappings.
51
 *
52
 * The psalm-type looks gnarly, but it covers just about everything.
53
 *
54
 * @phpstan-type MimeTypeMap = array{
55
 *    mimes?: array<
56
 *        non-empty-string|string, list<non-empty-string>|array<int<0, max>, string>
57
 *    >|non-empty-array<
58
 *        non-falsy-string|string, non-empty-array<0, non-falsy-string>|array<int<0, max>, string>
59
 *    >,
60
 *    extensions?: array<
61
 *        non-empty-string, list<non-empty-string>
62
 *     >|non-empty-array<
63
 *        string|non-falsy-string, array<int<0, max>, string>|non-empty-array<0, non-falsy-string>
64
 *    >|array<
65
 *        string, array<int<0, max>, string>
66
 *    >
67
 * }
68
 */
69
class Generator
70
{
71
    /**
72
     * @var MimeTypeMap
73
     */
74
    protected array $mapCache = [];
75
76
    /**
77
     * Create a new generator instance with the given mime.types text.
78
     *
79
     * @param non-empty-string $mimeTypesText The text from the mime.types file.
80
     */
81 7
    public function __construct(protected string $mimeTypesText) {}
82
83
    /**
84
     * Generate the JSON from the mapCache.
85
     *
86
     * @param bool $minify Whether to minify the generated JSON.
87
     *
88
     * @throws JsonException
89
     *
90
     * @return non-empty-string
91
     */
92 1
    public function generateJson(bool $minify = true): string
93
    {
94 1
        return json_encode($this->generateMapping(), flags: JSON_THROW_ON_ERROR | ($minify ? 0 : JSON_PRETTY_PRINT));
95
    }
96
97
    /**
98
     * Read the given mime.types text and return a mapping compatible with the MimeTypes class.
99
     *
100
     * @return MimeTypeMap The mapping.
101
     */
102 7
    public function generateMapping(): array
103
    {
104 7
        if ($this->mapCache !== []) {
105 1
            return $this->mapCache;
106
        }
107
108 7
        $lines = explode("\n", $this->mimeTypesText);
109
110 7
        foreach ($lines as $line) {
111 7
            $line = preg_replace('~#.*~', '', $line) ?? $line;
112 7
            $line = trim($line);
113
114 7
            if ($line === '') {
115 7
                continue;
116
            }
117
118 5
            $parts = array_values(array_filter(
119 5
                explode("\t", $line),
120 5
                static fn (string $value): bool => trim($value) !== ''
121 5
            ));
122
123 5
            $this->generateMapCache($parts);
124
        }
125
126 7
        return $this->mapCache;
127
    }
128
129
    /**
130
     * Generates the PHP Enum found in `dist`.
131
     *
132
     * @param non-empty-string $classname
133
     * @param non-empty-string $namespace
134
     *
135
     * @throws RuntimeException
136
     *
137
     * @return string
138
     */
139 5
    public function generatePhpEnum(string $classname = 'MimeType', string $namespace = 'Esi\Mimey'): string
140
    {
141 5
        $values = [
142 5
            '%namespace%'       => $namespace,
143 5
            '%classname%'       => $classname,
144 5
            '%interface_usage%' => $namespace !== __NAMESPACE__ ? ('use ' . MimeTypeInterface::class . ";\n") : '',
145 5
            '%cases%'           => '',
146 5
            '%type2ext%'        => '',
147 5
            '%ext2type%'        => '',
148 5
        ];
149
150 5
        $stubContents = (string) file_get_contents(\dirname(__DIR__, 2) . '/stubs/mimeType.php.stub');
151
152 5
        $mapping = $this->generateMapping();
153
154 5
        $nameMap = [];
155
156 5
        if (!isset($mapping['mimes'], $mapping['extensions'])) {
157 3
            throw new RuntimeException('Unable to generate mapping. Possibly passed malformed $mimeTypesText');
158
        }
159
160 2
        foreach ($mapping['extensions'] as $mime => $extensions) {
161 2
            $nameMap[$mime] = $this->convertMimeTypeToCaseName($mime);
162
163 2
            $values['%cases%'] .= sprintf(Generator::spaceIndent(4, "case %s = '%s';\n"), $nameMap[$mime], $mime);
164 2
            $values['%type2ext%'] .= sprintf(Generator::spaceIndent(12, "self::%s => '%s',\n"), $nameMap[$mime], $extensions[0]);
165
        }
166
167 2
        foreach ($mapping['mimes'] as $extension => $mimes) {
168 2
            $values['%ext2type%'] .= sprintf(Generator::spaceIndent(12, "'%s' => self::%s,\n"), $extension, $nameMap[$mimes[0]]);
169
        }
170
171 2
        return str_replace(
172 2
            array_keys($values),
173 2
            array_values($values),
174 2
            $stubContents
175 2
        );
176
    }
177
178
    /**
179
     * @param string $mimeType
180
     */
181 2
    protected function convertMimeTypeToCaseName(string $mimeType): string
182
    {
183 2
        if ($mimeType !== '') {
184 2
            $mimeType = preg_replace('/([\/\-_+.]+)/', '', ucfirst(ucwords($mimeType, '/-_+.'))) ?? $mimeType;
185
        }
186
187 2
        return $mimeType;
188
    }
189
190
    /**
191
     * Helper function for self::generateMapping().
192
     *
193
     * @param list<string> $parts
194
     */
195 5
    protected function generateMapCache(array $parts): void
196
    {
197 5
        if (\count($parts) === 2) {
198 4
            $mime       = trim($parts[0]);
199 4
            $extensions = explode(' ', $parts[1]);
200
201 4
            foreach ($extensions as $extension) {
202 4
                $extension = trim($extension);
203
204 4
                if ($mime !== '' && $extension !== '') {
205 4
                    $this->mapCache['mimes'][$extension][] = $mime;
206 4
                    $this->mapCache['extensions'][$mime][] = $extension;
207 4
                    $this->mapCache['mimes'][$extension]   = array_unique($this->mapCache['mimes'][$extension]);
208 4
                    $this->mapCache['extensions'][$mime]   = array_unique($this->mapCache['extensions'][$mime]);
209
                }
210
            }
211
        }
212
    }
213
214
    /**
215
     * Helper function for self::generatePhpEnum().
216
     */
217 3
    protected static function spaceIndent(int $spaces, string $string): string
218
    {
219 3
        if ($spaces <= 0) {
220 1
            $spaces = 4;
221
        }
222
223 3
        $spaces += \strlen($string);
224
225 3
        return str_pad($string, $spaces, ' ', STR_PAD_LEFT);
226
    }
227
}
228