Skip to content

Commit c804312

Browse files
committed
dist-web: options::page names the page (#649 P2)
1 parent d6bee6a commit c804312

2 files changed

Lines changed: 72 additions & 6 deletions

File tree

dist/web.cppm

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// 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).
34
//
45
// WHY THIS IS NEITHER A RULE NOR A TOOL, AND WHY IT COMPILES NO TRANSLATION
56
// UNIT. Same shape `dist/appimage.cppm` and `dist/apple.cppm` already state:
@@ -117,6 +118,14 @@ struct options {
117118
// `{{title}}` in the template. Empty means `[package] name`.
118119
std::string title;
119120

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+
120129
// Where the produced directory lands. Empty means `<out_dir>/web`.
121130
std::string output;
122131
std::string out_dir = std::string(mcpp::out_dir());
@@ -176,6 +185,25 @@ inline std::string target_for(const options& opt) {
176185
return (n && *n) ? std::string(n) : std::string();
177186
}
178187

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+
179207
inline std::string replace_all_copy(std::string s, std::string_view from, std::string_view to) {
180208
if (from.empty()) return s;
181209
std::size_t pos = 0;
@@ -261,6 +289,14 @@ inline plan plan_for(options opt = {}) {
261289
return p;
262290
}
263291

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+
264300
if (!opt.template_file.empty()) {
265301
const std::string tpl =
266302
(std::filesystem::path(mcpp::manifest_dir()) / opt.template_file).string();
@@ -291,11 +327,14 @@ inline plan plan_for(options opt = {}) {
291327
const std::string webDir = !opt.output.empty() ? opt.output
292328
: (std::filesystem::path(opt.out_dir) / "web").string();
293329

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`).
294333
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();
296335
if (!write_if_different(indexSrc, templateBytes)) {
297336
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);
299338
return p;
300339
}
301340

@@ -363,9 +402,9 @@ inline plan plan_for(options opt = {}) {
363402
page.role = "artifact";
364403
page.description = "INDEX.HTML";
365404
page.argv = { "${mcpp.self}", "stage", "--verify", "content", "--output",
366-
webDir + "/index.html", indexSrc };
405+
webDir + "/" + *pageFile, indexSrc };
367406
page.inputs = { indexSrc };
368-
page.outputs = { webDir + "/index.html" };
407+
page.outputs = { webDir + "/" + *pageFile };
369408
p.steps.push_back(std::move(page));
370409

371410
p.applies = true;

tests/web-consumer/check-web-plan.sh

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,32 @@ out=$(node "$WEB/web-consumer.js")
7373
[ "$out" = "1-2-3" ] || fail "node did not print 1-2-3 (got: $out)"
7474
echo "ok: node $WEB/web-consumer.js prints 1-2-3"
7575

76+
echo "== options::page names the page, and refuses a name that is not a bare *.html file =="
77+
# A copy of this fixture beside it, so its path dependency on the collection
78+
# (`../..`) still resolves; the copy's build program sets `options::page`.
79+
PAGE_FIXTURE="../.web-consumer-page"
80+
rm -rf "$PAGE_FIXTURE"
81+
mkdir -p "$PAGE_FIXTURE"
82+
cp -r mcpp.toml build.mcpp src "$PAGE_FIXTURE"/
83+
sed -i.bak 's| opt.target = "web-consumer";| opt.target = "web-consumer";\n opt.page = "web-consumer.html";|' "$PAGE_FIXTURE/build.mcpp"
84+
grep -q 'opt.page = "web-consumer.html"' "$PAGE_FIXTURE/build.mcpp" \
85+
|| fail "the named-page fixture was not written" "$PAGE_FIXTURE/build.mcpp"
86+
( cd "$PAGE_FIXTURE" && "$MCPP" pack --format web --target wasm32-emscripten > pack.log 2>&1 ) \
87+
|| fail "mcpp pack --format web with options::page failed" "$PAGE_FIXTURE/pack.log"
88+
PWEB=$(find "$PAGE_FIXTURE/target" -type d -name web | head -1)
89+
[ -f "$PWEB/web-consumer.html" ] || fail "options::page = web-consumer.html produced no such page" "$PAGE_FIXTURE/pack.log"
90+
[ -e "$PWEB/index.html" ] && fail "options::page named another page, and index.html was written as well"
91+
grep -q 'web-consumer.js' "$PWEB/web-consumer.html" || fail "the named page does not load the launcher" "$PWEB/web-consumer.html"
92+
echo "ok: options::page = web-consumer.html writes that page and no index.html"
93+
94+
sed -i.bak 's| opt.page = "web-consumer.html";| opt.page = "pages/web-consumer.html";|' "$PAGE_FIXTURE/build.mcpp"
95+
if ( cd "$PAGE_FIXTURE" && "$MCPP" pack --format web --target wasm32-emscripten > refuse.log 2>&1 ); then
96+
fail "options::page = pages/web-consumer.html was accepted" "$PAGE_FIXTURE/refuse.log"
97+
fi
98+
grep -q "has a directory component" "$PAGE_FIXTURE/refuse.log" \
99+
|| fail "the refusal does not say why the page name was refused" "$PAGE_FIXTURE/refuse.log"
100+
echo "ok: a page name with a directory component is refused, and the refusal says why"
101+
rm -rf "$PAGE_FIXTURE"
102+
76103
rm -f build.log pack.log
77-
echo "PASS: dist-web produces a static directory, node runs it, 1-2-3"
104+
echo "PASS: dist-web produces a static directory, node runs it, 1-2-3; options::page names the page"

0 commit comments

Comments
 (0)