class

Hwaro::Models::Config

Inherits Reference < Object

Constants

ARRAY_SECTION_KEYS = ["taxonomies"] of ::String

Section keys read as an array of tables ([[taxonomies]]). Every other registered section key is a table, except the two below.

AUTO_SECTIONS_KEY = "auto_sections"

[menus] auto_sections = "<menu>" — a setting, not a menu name.

BOOLEAN_SECTION_KEYS = ["sitemap"] of ::String

Sections whose loader also accepts a boolean: sitemap = true is the pre-table form load_sitemap still honours for backward compatibility.

KNOWN_TOP_LEVEL_KEYS = SCALAR_KEYS + SECTION_LOADERS.flat_map(&.keys).uniq!.sort!

Every top-level key load reads (scalars + registered section keys). Used to warn on unrecognized keys instead of silently ignoring them — a typo'd [markdonw] or titel = otherwise disables a feature with zero feedback. Templates cannot read arbitrary raw config keys (the site/config objects expose structured fields only), so an unknown top-level key is always dead configuration. Sorted, so "did you mean" suggestions tie-break the same way regardless of load order.

NON_FILE_BASENAMES = {"", ".", "..", "/"}

Basenames that do not name a file. Every generated-file emitter (sitemap, robots, search, feeds, llms) writes to Path[output_dir, File.basename(configured_filename)], and File.basename maps each of these back to itself — so the write target collapses onto the output DIRECTORY instead of a file inside it. The atomic writer then tries to rename its temp file onto a directory and the build dies with a raw IO::Error naming an internal .<pid>.<fiber>.tmp path under exit 70, the code this project reserves for internal bugs.

SCALAR_KEYS = ["title", "description", "base_url", "default_language"] of ::String

The four scalar keys load reads directly, ahead of any section.

SECTION_LOADERS = [SectionLoader.new(["sitemap"] of ::String, ->(c : Config) do load_sitemap(c) end), SectionLoader.new(["robots"] of ::String, ->(c : Config) do load_robots(c) end), SectionLoader.new(["llms"] of ::String, ->(c : Config) do load_llms(c) end), SectionLoader.new(["feeds"] of ::String, ->(c : Config) do load_feeds(c) end), SectionLoader.new(["search"] of ::String, ->(c : Config) do load_search(c) end), SectionLoader.new(["plugins"] of ::String, ->(c : Config) do load_plugins(c) end), SectionLoader.new(["content"] of ::String, ->(c : Config) do load_content_files(c) end), SectionLoader.new(["content"] of ::String, ->(c : Config) do load_content_new(c) end), SectionLoader.new(["content"] of ::String, ->(c : Config) do load_content_summary(c) end), SectionLoader.new(["content"] of ::String, ->(c : Config) do load_content_backlinks(c) end), SectionLoader.new(["pagination"] of ::String, ->(c : Config) do load_pagination(c) end), SectionLoader.new(["highlight"] of ::String, ->(c : Config) do load_highlight(c) end), SectionLoader.new(["auto_includes"] of ::String, ->(c : Config) do load_auto_includes(c) end), SectionLoader.new(["og"] of ::String, ->(c : Config) do load_og(c) end), SectionLoader.new(["menus"] of ::String, ->(c : Config) do load_menus(c) end), SectionLoader.new(["taxonomies"] of ::String, ->(c : Config) do load_taxonomies(c) end), SectionLoader.new(["search"] of ::String, ->(c : Config) do validate_search_facets(c) end), SectionLoader.new(["languages"] of ::String, ->(c : Config) do load_languages(c) end), SectionLoader.new(["versions"] of ::String, ->(c : Config) do load_versions(c) end), SectionLoader.new(["build"] of ::String, ->(c : Config) do load_build(c) end), SectionLoader.new(["serve"] of ::String, ->(c : Config) do load_serve(c) end), SectionLoader.new(["markdown"] of ::String, ->(c : Config) do load_markdown(c) end), SectionLoader.new(["series"] of ::String, ->(c : Config) do load_series(c) end), SectionLoader.new(["related"] of ::String, ->(c : Config) do load_related(c) end), SectionLoader.new(["git"] of ::String, ->(c : Config) do load_git(c) end), SectionLoader.new(["permalinks"] of ::String, ->(c : Config) do load_permalinks(c) end), SectionLoader.new(["assets"] of ::String, ->(c : Config) do load_assets(c) end), SectionLoader.new(["sass"] of ::String, ->(c : Config) do load_sass(c) end), SectionLoader.new(["pwa"] of ::String, ->(c : Config) do load_pwa(c) end), SectionLoader.new(["amp"] of ::String, ->(c : Config) do load_amp(c) end), SectionLoader.new(["image_processing"] of ::String, ->(c : Config) do load_image_processing(c) end), SectionLoader.new(["doctor"] of ::String, ->(c : Config) do load_doctor(c) end), SectionLoader.new(["static"] of ::String, ->(c : Config) do load_static(c) end), SectionLoader.new(["deployment"] of ::String, ->(c : Config) do load_deployment(c) end), SectionLoader.new([] of ::String, ->(c : Config) do resolve_deployment_source_dir(c) end), SectionLoader.new(["outputs"] of ::String, ->(c : Config) do load_outputs(c) end), SectionLoader.new(["links"] of ::String, ->(c : Config) do load_links(c) end), SectionLoader.new(["data"] of ::String, ->(c : Config) do load_data_remote(c) end), SectionLoader.new(["privacy"] of ::String, ->(c : Config) do load_privacy(c) end), SectionLoader.new(["csp"] of ::String, ->(c : Config) do load_csp(c) end), SectionLoader.new(["content"] of ::String, ->(c : Config) do load_content_generate(c) end), SectionLoader.new(["content"] of ::String, ->(c : Config) do load_content_schema(c) end)]
SELF_VALIDATING_SECTION_KEYS = ["versions"] of ::String

Sections whose loader validates the shape itself: [versions] takes a table or a bare [[versions]] array and raises a config error for anything else, so "Ignoring" would be wrong.

VERSION_NAME_RE = /\A[A-Za-z0-9][A-Za-z0-9._~-]*\z/

[versions] (switches) + [[versions.list]] (entries). TOML cannot make one key both a table and an array of tables, so a bare [[versions]] array is accepted too: entries only, default switches.

Validation is strict — a misconfigured version silently publishing docs at the wrong URL is worse than a failed build:

  • name required, URL-safe, unique
  • path (defaults to name) content-relative, unique, non-nested
  • exactly one latest = true (none → the first entry is latest)
  • search / feeds / taxonomies ∈ {"latest", "all"}
VERSIONS_ARRAY_HEADER_RE = /^[ \t]*\[\[[ \t]*versions[ \t]*\]\][ \t]*(#.*)?$/m
VERSIONS_TABLE_HEADER_RE = /^[ \t]*\[[ \t]*versions[ \t]*\][ \t]*(#.*)?$/m

Constructors

load(config_path : String = "config.toml", env : String | Nil = nil) : Config
Source

Class methods

validate_base_url!(value : String) : Nil

Load and parse a config.toml into a populated Config.

Raises Hwaro::HwaroError(HWARO_E_CONFIG) directly at the source for file-not-found and TOML parse errors so every caller (build, deploy, doctor, tool, services) gets a classified error with exit code 3 without having to do substring matching on the exception message. File-not-found is classified as HWARO_E_CONFIG rather than HWARO_E_IO because a missing config.toml is a config-level user error, not an arbitrary IO failure. Accepts an absolute http(s)://host[:port][/path] URL or the empty string (which means "no absolute URL is configured"). Raises ArgumentError on anything else so callers can wrap the failure in whichever classified HwaroError code suits their context (HWARO_E_CONFIG for config.toml, HWARO_E_USAGE for CLI flags).

Source

Instance methods

amp=(amp : AmpConfig)
Source
assets
Source
assets=(assets : AssetsConfig)
Source
auto_includes
Source
auto_includes=(auto_includes : AutoIncludesConfig)
Source
base_path

Path component of base_url, used to make root-relative links work when the site is deployed under a subpath (e.g. GitHub/GitLab project pages served at https://user.github.io/repo/). For https://x.com/repo this returns /repo; for a domain-root deployment (https://x.com) or an empty base_url it returns "". Trailing slashes are stripped so callers can build base_path + page.url without producing //.

Source
base_url
Source
base_url=(value : String)

Normalize on assignment: a trailing slash makes {{ base_url }}/path templates (and canonical/og URLs) emit //. Strip it so the build is correct whether the trailing slash came from config.toml or --base-url (previously only doctor --fix normalized this).

Source
build
Source
build=(build : BuildConfig)
Source
content_files
Source
content_files=(content_files : ContentFilesConfig)
Source
content_generate
Source
content_generate=(content_generate : Array(ContentGenerateConfig))
Source
content_new
Source
content_new=(content_new : ContentNewConfig)
Source
content_schema
Source
content_schema=(content_schema : Array(ContentSchemaConfig))
Source
csp=(csp : CspConfig)
Source
data_remote
Source
data_remote=(data_remote : Array(RemoteDataConfig))
Source
default_language
Source
default_language=(value : String)
Source
deployment
Source
deployment=(deployment : DeploymentConfig)
Source
description
Source
description=(description : String)
Source
doctor
Source
doctor=(doctor : DoctorConfig)
Source
feeds
Source
feeds=(feeds : FeedConfig)
Source
git=(git : GitConfig)
Source
highlight
Source
highlight=(highlight : HighlightConfig)
Source
image_processing
Source
image_processing=(image_processing : ImageProcessingConfig)
Source
language(code : String) : LanguageConfig | Nil

Get language config by code, returns nil if not found

Source
languages
Source
languages=(value : Hash(String, LanguageConfig))
Source
llms
Source
llms=(llms : LlmsConfig)
Source
markdown
Source
markdown=(markdown : MarkdownConfig)
Source
multilingual?

Check if site is multilingual. Memoized — this runs several times per page during render, and the languages table only mutates during config load, before the first call. The languages= / default_language= setters invalidate; in-place mutation of the languages Hash after the first call would not (don't do that).

Source
og=(og : OpenGraphConfig)
Source
outputs
Source
outputs=(outputs : OutputsConfig)
Source
pagination
Source
pagination=(pagination : PaginationConfig)
Source
plugins
Source
plugins=(plugins : PluginConfig)
Source
privacy
Source
privacy=(privacy : PrivacyConfig)
Source
pwa=(pwa : PwaConfig)
Source
require_resolved_base_url!(override : String | Nil) : Nil

A build needs a real base_url: refuse one still naming an unset variable unless the command line supplies its own (--base-url, or the localhost URL serve always passes).

Source
robots
Source
robots=(robots : RobotsConfig)
Source
sass
Source
sass=(sass : SassConfig)
Source
sass_source?(relative_path : String) : Bool

True when built-in Sass compilation is on and relative_path is an SCSS source — such files compile to .css instead of publishing verbatim through the static copy. The extension must be lowercase .scss, matching what the compiler's glob picks up — other casings keep publishing verbatim.

Source
search
Source
search=(search : SearchConfig)
Source
series
Source
series=(series : SeriesConfig)
Source
serve
Source
serve=(serve : ServeConfig)
Source
sitemap
Source
sitemap=(sitemap : SitemapConfig)
Source
sorted_languages

Get sorted languages by weight

Source
static
Source
static=(static : StaticConfig)
Source
summary
Source
summary=(summary : SummaryConfig)
Source
taxonomies
Source
taxonomies=(taxonomies : Array(TaxonomyConfig))
Source
title
Source
title=(title : String)
Source
unresolved_base_url_var

The variable a base_url = "${SITE_URL}" still names because it is unset. base_url stays empty and the build refuses to run without a --base-url (see require_resolved_base_url!); serve, which always supplies its own, and the commands that only read config keep working.

Source
unresolved_base_url_var=(unresolved_base_url_var : String | Nil)

The variable a base_url = "${SITE_URL}" still names because it is unset. base_url stays empty and the build refuses to run without a --base-url (see require_resolved_base_url!); serve, which always supplies its own, and the commands that only read config keep working.

Source
versions
Source
versions=(versions : VersionsConfig)
Source
with_base_path(path : String) : String

Prefix a site-internal root-relative path (e.g. /posts/x/) with base_path so generated URLs resolve under a subpath deployment. Absolute http(s):// URLs and paths that are not root-relative are returned unchanged; a no-op when base_path is "" (domain-root deploy). Callers that may hold a path without a leading slash (e.g. some page.url values) should normalize it first — this helper only prefixes values that already start with "/".

Source

Nested types