packages/sql-parser/src/Resource/ResourceWriter.php

1<?php
2
3declare(strict_types=1);
4
5namespace SqlParser\Resource;
6
7use RuntimeException;
8use SqlParser\Table\ParseTable;
9use SqlParser\Table\TableFile;
10
11/**
12 * Writes the generated resources of one release where the registry expects them.
13 *
14 * @visibility root
15 */
16final class ResourceWriter
17{
18    /**
19     * @param TableFile $tables Stores parse tables
20     */
21    public function __construct(private readonly TableFile $tables = new TableFile())
22    {
23    }
24
25    /**
26     * Writes the parse table of a release.
27     *
28     * @param SqlVersion $version The release
29     * @param ParseTable $table Its table
30     *
31     * @throws RuntimeException When the file cannot be written
32     */
33    public function writeTable(SqlVersion $version, ParseTable $table): void
34    {
35        $this->ensureDirectory($version->tablePath);
36        $this->tables->save($table, $version->tablePath);
37    }
38
39    /**
40     * Writes the keyword table of a release as a PHP file returning an array.
41     *
42     * @param SqlVersion $version The release
43     * @param array<string, array<string, string>> $keywords Terminal name by spelling, by group
44     * @param string $origin Where the keywords were read from
45     *
46     * @throws RuntimeException When the file cannot be written
47     */
48    public function writeKeywords(SqlVersion $version, array $keywords, string $origin): void
49    {
50        $this->ensureDirectory($version->keywordPath);
51        $body = "<?php\n\ndeclare(strict_types=1);\n\n/**\n * Keyword tokens of {$version->name}, generated from {$origin}.\n *\n * @return array<string, array<string, string>>\n */\nreturn " . $this->export($keywords, 0) . ";\n";
52        if (file_put_contents($version->keywordPath, $body) === false) {
53            throw new RuntimeException("Cannot write keyword table to {$version->keywordPath}");
54        }
55    }
56
57    /**
58     * Renders a nested array of strings as PHP source, one entry per line.
59     *
60     * @param array<string, array<string, string>|string> $values Values to render
61     * @param int $indent Nesting depth
62     *
63     * @return string PHP source of the array
64     */
65    public function export(array $values, int $indent): string
66    {
67        $pad = str_repeat('    ', $indent + 1);
68        $lines = ['['];
69        foreach ($values as $key => $value) {
70            $rendered = is_array($value) ? $this->export($value, $indent + 1) : var_export($value, true);
71            $lines[] = $pad . var_export($key, true) . ' => ' . $rendered . ',';
72        }
73        $lines[] = str_repeat('    ', $indent) . ']';
74
75        return implode("\n", $lines);
76    }
77
78    /**
79     * Makes sure the directory of a path exists.
80     *
81     * @param string $path File about to be written
82     *
83     * @throws RuntimeException When the directory cannot be made
84     */
85    public function ensureDirectory(string $path): void
86    {
87        $directory = dirname($path);
88        if (!is_dir($directory) && !mkdir($directory, 0777, true) && !is_dir($directory)) {
89            throw new RuntimeException("Cannot create {$directory}");
90        }
91    }
92}
93