| App::FuguWeb::Site(3p) | Perl Library Manual | App::FuguWeb::Site(3p) |
NAME
App::FuguWeb::Site - the whole build, from the description to the pages
SYNOPSIS
use App::FuguWeb::Site;
my $site = App::FuguWeb::Site->new(
config => $config,
out => 'web/build',
);
unless ($site->build) {
my $missing = $site->missing_tool;
die "$missing not found\n" if defined $missing;
die "the build failed\n";
}
$site->clean;
DESCRIPTION
One method renders the site. Each step reports its own failure and stops the build: a site that is half rendered must not look like a success.
The build reads nothing from the network, writes nothing outside the output directory, and gives the same bytes for the same checkout.
METHODS
new
App::FuguWeb::Site->new(
config => $config, # required
out => $dir, # required: the output directory
log => $logger, # default: Fugu::Log->default
render => $render, # default: one over the same description
)
config, staging
my $staging = $site->staging;
my $english = $site->staging('en');
The site description, and the mdoc staging directory, or the directory of one language inside it.
missing_tool
The renderer that failed the probe of the last "build", or "undef". The build probes once and names the tool in the log; the caller maps this value to its own exit code, so an operator learns which package to install.
build
Render the whole site:
- 1.
- check that every renderer is installed, and name the first one that is not;
- 2.
- refuse an output directory that no build may own, and refuse a symlink on a path that the build writes;
- 3.
- lint every mdoc source of each language;
- 4.
- create the output directory, the directory of each language tree, and the staging directory of each language;
- 5.
- copy the mdoc source of each language into the staging directory of that language, under its staged name. A source of a translation takes the staged name of its namesake;
- 6.
- copy the base stylesheet and every asset;
- 7.
- write the key directory, when the description names one;
- 8.
- render each "page" block into each language tree, from the values of that language, then the pages of the top level: the copy of 404.html when the description holds that page, and the forward index.html;
- 9.
- render one page for each manual in each group, into each language tree, from the source of that language;
- 10.
- remove the staging directory, every file that the site dropped, and every directory that is then empty.
The last step is what makes a second build over the same directory right. A manual that was renamed leaves its old page behind, and a site that carried both would fail its own check. The build owns the output directory, so it owns the removal too.
The build owns a plain file at the top level of the output, and a plain file directly in a directory of the top level whose name has the shape of a language code. A page of an older flat layout, and the tree of a language that the description dropped, are therefore removed. Below the root the build also owns a file that the site names, and one that takes a shape of the key directory. It keeps every other entry and reports it. An operator directory in the output therefore survives a build, empty or not, unless it sits at the top level and its name has the shape of a language code. The build takes such a directory for a language tree: it removes each plain file directly in it that the site does not name, and then the directory itself once it is empty. An operator directory of that name below the top level, such as img/de, is no language tree, so it survives.
A description with no "keys" block owns no shape of the key directory and no directory of the key tree. A site of another maker holds .well-known/security.txt too, and the clean must not delete that site.
The build removes an empty directory that it owns: one that the site holds a name below, one of the key tree, and a language tree. The output directory itself always stays. The clean reads the same three tests, so a build can never remove a directory that the clean refuses. "rmdir" refuses a directory that still holds a name, so the walk needs no second test.
The method returns true on success, and "undef" with a message in the log otherwise.
The output directory is created on purpose. The recipe that this replaced got it as a side effect of the staging "mkdir", which would have broken the moment the staging step moved.
clean
Remove the output directory. The method returns true when the directory is gone, whether or not it was there to begin with.
"clean" removes a built site and refuses anything else. It deletes a tree without asking, so the one thing it must never do is delete a tree that no build made.
A plain file of the top level is one that a build writes, whatever the description names today. So is a plain file directly in a language tree: a renamed manual leaves its old page there. A file below the root must be one that the site names, or one of the shapes that the key directory takes. A directory must hold a name of the site, be a language tree, or be a directory of the key tree. A symlink is never a thing that a build wrote. The clean refuses every other entry, and it names it.
The clean and the prune read one predicate, so a build can never remove a file that the clean refuses.
The staging directory is a build detail, and the build writes one flat directory of sources into it for each language. A tree of another shape belongs to whoever made it.
With "--out" the description still names the key directory, so "clean" reads it when it can. A description that does not load names nothing. The files of the top level and the language trees, which the clean reads by shape, are then the whole site that "clean" knows.
That fallback is weak, so "clean" asks for one more thing: the target must hold the stylesheet. Every build writes it, so a target without it is the output of no build. The target guard is the first line, and this is the second.
A description that does not load still names three things: the source directory, the output directory, and the name and org of its "keys" block. Fugu::Config keeps every setting and block that it read before the fault, so each guard answers for the real directories of that project. A fault above the "keys" block hides it, and "clean" then refuses the key tree of its own output. Remove that tree by hand.
A description that drops its "keys" block strands the published tree. The site names nothing there any more, so the build keeps it, "clean" refuses it and the checks report it. Remove the directory, or name the block again.
THE OUTPUT DIRECTORY
Both "build" and "clean" refuse a target that no build may own. They refuse the root of the filesystem, the home directory, the project root, and any directory that holds the project. They refuse the source directory, and every directory of it. "--out" reaches both commands, and the "out_dir" setting it overrides is checked in the description, so the guard has to sit here as well.
A description that names neither directory takes the two defaults.
The source directory needs the rule because it reads like a built site: plain files of the top level. The key directory is the worst case. Each key file sits at its top level, where the clean takes a plain file, so a clean of it would take the trust anchor of every release.
The output directory is the one exception below the source, and it needs to be: the default output directory is web/build, and it sits inside the default source directory web. The description names that path, so a build owns it. Nothing else below the source is a build's.
The rule is deliberately not "inside the project". The tests and the CI both build into a temporary directory outside it.
A ".." in the target is resolved before the comparison. "File::Spec->canonpath" leaves it alone by design, and a guard that compared the uncollapsed form would let "<project>/.." through as a directory it had never seen.
pod_date
The date that every POD page carries: the date of the last commit, and today when git does not answer. git does not preserve file times, so a build that read one would give different bytes on every checkout.
THE STAGING DIRECTORY
"mandoc" decides between a local link and a link to the manual host by looking for a file named "%N.%S" in its working directory. Every mdoc source is therefore copied into one directory for each language under its "staged_name" in App::FuguWeb::Manual, and "mandoc" runs there.
The directory lives inside the output directory, under .man/<code>, and the build removes it. A plain file directly in .man, or a directory there of another name, is somebody else's, and the build refuses it. Staging is a build detail: a published tree that carried it would serve the mdoc sources beside the pages made from them.
THE KEY DIRECTORY
A description with a "keys" block publishes the public keys of an organization. The build copies each key file and the manifest pair as they stand. It generates the "KEYS" file, the human page, the Web Key Directory tree and security.txt. App::FuguWeb::Keys holds the wiring, and the Fugu modules hold every generic part.
The site build cannot sign, so the manifest pair is a source file. A build that signed would prove that the builder holds the key, and never that the release does.
A description with no "keys" block writes no key directory, so every site that predates it keeps its output.
ASSETS
An asset is any file directly in the source directory that the build does not render: not a "*.body.html" fragment, not Markdown, and not a dot file. robots.txt and CNAME therefore need no entry in the description, and a CLAUDE.md beside them is not published.
Markdown in the source directory is either a page source, which a "page" block names and "lowdown" renders, or notes for the maintainers. Neither belongs in the output as it stands.
THE STYLESHEET
The base stylesheet ships with the tool, at share/fuguweb/style.css, and "share_path" in Fugu::File finds it: in a checkout through this module's location, and under the share tree of an installed App-FuguWeb distribution. The "stylesheet" setting overrides the search.
A stylesheet that is not found fails the build and names the path. A site with no stylesheet must not look like a success.
SEE ALSO
App::FuguWeb, App::FuguWeb::Config, App::FuguWeb::Keys, App::FuguWeb::Render, App::FuguWeb::Page, App::FuguWeb::Index, Fugu::File
AUTHOR
Dick Olsson <hi@senzilla.io>
| 2026-10-21 | OpenBSD |