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
The site description, and the mdoc staging directory.
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:
check that every renderer is installed, and name the first one that is not;
refuse an output directory that no build may own, and refuse a symlink on a path that the build writes;
lint every mdoc source;
create the output directory and the staging directory inside it;
copy every mdoc source into the staging directory under its staged name;
copy the base stylesheet and every asset;
write the key directory, when the description names one;
render each
pageblock;render one page for each manual in each group;
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. Below the root it 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.
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, and one of the key tree. The output directory itself always stays. The clean reads the same two 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. 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, 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. A tree below it 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, and one flat directory of files is 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: a flat directory of files. 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 under its "staged_name" in App::FuguWeb::Manual, and mandoc runs there.
The directory lives inside the output directory, under .man, and the build removes 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>