Adapters
Adapters implement the ContentAdapter base class and are responsible for all data
acquisition. They convert raw source data into Value objects the pipeline can render.
ContentAdapter (base class)
class ContentAdapter {
public:
ContentAdapter(core::config::SiteConfig site_cfg,
core::config::CollectionCfgMap collections);
virtual ~ContentAdapter() = default;
virtual core::error::Result<FetchResult> fetch_all(FetchCallback progress = nullptr) = 0;
virtual core::error::VoidResult ping() = 0;
virtual std::string adapter_name() const = 0;
protected:
core::config::SiteConfig site_cfg_;
core::config::CollectionCfgMap collections_;
core::Value build_site_value() const;
static core::Value resolve_path(const core::Value& v, std::string_view path);
static void apply_field_map(
core::Value& item,
const std::unordered_map<std::string, std::string>& field_map);
void enrich_item(core::Value& item, const std::string& collection_name) const;
static core::RenderItem build_render_item(
const core::Value& v,
const core::config::CollectionConfig& coll_cfg);
void apply_cross_references(
FetchResult& result,
const core::config::CrossRefCfgMap& cross_refs) const;
static void apply_prev_next(FetchResult& result, const std::string& collection_name);
};
Protected helpers available to all adapters:
build_site_value()— convertsSiteConfigto aValueforFetchResult::site.resolve_path(value, "dot.path")— navigates aValueby dot-path. Numeric segments are array indices. Array-of-object segments project a field across all elements:"tags.slug"→ array of slug strings.apply_field_map(item, field_map)— renames or projects fields in-place. Entries aretarget_field: "source.dot.path".enrich_item(item, collection_name)— extractsyear/month/dayfrompublished_at, computespermalinkandoutput_pathfrom the collection's permalink pattern.build_render_item(value, coll_cfg)— builds aRenderItemfrom aValueand its collection config.apply_cross_references()— for each cross-ref config, looks up the referenced collection and injects the resolved items into the source collection's items asitem[<cross_ref_key>].apply_prev_next()— for a collection with items sorted bypublished_at, injectsprev_itemandnext_iteminto each item.
MarkdownAdapter
Reads Markdown files from one or more directories. Each file becomes one RenderItem.
source:
type: markdown
recursive: true
collection_paths:
posts: "content/posts"
pages: "content/pages"
tags: "content/tags"
File processing:
- YAML frontmatter (between
---delimiters) is parsed into the item'sValue. - The Markdown body is converted to HTML by md4c (GFM: tables, strikethrough,
autolinks) and stored as
item["html"]. - If no
slugkey is in frontmatter, it falls back to the file stem. An explicitslug: ""is preserved (enabling root-level output via/{slug}/→index.html). published_atfalls back to the file creation time (birth time on macOS/Linux viastatx, mtime fallback on older kernels).custom_templatein frontmatter overrides the collection'sitem_template.field_mapsare applied beforeenrich_item(), same as the REST adapter.- Taxonomy synthesis: if a collection has no files of its own but appears as a
cross_referencestarget, Guss synthesises its items from data embedded in the source collection. Tags embedded in posts become a fulltagscollection. apply_prev_next()is called for the"posts"collection to injectprev_item/next_itemon each post.
Files are parsed in parallel via OpenMP across collection directories.
RestCmsAdapter
Fetches collections from any REST CMS (Ghost, WordPress, Contentful, or any JSON API).
source:
type: rest_api
base_url: "https://cms.example.com/api/v2"
auth:
type: api_key
header: "X-Ghost-Key"
key: "your-content-api-key"
endpoints:
posts:
path: "/posts"
response_key: "posts"
params:
include: "tags,authors"
tags:
path: "/tags"
response_key: "tags"
All collections are fetched in parallel via OpenMP. The adapter applies field_maps,
calls enrich_item() per item, builds cross-references, and injects prev_item/
next_item into the "posts" collection.
Pagination strategies — evaluated in priority order:
| Priority | Strategy | Config field |
|---|---|---|
| 1 | Total pages header | total_pages_header: "X-Total-Pages" |
| 2 | Total count header | total_count_header: "X-Total-Count" |
| 3 | Link header (RFC 5988) | link_header: true |
| 4 | JSON cursor | json_cursor: "meta.next_cursor" |
| 5 | JSON next URL | json_next_url: "meta.next_page_url" |
| 6 | JSON next sentinel | json_next: "meta.pagination.next" |
| 7 | Optimistic fetching | optimistic_fetching: true |
| 8 | Single page | (none set) |
The adapter selects the first matching strategy from the config and runs it for the lifetime of that endpoint's fetch. Strategies 3–6 are per-request (the next URL or token is extracted from each response). Strategies 1–2 determine the total upfront from the first response's headers.
Field maps rename or project fields from the API response before enrichment:
source:
field_maps:
posts:
body: "html" # rename "html" → "body"
author_name: "authors.0.name" # project nested value
tag_slugs: "tags.slug" # project array field
Cross-references inject related collection items:
source:
cross_references:
tags:
from: "posts"
via: "tags.slug"
match_key: "slug"
This injects matching tag objects from the tags collection into each post item's
tags array, replacing the raw embedded tag data with the full fetched tag objects.
Item-level template override: if an item's Value contains a custom_template
field, that template is used instead of the collection's item_template.
optimistic_fetching stops silently on HTTP 404, an empty response, or a parse
error — no error is propagated. This allows safe use against APIs that return 404
instead of an empty last page.