packages/sql-catalog/src/Reporter/Html/HtmlReporter.php
1<?php
2
3declare(strict_types=1);
4
5namespace SqlCatalog\Reporter\Html;
6
7use Override;
8use SqlCatalog\Core\Catalog\Catalog;
9use SqlCatalog\Core\Reporter\CatalogArtifacts;
10use SqlCatalog\Core\Reporter\ReporterInterface;
11use SqlCatalog\Reporter\Html\Page\ClassPage;
12use SqlCatalog\Reporter\Html\Page\FileIndexPage;
13use SqlCatalog\Reporter\Html\Page\FilePage;
14use SqlCatalog\Reporter\Html\Page\FindingPage;
15use SqlCatalog\Reporter\Html\Page\NamespacePage;
16use SqlCatalog\Reporter\Html\Page\OverviewPage;
17use SqlCatalog\Reporter\Html\Page\StatementIndexPage;
18use SqlCatalog\Reporter\Html\Page\StatementPage;
19use SqlCatalog\Reporter\Html\Page\TableIndexPage;
20use SqlCatalog\Reporter\Html\Page\TablePage;
21
22/**
23 * Writes the catalog as a site of linked HTML pages.
24 *
25 * A reader comes to a catalog to find a statement and decide something about
26 * it, so the site is laid out as the routes to one: by the table it names, by
27 * the namespace and class that issue it, by the file it is written in, by
28 * what the analysis reported on it, or through the whole listing narrowed
29 * down. Every statement has a page of its own, which is where every route
30 * ends.
31 *
32 * @visibility root
33 */
34final class HtmlReporter implements ReporterInterface
35{
36 /**
37 * Supplies SQL presentation independently of page rendering.
38 */
39 public function __construct(private readonly SqlFormatter $formatter = new SqlFormatter())
40 {
41 }
42
43 /**
44 * The name the page a reader opens first is written under.
45 */
46 public const FILE = ReportSite::INDEX;
47
48 /**
49 * The name the command line selects this reporter by.
50 */
51 #[Override]
52 public function name(): string
53 {
54 return 'html';
55 }
56
57 /**
58 * What the reporter produces.
59 */
60 #[Override]
61 public function description(): string
62 {
63 return 'a site of linked HTML pages, for reading and sharing';
64 }
65
66 /**
67 * The catalog rendered as a site of pages.
68 */
69 #[Override]
70 public function render(Catalog $catalog): CatalogArtifacts
71 {
72 $site = new ReportSite($catalog, formatter: $this->formatter);
73 $shell = new PageShell();
74 $home = ['Overview', ReportSite::INDEX];
75
76 $files = (new ReportAssets())->all();
77 $files[PageShell::INDEX] = (new SearchIndex())->render($site);
78 $files[ReportSite::INDEX] = $shell->render($site, ReportSite::INDEX, 'Overview', [['Overview', null]], (new OverviewPage())->render($site), $shell->onThisPage([
79 ['Needs attention', 'attention'],
80 ['How far the analysis got', 'coverage'],
81 ]));
82 $files[ReportSite::STATEMENTS] = $shell->render($site, ReportSite::STATEMENTS, 'Statements', [$home, ['Statements', null]], (new StatementIndexPage())->render($site));
83 $files[ReportSite::TABLES] = $shell->render($site, ReportSite::TABLES, 'Tables', [$home, ['Tables', null]], (new TableIndexPage())->render($site));
84 $namespaces = new NamespacePage();
85 $files[ReportSite::NAMESPACES] = $shell->render($site, ReportSite::NAMESPACES, 'Namespaces', [$home, ['Namespaces', null]], $namespaces->render($site), $shell->onThisPage($namespaces->anchors($site)));
86 $directories = new FileIndexPage();
87 $files[ReportSite::FILES] = $shell->render($site, ReportSite::FILES, 'Files', [$home, ['Files', null]], $directories->render($site), $shell->onThisPage($directories->anchors($site)));
88 $findings = new FindingPage();
89 $files[ReportSite::FINDINGS] = $shell->render($site, ReportSite::FINDINGS, 'Findings', [$home, ['Findings', null]], $findings->render($site), $shell->onThisPage($findings->anchors($site)));
90
91 return new CatalogArtifacts(
92 array_merge($files, $this->tablePages($site, $shell), $this->classPages($site, $shell), $this->filePages($site, $shell), $this->statementPages($site, $shell)),
93 ReportSite::INDEX,
94 );
95 }
96
97 /**
98 * One page per table the catalog names.
99 *
100 * @return array<string, string>
101 */
102 public function tablePages(ReportSite $site, PageShell $shell): array
103 {
104 $page = new TablePage();
105 $pages = [];
106 foreach ($site->tables() as $table) {
107 $name = $site->tablePage($table);
108 $label = (new TableName($table))->label();
109 $pages[$name] = $shell->render(
110 $site,
111 $name,
112 $label,
113 [['Overview', ReportSite::INDEX], ['Tables', ReportSite::TABLES], [$label, null]],
114 $page->render($site, $table),
115 $page->context($site, $table),
116 );
117 }
118
119 return $pages;
120 }
121
122 /**
123 * One page per class that issues a statement.
124 *
125 * @return array<string, string>
126 */
127 public function classPages(ReportSite $site, PageShell $shell): array
128 {
129 $page = new ClassPage();
130 $pages = [];
131 foreach ($site->classes() as $class) {
132 $name = $site->classPage($class);
133 $pages[$name] = $shell->render(
134 $site,
135 $name,
136 $class,
137 [['Overview', ReportSite::INDEX], ['Namespaces', ReportSite::NAMESPACES], [Scope::of($class . '::x')->classShort() ?? $class, null]],
138 $page->render($site, $class),
139 $page->context($site, $class),
140 );
141 }
142
143 return $pages;
144 }
145
146 /**
147 * One page per file a statement is written in.
148 *
149 * @return array<string, string>
150 */
151 public function filePages(ReportSite $site, PageShell $shell): array
152 {
153 $page = new FilePage();
154 $pages = [];
155 foreach ($site->files() as $file) {
156 $name = $site->filePage($file);
157 $pages[$name] = $shell->render(
158 $site,
159 $name,
160 $file,
161 [['Overview', ReportSite::INDEX], ['Files', ReportSite::FILES], [$file, null]],
162 $page->render($site, $file),
163 $page->context($site, $file),
164 );
165 }
166
167 return $pages;
168 }
169
170 /**
171 * One page per statement.
172 *
173 * @return array<string, string>
174 */
175 public function statementPages(ReportSite $site, PageShell $shell): array
176 {
177 $page = new StatementPage();
178 $pages = [];
179 foreach ($site->index()->entries() as $entry) {
180 $name = $site->statementPage($entry->id);
181 $pages[$name] = $shell->render(
182 $site,
183 $name,
184 strtoupper($entry->kind->value) . ' at ' . $entry->site->display(),
185 [['Overview', ReportSite::INDEX], ['Statements', ReportSite::STATEMENTS], [$entry->id, null]],
186 $page->render($site, $entry),
187 $page->context($site, $entry),
188 );
189 }
190
191 return $pages;
192 }
193}
194