Derivations
Anything you can compute, you don't store.
A derivation is a pure function of the Site Tree and the config that returns a view: a list of pages, a grouping of documents, an order. It reads the tree. It reads nothing else. It writes nothing. Call it twice and you get the same answer. That is the whole definition, and it is what lets every output of a build agree with every other output.
The pure derivations live in taxus-domain/src/derivation.rs and
taxus-domain/src/tree.rs. The generator has a few functions that fit the
definition but need the config or the generator's own types; this page
lists those too and says where they live.
Tree order: derivation::documents
Question. In what order should the site be walked?
Inputs. The tree.
Where. taxus_domain::derivation::documents(tree) -> Vec<Node>.
Answer. Every document in tree order: a section's own index file if
it has one, then its pages by slug, then each subsection by slug,
recursively. Drafts are included; callers filter. This is the order
routes are registered in (RouteRegistry::from_tree), the order content
is processed in, and the order every later list starts from. It is what
makes builds deterministic.
Consumed by. RouteRegistry::from_tree (stage 1), the sitemap
(stage 8), taxonomy grouping (stage 10), and iter_pages on the tree.
Reachability: derivation::descendant_pages
Question. Which pages are anywhere under this section?
Inputs. A section node.
Where. taxus_domain::derivation::descendant_pages(section) -> Vec<&PageNode>.
Answer. The section's own pages, then each subsection's, depth-first.
This is the query that replaces a stored "all pages below me" field. The
tree's SiteTree::iter_pages is this function applied to the root.
Consumed by. recent (below). No template field exposes it directly.
Sorting: tree::sort_pages
Question. In what order should a listing show its pages?
Inputs. A list of pages and a SortBy.
Where. taxus_domain::tree::sort_pages(&mut [&PageNode], SortBy).
Answer. date: newest first, undated last. title:
case-insensitive ascending. weight: lowest first. none: leave the
input order. The sort is stable, so ties keep tree order. SortBy comes
from the listing section's sort_by frontmatter.
Consumed by. Section listings (stage 6) and recent.
Recent pages: derivation::recent
Question. What are the newest pages on the whole site?
Inputs. The tree and whether drafts count.
Where. taxus_domain::derivation::recent(tree, include_drafts) -> Vec<&PageNode>.
Answer. Every page in the site, filtered by draft status, sorted by date newest first. Section index files are not pages and are not included.
Consumed by. Feeds (stage 11), through feed_pages below.
Aggregation: derivation::aggregate (pages_from)
Question. Which pages does this section list, including ones it does not own?
Inputs. The receiving section, the tree, and the node paths named in
the receiver's pages_from.
Where. taxus_domain::derivation::aggregate(section, tree, from) -> Vec<&PageNode>.
Answer. The receiver's direct pages, then each donor's direct pages in
the order the donors are named, with duplicates removed by node path.
Donors that do not exist are skipped. The result is not sorted; the
caller sorts with the receiver's sort_by.
Consumed by. build::pipeline::pages::collect_child_pages, which
resolves the pages_from strings to node paths (warning on bad ones),
calls aggregate, sorts, and drops pages the build skipped. The result
is the template field section.pages (stage 6) and what get_section
returns.
Taxonomies: derivation::group_by_terms
Question. Which documents carry each tag, category or series?
Inputs. The tree, whether drafts count, and a function that reads one taxonomy's terms off a frontmatter.
Where. taxus_domain::derivation::group_by_terms(tree, include_drafts, terms_of) -> BTreeMap<String, Vec<Node>>.
Answer. A map from term name to the documents that declare it, in tree order, with term names sorted. Sections with an index file participate like pages. A document that repeats a term appears twice under it.
Consumed by. build::pipeline::taxonomy::build_taxonomy_map (stage
10) calls it three times, once per kind, and fills a TaxonomyMap. The
term pages /tags/rust/ and the list pages /tags/ are rendered from
that map, with extra.taxonomy as the template variable.
Pagination
Question. How is a long listing split across several URLs?
Inputs. The section's sorted listing and its paginate_by.
Where. build::pipeline::pages::render_paginated_section (private,
in taxus-generator/src/build/pipeline/pages.rs).
Answer. Slices of paginate_by items. Slice 1 renders at the
section's own URL; slice n renders at <section>/page/n/. Each render
gets a PaginationContext with current, total, prev, next,
first and last. This is a derivation over a derivation: it takes the
listing above and cuts it. It is not in the domain crate because the
slices exist only to be rendered.
Consumed by. The template field section.pagination (stage 6).
get_section never returns pagination; slicing belongs to the section's
own render.
Feed entries
Question. Which pages does the feed announce, and in what order?
Inputs. The tree and the [feed] sections config.
Where. build::pipeline::feeds::feed_pages(tree, sections) -> Vec<&PageNode>,
then feed::FeedEntry::from_page for each.
Answer. recent(tree, false), narrowed to pages that have a date,
and to pages under one of the configured sections when any are named.
The feed generator then applies [feed] limit. Undated pages are left
out on purpose: a feed entry needs a publication date, and stamping it
with the build time would re-announce the page on every build.
Consumed by. dist/feed.xml and dist/feed.atom (stage 11).
Sitemap entries
Question. Which addresses does the site have?
Inputs. The tree, the processed pages, and the site's base URL.
Where. build::pipeline::sitemap::generate_sitemap.
Answer. One entry per non-draft document from documents, with the
permalink, the date as lastmod, priority 1.0 for the root, 0.8 for
sections and 0.7 for pages, sorted by URL. Taxonomy and pagination pages
are not documents and are not listed.
Consumed by. dist/sitemap.xml (stage 8).
Prev/next and ancestors
There is no prev/next derivation and no ancestors derivation in the code
today. NodePath::parent gives a node's parent path, and get_section
can fetch it, so a template can reach the parent. Nothing walks the
ancestor chain for it, and no context field names a previous or next
page.
Tree method or derivation?
Two kinds of function take a tree. The rule for deciding which to write:
- It is a tree method if it answers "is this node here, and give it
to me": a lookup by path, or an iteration that takes no parameters and
applies no policy.
SiteTree::get_section,get_page,iter_pages. - It is a derivation if it selects, filters, orders or groups, or if
the answer depends on frontmatter values, config, or a caller's choice
such as whether drafts count. Everything in
derivation.rs.
A method that took include_drafts would be a derivation in disguise. A
derivation that returned a single node by path would be a method in
disguise. Keeping them apart keeps the tree small and the policy visible.
Two further rules for a new derivation:
- It goes in
taxus_domain::derivationif it needs only the tree and plain values. It stays in the generator if it needs the config struct, rendered HTML, or generator types such asProcessedPage. - It returns borrowed nodes (
&PageNode,Node<'_>), never copies. The tree owns the data; a derivation is a view of it.