bash-doxygen
September 16, 2026 ยท View on GitHub
bash-doxygen is a documentation-led Doxygen filter for Bash. It converts
Doxygen-style Bash comments and the Bash declaration that follows them into a
small pseudo-C++ representation that Doxygen can index.
The filter is intentionally conservative about what it documents: a function or variable is only emitted when it is decorated with a Doxygen comment block. Undocumented helper functions and implementation details are ignored.
The maintained implementation lives in a single portable AWK file:
./doxygen-bash.awk
Comment style
The primary supported style is a contiguous block of ## comments immediately
before the declaration being documented:
## @brief Read a file from disk.
## @details
## The caller is responsible for validating the path before calling this
## function.
## @param path File path to read.
## @returns 0 on success; non-zero otherwise.
read_file() {
cat -- "\$1"
}
For variables, use @var when you want the filter to validate that the comment
matches the declaration:
## @var CACHE_DIR
## @brief Directory used for cached data.
readonly CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/example"
The filter preserves normal Doxygen commands such as @brief, @details,
@param, @returns, @retval, @note, @warning, @see, and custom aliases.
It only interprets a small structural subset: @file, @fn, @namespace,
@module, @package, @var, and @param names.
Manual usage
Run the maintained filter directly with AWK:
awk -f ./doxygen-bash.awk ./script.bash > ./script.dox.cpp
The generated file is not intended to be compiled. It is a Doxygen-friendly intermediate representation.
You can also make the filter executable:
chmod +x ./doxygen-bash.awk
./doxygen-bash.awk ./script.bash > ./script.dox.cpp
Options
The filter accepts simple command-line options before file names:
awk -f ./doxygen-bash.awk -- --strict ./script.bash > ./script.dox.cpp
awk -f ./doxygen-bash.awk -- --compact ./script.bash > ./script.dox.cpp
--strict exits with a non-zero status when the filter emits a diagnostic.
This is useful in CI when documentation drift should fail the build.
--compact suppresses blank placeholder lines in the generated output. By
default, blank lines are emitted for ignored source lines so Doxygen diagnostics
remain closer to the original Bash source locations.
Doxyfile usage
Use the filter with Doxygen by adding it to FILTER_PATTERNS:
FILTER_PATTERNS = *.sh=./doxygen-bash.awk \
*.bash=./doxygen-bash.awk
A minimal configuration might look like this:
PROJECT_NAME = "Bash Project"
INPUT = .
FILE_PATTERNS = *.sh *.bash
RECURSIVE = YES
FILTER_PATTERNS = *.sh=./doxygen-bash.awk \
*.bash=./doxygen-bash.awk
EXTENSION_MAPPING = sh=C++ bash=C++
EXTRACT_ALL = NO
QUIET = YES
When using strict mode through Doxygen, wrap the filter in a small script:
#!/bin/sh
awk -f ./doxygen-bash.awk -- --strict "$@"
Then reference that wrapper from FILTER_PATTERNS.
Building release artifacts
The maintained root source is transformed into three executable distribution representations. AWK Minifier is a pinned build dependency managed separately from documentation-only dependencies.
Prepare and verify ordinary build dependencies with:
make deps
make deps-check
After dependency preparation, make build is network-free and produces six
files:
dist/doxygen-bash.dev.awk
dist/doxygen-bash.dev.awk.sha256
dist/doxygen-bash.awk
dist/doxygen-bash.awk.sha256
dist/doxygen-bash.min.awk
dist/doxygen-bash.min.awk.sha256
The representations have distinct distribution purposes while retaining one runtime contract:
doxygen-bash.dev.awkcontains the complete maintained source body, including documentation and implementation comments;doxygen-bash.awkis the canonical ordinary consumer artifact and removes full-line comments from the source body while retaining build provenance; anddoxygen-bash.min.awktransforms the ordinary body with the pinned AWK Minifier release while retaining a small provenance header.
Each executable artifact has an adjacent SHA-256 file in standard sha256sum
format. The minifier pin is declared in dependencies.txt and materialized as
vendor/awk-minifier.awk by bashdeps.
Run GNU awk lint against maintained AWK source independently of the behavior suite:
make check
make check requires gawk and treats GNU awk lint warnings as fatal. Linting
is deliberately not part of the test target.
Generated reference documentation
This repository publishes its own Doxygen reference documentation and deliberately
dogfoods both awk-doxygen and bash-doxygen while doing so.
Stable documentation dependencies are declared separately from ordinary build
dependencies in dependencies-docs.txt. A pinned bashdeps release materializes
the pinned filter artifacts and ADR navigation tooling beneath vendor/. The
current stable pins are awk-doxygen v0.0.4, bash-doxygen v0.0.14, and adrctl
v0.0.13.
Prepare the documentation dependencies with:
make deps-docs
Verify them without network access or repair with:
make deps-docs-check
Generate the ephemeral linked ADR landing page from already-prepared dependency state with:
make adr-index
Generate the stable reference tree with:
make docs
The generated ADR landing page lives at doc/adr/README.md; the generated HTML
lives under doc/reference/. Both are ignored by Git and are regenerated from
maintained source and pinned documentation tooling rather than committed.
make docs consumes already-prepared dependency state; it does not synchronize
or repair dependencies itself.
Stable Pages generation intentionally uses the released, SHA-256-pinned filters
in vendor/. This exercises the same consumer boundary downstream projects use
instead of silently documenting with moving repository-local source. The same
shared documentation path generates the ADR landing page before Doxygen for
stable publication and both ADR-006 canaries, so those paths validate the same
site structure.
ADR-006 adds two complementary canaries without moving those stable pins. Pull
requests and main generate the same reference corpus with current repository
doxygen-bash.awk as the Bash filter, providing pre-release integration feedback.
When a release is published, a second canary downloads the exact canonical
released doxygen-bash.awk asset and checksum, verifies the bytes, and generates
the same reference documentation. This catches both source-level regressions
before release and packaging failures after release while leaving stable Pages
publication reproducible.
Routine documentation generation includes linked ADR navigation only; it does not automatically compose an ADR relationship graph. ADR-007 governs the landing-page generation and shared stable/canary boundary.
Supported declarations
The filter recognizes documented functions using common Bash forms:
name() {
name () {
function name {
function name() {
namespace::name() {
It recognizes documented variables using common assignment and declaration forms:
NAME=value
NAME=(one two three)
readonly NAME=value
export NAME=value
local NAME=value
declare -r NAME=value
declare -a NAME=(one two three)
declare -A NAME=([key]=value)
declare -i COUNT=0
declare -n REF=NAME
declare -l LOWER=value
declare -u UPPER=value
Variable output is enriched with inferred Bash characteristics, including read-only/read-write, exported, local, indexed array, associative array, integer, nameref, lowercase transform, and uppercase transform.
Function namespaces
Namespace support is explicit and function-only. A literal Bash symbol that
already contains :: is preserved as source evidence and is emitted beneath the
corresponding Doxygen namespace:
## @fn config::load()
config::load() {
:
}
A differently named Bash implementation can request the same documentation
identity with a qualified @fn:
## @fn config::load()
config_load() {
:
}
or with @namespace plus an unqualified @fn:
## @namespace config
## @fn load()
config_load() {
:
}
Nested namespace paths such as network::http are supported. The filter does
not infer namespaces from implementation prefixes such as config_ or
network_http_. Redundant literal, @fn, and @namespace evidence is accepted
when it agrees; contradictory explicit evidence produces a diagnostic. The
source @namespace and @fn directives are consumed as structural metadata and
are not emitted into the generated function documentation block.
Documentation modules and groups
Logical Bash API modules are mapped to Doxygen groups. @module is the
canonical source spelling. @package is accepted as an exact alias and is
translated to the same group model; it does not imply Java package semantics or
another language-specific package construct.
A module-only documentation block defines the Doxygen group:
## @module networking
## @brief Network-related functionality.
The filter translates that block to a Doxygen @defgroup definition. Module
identifiers are intentionally flat in the initial contract and must match
[A-Za-z_][A-Za-z0-9_]*; nested module/group paths are not currently supported.
A function opts into a module by combining the module marker with an explicit
@fn:
## @module networking
## @fn download()
## @brief Downloads a resource.
download() {
:
}
A variable uses the same pattern with an explicit @var:
## @module networking
## @var NETWORK_TIMEOUT
## @brief Default network timeout in seconds.
readonly NETWORK_TIMEOUT=30
Member blocks are translated to Doxygen @ingroup membership. The source
@module or @package directive is consumed as structural metadata and is not
passed through to Doxygen.
Module membership is local to each documentation block. A module definition
does not establish persistent scope for later symbols, and the filter does not
infer modules from prefixes, filenames, directories, sourcing relationships, or
namespace names. A function or variable placed directly after a module marker
without the required @fn or @var is diagnosed instead of being assigned by
proximity.
Modules and function namespaces are orthogonal. A function may remain a member of a namespace established under the namespace rules above while independently belonging to a Doxygen group.
Diagnostics
Diagnostics are written to standard error. The filter warns when:
- a documentation block is not followed by a recognized declaration;
- an
@fnblock precedes a variable declaration; - an
@varblock precedes a function declaration; - an ordinary unqualified
@fnname differs from the function declaration; - an
@varname differs from the variable declaration; - an
@namespacepath is invalid, incomplete, or used for variable documentation; - explicit namespace evidence conflicts with literal or qualified function identity;
- a module identifier is malformed or outside the supported flat identifier grammar;
@moduleand/or@packagemetadata in one block names conflicting modules; or- module membership is attempted without explicit
@fnor@varmetadata.
With --strict, any warning causes the filter to exit non-zero.
Testing
After ordinary build dependencies have been prepared, run the complete suite from the repository root:
make test
The harness emits one TAP version 13 stream and runs the same behavior-focused
fixtures against the maintained source plus all three generated AWK artifacts.
Successful translations in tests/fixtures/ are compared with golden pseudo-C++
output in tests/expected/. Diagnostic cases in tests/diagnostics/ verify both
normal warning behavior and strict-mode failure. The suite also covers
compact/default blank-line behavior, generated-artifact provenance, and adjacent
checksum verification.
To exercise only the maintained source without preparing build dependencies or
building dist/, run:
make test-source
To build and exercise only the three distribution artifacts, run:
make test-dist
GNU awk lint remains a separate validation boundary:
make check
Design notes
This project is not a full Bash parser. It is a documentation compiler for the
small subset of Bash declarations that can reasonably follow a Doxygen block.
The parser is permissive about whitespace and declaration style, but strict
about documented intent when @fn, @namespace, @module, @package, or
@var is provided. Documentation namespaces are derived only from literal
:: qualification or explicit structural metadata; underscore or prefix
conventions are never inferred. Documentation modules likewise require an
explicit block-local marker and do not create persistent source scope.
Governance
Architecture decisions are recorded in doc/adr/, with concise summaries in
doc/decisions.md. ADR-003 governs the three executable release representations
and their checksums, ADR-004 governs behavior-focused TAP regression testing,
ADR-005 governs stable reference publication, ADR-006 governs current-source and
released-artifact documentation canaries, ADR-007 governs the ephemeral ADR
landing page shared by all documentation paths, ADR-008 governs bounded
declaration association, ADR-009 governs documentation namespaces and literal
qualified function identities, and ADR-010 governs documentation modules and
their Doxygen group representation.
License
This project is licensed under the Creative Commons License 1.0 Universal License. See LICENSE for details.
Contributing
Contributions are welcome. Please read CONTRIBUTING.md and CODE_OF_CONDUCT.md.
Shared Coding Standards
This repository adopts the complete pinned coding_standards@v1.0.9 snapshot
under doc/standards/; .codingstandardrc records the verified release archive
digest. Applicable imported standards are governing requirements unless an
accepted repository-specific ADR or explicit local policy refines them. Presence
in the complete snapshot does not itself make a language-specific standard
applicable, and files under doc/standards/examples/ are illustrative unless a
governing standard says otherwise.
Do not edit imported standards locally. Shared changes belong upstream in
wesley-dean/coding_standards; repository-specific exceptions belong in local
governance. Standards updates are reviewed repository changes, not automatic
synchronization.
Authoritative shared source-documentation standards used by this repository:
- Bash:
doc/standards/bash/documentation-standard.md - AWK:
doc/standards/awk/documentation-standard.md