App::FuguWeb(3p) Perl Library Manual App::FuguWeb(3p)

App::FuguWeb - a static documentation site for a Perl project

    fuguweb build --out web/build
    fuguweb check --out web/build
    use App::FuguWeb;
    my $safe = App::FuguWeb::escape_html($title);
    my $file = App::FuguWeb::CONFIG_FILE;      # .fuguwebrc

"fuguweb" renders one static site from the documentation that a Perl project already keeps: mdoc(7) manuals, POD sidecars, and Markdown. There is no templating language and no JavaScript. The tool runs "mandoc", "lowdown", and "pod2man", and wraps each result in one shared chrome.

A project needs no build recipe. It writes one .fuguwebrc at its root, keeps its body fragments and its assets in one source directory, and runs "fuguweb build". See fuguweb(1) for the subcommands and the exit codes.

This file holds what more than one module in the namespace needs: two constants and four functions, documented below. The rest of the work is split by concern:

The site description: the settings, the navigation, the pages, and the manual groups.
The chrome around one body fragment.
The three external renderers.
One manual source: its path, name, section, page, and description.
The body of the manual index.
The whole build.
The checks over a built site.
Subcommand dispatch over Fugu::CLI.

.fuguwebrc sits at the project root and uses the Fugu::Config grammar: a setting on a line of its own, and a block that opens with a brace at the end of its header line. A "#" starts a comment, and the grammar has no escape for it, so a value may not hold one.

    site       = OpenHAP
    out_dir    = web/build
    source_dir = web
    entry      = index.html
    nav "fugu.html" {
            label = Fugu
    }
    page "install.html" {
            title    = Install
            markdown = INSTALL.md
    }
    manuals "Fugu" {
            dir       = man/fugu
            anchor    = fugu
            namespace = "Fugu::"
    }
    modules "OpenHAP modules" {
            dir    = lib/App/OpenHAP
            anchor = modules
    }

A "page" block names exactly one source: "body" for a fragment in the source directory, "markdown" for a Markdown file, or "index = yes" for the generated manual index. "unlinked = yes" marks a page that no other page links to, such as 404.html.

Three rules keep a list out of the file. A "manuals" block reads its directory. A "modules" block finds every .pod file below its directory, and the sidecar that names the directory itself. Every file in the source directory that the build does not render is an asset, and the build copies it; "ASSETS" in App::FuguWeb::Site says exactly which those are.

App::FuguWeb::Config documents every setting and every default.

    my $safe = App::FuguWeb::escape_html($text);

Escape "&", "<" and ">", in that order. The function takes bytes and returns bytes: no file in the namespace carries "use utf8", so a multi-byte character passes through untouched.

    my $safe = App::FuguWeb::escape_attr($text);

The same, plus the double quote. Use it for a value on its way into a double-quoted attribute: a value that holds a quote ends the attribute early, and everything after it becomes markup. "escape_html" alone does not guard an attribute.

    my $names = App::FuguWeb::list_dir($dir) or die "cannot read: $!";

The names in one directory, sorted, without "." and "..". The function returns an array reference, or "undef" with the reason in $!, so a caller can tell an empty directory from one it cannot read.

The sort compares bytes and never reads the locale of the builder: a site must not depend on the machine that built it.

    App::FuguWeb::path_below($path, $root)

Report whether $path is $root or lies below it. A trailing slash on either does not change the answer. Both paths must be of the same kind: both absolute, or both relative to the same directory.

The name of the configuration file, .fuguwebrc. The name and the discovery match .fuguvmrc.

The name of the stylesheet in the output directory, style.css. The site is served from one flat directory, so every page links it under that name and the build writes it there.

fuguweb(1), Fugu::Config, mandoc(1), lowdown(1), pod2man

Dick Olsson <hi@senzilla.io>

2026-08-23 OpenBSD