Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
CatalogFile
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
8 / 8
19
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 pluralKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 render
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
5
 rows
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 value
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 quote
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 section
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Verba\Tool;
6
7/**
8 * Reads and renders a `<domain>.<locale>.php` catalog for the sync tooling,
9 * exposing the raw message and obsolete sections and the optional plural key.
10 * Rendering is deterministic: sections are key-sorted and values are emitted as
11 * plain single-quoted literals.
12 *
13 * @api
14 */
15final class CatalogFile
16{
17    /**
18     * @param array<string, string|list<string>|null> $messages
19     * @param array<string, string|list<string>|null> $obsolete
20     */
21    public function __construct(
22        public array $messages,
23        public array $obsolete,
24        public ?string $plural,
25    ) {}
26
27    public static function load(string $file): self
28    {
29        if (!is_file($file)) {
30            return new self([], [], null);
31        }
32
33        /** @var mixed $data */
34        $data = require $file;
35
36        if (!is_array($data)) {
37            return new self([], [], null);
38        }
39
40        return new self(
41            self::section($data['messages'] ?? null),
42            self::section($data['obsolete'] ?? null),
43            self::pluralKey($data['plural'] ?? null),
44        );
45    }
46
47    private static function pluralKey(mixed $plural): ?string
48    {
49        return is_string($plural) ? $plural : null;
50    }
51
52    public function render(): string
53    {
54        $lines = ['<?php', '', 'declare(strict_types=1);', '', 'return ['];
55
56        if ($this->plural !== null) {
57            $lines[] = "\t'plural' => " . $this->quote($this->plural) . ',';
58        }
59
60        $lines[] = "\t'messages' => [";
61
62        foreach ($this->rows($this->messages) as $row) {
63            $lines[] = $row;
64        }
65
66        $lines[] = "\t],";
67
68        if ($this->obsolete !== []) {
69            $lines[] = "\t'obsolete' => [";
70
71            foreach ($this->rows($this->obsolete) as $row) {
72                $lines[] = $row;
73            }
74
75            $lines[] = "\t],";
76        }
77
78        $lines[] = '];';
79        $lines[] = '';
80
81        return implode("\n", $lines);
82    }
83
84    /**
85     * @param array<string, string|list<string>|null> $section
86     * @return list<string>
87     */
88    private function rows(array $section): array
89    {
90        ksort($section, SORT_STRING);
91
92        $rows = [];
93
94        foreach ($section as $id => $value) {
95            $rows[] = "\t\t" . $this->quote($id) . ' => ' . $this->value($value) . ',';
96        }
97
98        return $rows;
99    }
100
101    /**
102     * @param string|list<string>|null $value
103     */
104    private function value(string|array|null $value): string
105    {
106        if ($value === null) {
107            return 'null';
108        }
109
110        if (is_string($value)) {
111            return $this->quote($value);
112        }
113
114        return '[' . implode(', ', array_map($this->quote(...), $value)) . ']';
115    }
116
117    private function quote(string $value): string
118    {
119        return "'" . str_replace(['\\', "'"], ['\\\\', "\\'"], $value) . "'";
120    }
121
122    /**
123     * @return array<string, string|list<string>|null>
124     */
125    private static function section(mixed $value): array
126    {
127        if (!is_array($value)) {
128            return [];
129        }
130
131        /** @var array<string, string|list<string>|null> $value */
132        return $value;
133    }
134}