|
1 | 1 | // mcpp.dist.web -- the wasm32-emscripten stem family becomes a static |
2 | | -// directory a browser can load, with an `index.html` this member writes. |
| 2 | +// directory a browser can load, with a page this member writes (`index.html` |
| 3 | +// unless `options::page` names another). |
3 | 4 | // |
4 | 5 | // WHY THIS IS NEITHER A RULE NOR A TOOL, AND WHY IT COMPILES NO TRANSLATION |
5 | 6 | // UNIT. Same shape `dist/appimage.cppm` and `dist/apple.cppm` already state: |
@@ -117,6 +118,14 @@ struct options { |
117 | 118 | // `{{title}}` in the template. Empty means `[package] name`. |
118 | 119 | std::string title; |
119 | 120 |
|
| 121 | + // The page's file name inside the produced directory. Empty means |
| 122 | + // `index.html`, what a static file server answers for the directory itself. |
| 123 | + // A bare `*.html` name with no directory component: an Emscripten build |
| 124 | + // through CMake names the page after the target (`<target>.html`), and a |
| 125 | + // project that ships such a page keeps its name (#649 P2). A name with a |
| 126 | + // separator, or without the `.html` extension, is refused at plan time. |
| 127 | + std::string page; |
| 128 | + |
120 | 129 | // Where the produced directory lands. Empty means `<out_dir>/web`. |
121 | 130 | std::string output; |
122 | 131 | std::string out_dir = std::string(mcpp::out_dir()); |
@@ -176,6 +185,25 @@ inline std::string target_for(const options& opt) { |
176 | 185 | return (n && *n) ? std::string(n) : std::string(); |
177 | 186 | } |
178 | 187 |
|
| 188 | +// The page name `options::page` asks for, or the reason it cannot be used. |
| 189 | +// Bare, because the page is written beside the launcher it loads with a |
| 190 | +// relative `<script src>`; a page in a subdirectory would load nothing. |
| 191 | +inline std::expected<std::string, std::string> page_name(const options& opt) { |
| 192 | + if (opt.page.empty()) return std::string("index.html"); |
| 193 | + const std::string& n = opt.page; |
| 194 | + if (n.find('/') != std::string::npos || n.find('\\') != std::string::npos) |
| 195 | + return std::unexpected(std::format( |
| 196 | + "`options::page` names '{}', which has a directory component; the " |
| 197 | + "page is written beside `<name>.js`, so it is a bare file name", n)); |
| 198 | + if (n == ".html" || n.size() <= 5 || !n.ends_with(".html")) |
| 199 | + return std::unexpected(std::format( |
| 200 | + "`options::page` names '{}', which is not a `*.html` file name", n)); |
| 201 | + if (n.starts_with(".")) |
| 202 | + return std::unexpected(std::format( |
| 203 | + "`options::page` names '{}', a hidden file a server does not list", n)); |
| 204 | + return n; |
| 205 | +} |
| 206 | + |
179 | 207 | inline std::string replace_all_copy(std::string s, std::string_view from, std::string_view to) { |
180 | 208 | if (from.empty()) return s; |
181 | 209 | std::size_t pos = 0; |
@@ -261,6 +289,14 @@ inline plan plan_for(options opt = {}) { |
261 | 289 | return p; |
262 | 290 | } |
263 | 291 |
|
| 292 | + const auto pageFile = page_name(opt); |
| 293 | + if (!pageFile) { |
| 294 | + std::cerr << "mcpp.dist.web: " << pageFile.error() << '\n'; |
| 295 | + mcpp::warning(("mcpp.dist.web: " + pageFile.error()).c_str()); |
| 296 | + p.reason = "page name refused"; |
| 297 | + return p; |
| 298 | + } |
| 299 | + |
264 | 300 | if (!opt.template_file.empty()) { |
265 | 301 | const std::string tpl = |
266 | 302 | (std::filesystem::path(mcpp::manifest_dir()) / opt.template_file).string(); |
@@ -291,11 +327,14 @@ inline plan plan_for(options opt = {}) { |
291 | 327 | const std::string webDir = !opt.output.empty() ? opt.output |
292 | 328 | : (std::filesystem::path(opt.out_dir) / "web").string(); |
293 | 329 |
|
| 330 | + // The side file carries the page name, so two packs of one program with |
| 331 | + // different pages do not overwrite each other's rendering; the default |
| 332 | + // keeps the name 0.11 wrote (`<target>-index.html`). |
294 | 333 | const std::string indexSrc = |
295 | | - (std::filesystem::path(opt.out_dir) / (target + "-index.html")).string(); |
| 334 | + (std::filesystem::path(opt.out_dir) / (target + "-" + *pageFile)).string(); |
296 | 335 | if (!write_if_different(indexSrc, templateBytes)) { |
297 | 336 | std::cerr << std::format("mcpp.dist.web: cannot write {}", indexSrc) << '\n'; |
298 | | - p.reason = "cannot write index.html"; |
| 337 | + p.reason = std::format("cannot write {}", *pageFile); |
299 | 338 | return p; |
300 | 339 | } |
301 | 340 |
|
@@ -363,9 +402,9 @@ inline plan plan_for(options opt = {}) { |
363 | 402 | page.role = "artifact"; |
364 | 403 | page.description = "INDEX.HTML"; |
365 | 404 | page.argv = { "${mcpp.self}", "stage", "--verify", "content", "--output", |
366 | | - webDir + "/index.html", indexSrc }; |
| 405 | + webDir + "/" + *pageFile, indexSrc }; |
367 | 406 | page.inputs = { indexSrc }; |
368 | | - page.outputs = { webDir + "/index.html" }; |
| 407 | + page.outputs = { webDir + "/" + *pageFile }; |
369 | 408 | p.steps.push_back(std::move(page)); |
370 | 409 |
|
371 | 410 | p.applies = true; |
|
0 commit comments