| App::FuguWeb::Config(3p) | Perl Library Manual | App::FuguWeb::Config(3p) |
NAME
App::FuguWeb::Config - the site description over Fugu::Config
SYNOPSIS
use App::FuguWeb::Config;
my $config = App::FuguWeb::Config->load(error => \my $reason)
or die "$reason\n";
say $config->site;
say $_->{href} for $config->nav;
say $_->{file} for $config->pages;
DESCRIPTION
The class reads .fuguwebrc, applies the defaults, and validates the result. The grammar, the quoting, and the yes/no spellings come from Fugu::Config; this class holds what is true of a site.
The object is immutable once loaded, and the module keeps no package state. Two sites in one process therefore share nothing.
Translations
A block of one argument is the default. A block of two is a translation: the first argument is the code of its language, and the second names the default block that it translates.
page de "index.html" {
title = Start
body = de/index.body.html
}
A "page", a "nav", a "manuals" and a "modules" block can take a translation. A translation of a "page" takes "title", "body" and "markdown". A translation of a "nav" takes "label". A translation of a group takes "heading" and "dir".
A page of a language takes each value of its translation, and the value of the default block where the translation holds none. The merged page obeys the rules of a default page. Thus a translation that names "markdown" for a page with a "body" fails, because the page then names two sources.
METHODS
load
App::FuguWeb::Config->load(
root => $dir, # default: discover
error => \my $reason,
)
Read and validate the description. The method returns the object, or "undef" with the reason in $reason.
Without "root", the method walks up from the working directory to the first directory that holds .fuguwebrc, as "fuguvm" finds .fuguvmrc.
The reason travels through a reference because the object that would hold it does not exist when the load fails.
A load fails when the file is absent, when a line does not parse, or when the description would make the build do something it must not. Every message names the file, and the block when a block is at fault.
The description is rejected when:
- there is no "site" setting;
- a "page" block names no source, or more than one. An "index" page can take a "body" as well, which opens it;
- two "page" blocks name the same file, or two manuals would become the same page;
- a "nav" block has no label;
- a translation names a language code that is not two lowercase letters, or names "en", the default language;
- a translation takes more than two arguments, names no default block of its type, or comes twice for one name and one language;
- a translation names a setting outside the set of its type;
- a "keys" block or a "key" block takes a translation;
- a group block names no "heading", or its name is not one word of lowercase letters, digits and hyphens, or two groups share one name;
- a group block sets "anchor", because the block name is the anchor. The message names the block to write in its place;
- a source of the "dir" of a group translation has no namesake in the default "dir";
- a path setting steps out of the project with a ".." component;
- a "page" block name is absolute or steps out of the output directory: the name becomes a file there, so it is a path and gets the same guard the sources get;
- a "manuals" namespace holds a path separator: it prefixes a manual name, and that name becomes both the staged file and the published page;
- a "modules" directory is not below "module_root", which would leave the module with no name to take but its whole path;
- a yes/no setting holds something that is neither;
- the top level of the source directory holds a symlink, or the path of a fragment that the build reads holds one. The rule holds for the file, and for each directory between the source directory and the file. Every file at the top level that the build does not render is copied into the site. The fragments are the body fragment of each page in each language, the footer, and the footer of each language. A symlink would publish whatever it points at, from anywhere on the machine.
A "keys" block adds its own rules. The description is rejected when:
- two "keys" blocks name one directory, or one "org" word, or a "key" block stands with no "keys" block;
- the "keys" block names no "org", or an "org" that no key name can hold;
- the "keys" name is not one segment below the source directory, names a directory of the path, names the staging directory of the build, takes the shape of a language code, or names a file that the site already holds;
- the "keys" block names a "contact" without an "expires", or the other way round, or an "expires" that RFC 3339 does not hold;
- the "keys" block names a "url" that is not absolute;
- the key directory holds no "SHA256", no "SHA256.sig", or a symlink;
- a "key" block names no file, more than one file, or a setting that no block defines;
- a "key" block names a status outside "current", "next" and "retired", or a second block declares the same key;
- a "key" block names a setting that its type does not take: an "email" or a "fingerprint" on a signify key, and an "email" on a certificate;
- an "email" is not a local part and a domain, or a "fingerprint" does not hold the width of its type. The fingerprint of an OpenPGP key holds 40 hexadecimal characters, and the SHA-256 of the DER of a certificate holds 64.
App::FuguWeb::Keys documents the blocks and the published tree.
root, path
The project root, and the .fuguwebrc that this object read.
source_path
my $dir = $config->source_path;
my $file = $config->source_path('footer.body.html');
The source directory, or one file in it.
site, out_dir, source_dir, entry, module_root, mandoc_os, man_url, stylesheet
The settings. "site" is the only one a project must give.
site (required) the name in the title of every page,
and the text of the header link
out_dir web/build where the build writes
source_dir web the fragments and the assets
entry index.html the front page of each tree, and the
header link
module_root lib the prefix a module name drops
(a trailing slash is dropped)
mandoc_os OpenBSD the mandoc -I os=, which pins the footer
man_url https://man.openbsd.org/ where a remote .Xr goes
stylesheet (searched) the base stylesheet
"stylesheet" overrides the search that "share_path" in Fugu::File does. The search finds the sheet in a checkout and in an installed App-FuguWeb distribution alike, so most projects never set it.
The "pod2man" "--center" and "--release" values are not settings. They are constants of App::FuguWeb::Render: they pin the "pod2man" output so the site does not vary with the build host.
nav
my @nav = $config->nav('de');
The navigation of one language, in file order. Each entry is a hashref with "href" and "label". The code defaults to "en", and an "href" is the same in each language.
pages
my @pages = $config->pages('de');
The pages of one language, in file order. The code defaults to "en". Each entry is a hashref:
file the name of the page in each language tree
title the title, which defaults to the file name
source one of body, markdown or index
value what the source names; for index, the body
that opens the page, or undef
unlinked true when no other page links to it
lang the language of the value: the code of the
page when its translation names the value,
and en otherwise
title_lang the language of the title, by the same rule
groups
The manual groups, in file order. Each entry is an "App::FuguWeb::Config::Group", and the two block types interleave as the description wrote them.
languages
my @codes = $config->languages;
The code of each language of the site, the default first. Each one names a tree of the output. The list holds "en", and then each code that a translation names, in byte order.
page_names
The name of each page of one language tree: the pages of the description, then one page for each manual of each group. Each tree holds the same names.
top_pages
The pages of the top level: index.html, which forwards to the entry page of the default tree, and 404.html when the description holds that page.
inventory
Every path that the output directory must hold after a build: each page of each language tree, the pages of the top level, the stylesheet, the assets, and each key directory. The build and the checks read the same list, so the two can never disagree about what the site holds.
A page of a tree is "<code>/<page>", for example en/manuals.html. The pages of the top level, the stylesheet and the assets are file names. A key directory is one part below the root, so its entries hold a solidus.
keys_dirs
The name of each key directory, in file order. The list is empty when the description holds no "keys" block, so it is the test for one.
A description can hold several "keys" blocks, and each one names one directory. Each directory holds its own root of trust, per D-02, and a binding never crosses a directory. Two blocks must not name one directory.
keys_org, keys_contact, keys_expires, keys_url
my $org = $config->keys_org('keys');
The settings of one "keys" block. Each accessor takes the name of the directory, and each one answers "undef" for a name that no block holds.
keys_org (required) the first field of every key name
keys_contact the Contact field of security.txt
keys_expires the Expires field of security.txt
keys_url the published prefix of the directory,
with no trailing slash
RFC 9116 makes "Contact" and "Expires" both necessary, so "contact" and "expires" stand or fall together. The "Encryption" field needs an absolute URL, so a block that names no "url" writes no such field.
A site publishes one security.txt, so one block alone names the contact and the expiry. A second block that names either one fails the load.
keys_path
my $dir = $config->keys_path('keys');
my $file = $config->keys_path( 'keys', 'SHA256' );
One source key directory, or one file in it. The method answers "undef" for a name that no "keys" block holds.
site_keys
my @key = $config->site_keys('keys');
The "key" blocks of one key directory, in file order. The directory that holds the file owns the block, as the extension of that file owns the type. A stem that two directories hold fails the load.
Each entry is a hashref:
name the key file in the directory
stem the block name, which is the name without the
extension
type signify, openpgp or x509, from the extension
serial the rotation count of the purpose
purpose what the key signs
status current, next or retired
since the date the key took its status, or undef
until the date it left it, or undef
email the address of an OpenPGP key, or undef
wkd the Web Key Directory hash of that address
fingerprint the declared fingerprint, in upper case, or undef
The list is named "site_keys" and not "keys", because a method named "keys" in the package makes every call of the builtin ambiguous.
site_bindings
my @binding = $config->site_bindings('keys');
The binding files of one key directory, in name order. A binding is the signature of one key file by another key of the same directory. Each entry is a hashref:
name the binding file in the directory
target the key file that the signature covers
signer the key file of the key that signed it
type the type of the signer
A binding carries no block: its name holds every field, and Fugu::KeyDir parses it. The list holds a binding whose target and whose signer are both keys of that directory, and App::FuguWeb::Keys reports every other name as a stray file.
key_paths
Every path of every key directory in the output, relative to the output directory, or the empty list when the description holds no "keys" block. "inventory" holds the same paths.
The site holds one well-known tree, and each directory writes into it. The list therefore holds one entry for the policy file, and one for an address that two directories name.
GROUPS
A "manuals" or "modules" block becomes one group. A group never holds a list of manuals: it reads its directory, so a manual that is added reaches the site with no edit anywhere.
A group whose directory does not exist fails the load and names the directory. A silent empty group hides a typo in a path, and a rename that nothing catches is what this file exists to prevent.
kind, name, namespace
"kind" is "manuals" or "modules". "name" is the block name, which becomes the "id" of the "<h2>". Every language shares it.
heading
my $text = $group->heading('de');
The text of the "<h2>" in one language: the heading of the translation, or the default heading. The code defaults to "en".
heading_lang
my $code = $group->heading_lang('de');
The language of that text: the code when the translation names a heading, and "en" when the group takes the default heading. The code defaults to "en". The index marks a heading of another language than the page, per "body" in App::FuguWeb::Index.
translate
$group->translate( 'de', heading => $text, dir => $path );
Give the group the heading and the directory of one language. A "heading" of "undef" keeps the default heading, and a "dir" of "undef" keeps the default sources for that language. The description calls it once for each translation of the block.
manuals
my @manuals = $group->manuals('de');
The manuals of the group in one language, in the order the index shows them, as App::FuguWeb::Manual objects. The code defaults to "en". The method reads each directory once and keeps the answer.
A translation "dir" mirrors the default "dir". The namesake of a source is the source of the same path below the other directory, and the umbrella of a "modules" group is the namesake of the umbrella. A manual of a language comes from its namesake when the translation "dir" holds one, and from the default source otherwise. It keeps the name and the page of the default source, so each language holds the same pages.
A "manuals" group reads its directory for *.1, *.3p, *.5, *.7 and *.8, and takes a plain file only. It does not glob: Perl's "glob" splits its pattern on whitespace and reads "[ ] { } ? ~", so a project whose path holds one of them would lose its manuals or collect a sibling directory's. It sorts by section, in the order 1, 3p, 5, 7, 8, and then by file name. The "namespace" setting prefixes the name and the staged file name, so man/fugu/Daemon.3p becomes Fugu::Daemon(3p).
A "modules" group finds every .pod file below its directory, and the sidecar that names the directory itself: lib/App/FuguWeb.pod is the umbrella of lib/App/FuguWeb/. It sorts by path, so Store.pod stays before Store/Memory.pod.
Both sorts compare bytes and never read the locale of the builder. A site must not depend on the machine that built it.
Never name a directory that holds more than one namespace. In this repository lib/App is such a directory, and one group there would swallow three namespaces.
orphan
my $path = $group->orphan('de');
The first source of the translation "dir" of one language that has no namesake in the default "dir", or "undef". The description refuses a group with an orphan.
SEE ALSO
App::FuguWeb, App::FuguWeb::Keys, App::FuguWeb::Manual, Fugu::Config, fuguweb(1)
AUTHOR
Dick Olsson <hi@senzilla.io>
| 2026-10-21 | OpenBSD |