Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
Sync
100.00% covered (success)
100.00%
44 / 44
100.00% covered (success)
100.00%
5 / 5
18
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
 run
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 reconcile
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
7
 park
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
6
 write
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Verba\Tool;
6
7/**
8 * Reconciles a domain's catalog files against the current source: fresh ids are
9 * added as untranslated, existing translations are kept, a reappearing id is
10 * restored from the obsolete section, and a vanished id is parked there (unless
11 * pruning). Rewrites are deterministic, so a second run changes nothing.
12 *
13 * @api
14 */
15final class Sync
16{
17    public function __construct(
18        private readonly Domain $domain,
19        private readonly bool $prune = false,
20    ) {}
21
22    public function run(): SyncReport
23    {
24        $extraction = new Extractor($this->domain)->extract();
25        $fresh = $extraction['messages'];
26
27        $locales = [];
28
29        foreach ($this->domain->locales as $locale) {
30            $locales[$locale] = $this->reconcile($locale, $fresh);
31        }
32
33        return new SyncReport($this->domain->name, $locales, $extraction['warnings']);
34    }
35
36    /**
37     * @param array<string, Message> $fresh
38     * @return array{added: int, obsolete: int, total: int, changed: bool}
39     */
40    private function reconcile(string $locale, array $fresh): array
41    {
42        $file = $this->domain->file($locale);
43        $catalog = CatalogFile::load($file);
44
45        $messages = [];
46        $added = 0;
47
48        foreach (array_keys($fresh) as $id) {
49            if (array_key_exists($id, $catalog->messages)) {
50                $messages[$id] = $catalog->messages[$id];
51            } elseif (array_key_exists($id, $catalog->obsolete)) {
52                $messages[$id] = $catalog->obsolete[$id];
53            } else {
54                $messages[$id] = null;
55                $added++;
56            }
57        }
58
59        $obsolete = $this->prune ? [] : $this->park($catalog, $fresh);
60
61        $next = new CatalogFile($messages, $obsolete, $catalog->plural);
62        $rendered = $next->render();
63        $changed = !is_file($file) || file_get_contents($file) !== $rendered;
64
65        if ($changed) {
66            $this->write($file, $rendered);
67        }
68
69        return [
70            'added' => $added,
71            'obsolete' => count($obsolete),
72            'total' => count($messages),
73            'changed' => $changed,
74        ];
75    }
76
77    /**
78     * @param array<string, Message> $fresh
79     * @return array<string, string|list<string>|null>
80     */
81    private function park(CatalogFile $catalog, array $fresh): array
82    {
83        $obsolete = [];
84
85        foreach ($catalog->messages as $id => $value) {
86            if (array_key_exists($id, $fresh)) {
87                continue;
88            }
89
90            $obsolete[$id] = $value;
91        }
92
93        foreach ($catalog->obsolete as $id => $value) {
94            if (array_key_exists($id, $fresh) || array_key_exists($id, $obsolete)) {
95                continue;
96            }
97
98            $obsolete[$id] = $value;
99        }
100
101        return $obsolete;
102    }
103
104    private function write(string $file, string $contents): void
105    {
106        $dir = dirname($file);
107
108        if (!is_dir($dir)) {
109            mkdir($dir, 0o755, true);
110        }
111
112        file_put_contents($file, $contents);
113    }
114}