Class: Abqari::SiteLoader
- Inherits:
-
Object
- Object
- Abqari::SiteLoader
- Defined in:
- lib/abqari/site_loader.rb
Overview
Site-loading collaborator. Owns reading the three classes of input that bootstrap a build:
- `config/site.yml` (and environment overrides)
- `content/**/*.md` (every page the build will render)
- `data/**/*.{yml,yaml,json}` (operator-curated reference tables)
Plus the nav/footer link cascade, which reads from both config (data/ files) and frontmatter — it's loader-shaped, so it lives here too.
No build-time mutation of pages happens in this class. Loaders return arrays/hashes; Site stores them. Filtering for drafts / future-dated / disabled / external-symlink pages happens at load time so downstream code sees a clean list.
Instance Method Summary collapse
-
#footer_links ⇒ Object
Footer cascade: same shape as nav.
-
#initialize(site) ⇒ SiteLoader
constructor
A new instance of SiteLoader.
-
#load_config ⇒ Object
Parse
config/site.yml, apply per-environment overrides via deep-merge. -
#load_data ⇒ Object
Read every
data/**/*.{yml,yaml,json}file into a hash keyed by the path-relative-with-extension-stripped form:. -
#load_pages ⇒ Object
Walk
content/**/*.md. -
#load_yaml_file(path) ⇒ Object
YAML.safe_load_filewith the engine-wide permitted classes, and a syntax error turned into something a person can act on. -
#nav_links ⇒ Object
Nav cascade: data/nav.yml
links:array > frontmatternav:blocks. -
#stray_bundle_markdown?(path) ⇒ Boolean
True for a markdown file sitting INSIDE a bundle directory that isn't that bundle's
index.md— e.g.
Constructor Details
#initialize(site) ⇒ SiteLoader
Returns a new instance of SiteLoader.
22 23 24 |
# File 'lib/abqari/site_loader.rb', line 22 def initialize(site) @site = site end |
Instance Method Details
#footer_links ⇒ Object
Footer cascade: same shape as nav.
181 182 183 |
# File 'lib/abqari/site_loader.rb', line 181 def explicit_links_from('footer') || frontmatter_links_for('footer') end |
#load_config ⇒ Object
Parse config/site.yml, apply per-environment overrides via
deep-merge. Returns a Hash. No env-var coercion or schema work
happens here — SiteConfig.validate! handles that downstream.
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/abqari/site_loader.rb', line 29 def load_config # Site config is site-only (no engine fallback) — every consuming # site declares its own theme, Folio config, etc. site_root = ENV['ABQARI_SITE_ROOT'] || Dir.pwd path = File.join(site_root, 'config', 'site.yml') unless File.exist?(path) # Silent `{}` here means "wrong directory" or a typo'd # ABQARI_SITE_ROOT produces a defaults-only build with no signal. # Warn loudly (and let ABQARI_STRICT_CONFIG make it fatal). msg = "config/site.yml not found at #{path} — building with defaults only " \ '(no title, no url). Are you in the site directory?' raise UserError, msg if ENV['ABQARI_STRICT_CONFIG'] == 'true' Log.warn msg return {} end raw = load_yaml_file(path) || {} envs = raw.delete('environments') || {} env = @site.env.to_s overrides = envs[env] || {} warn_on_unknown_env(env, envs) deep_merge(raw, overrides) end |
#load_data ⇒ Object
Read every data/**/*.{yml,yaml,json} file into a hash keyed by
the path-relative-with-extension-stripped form:
data/nav.yml → result['nav']
data/foo/bar.yml → result['foo_bar']
data/testimonials.yml → result['testimonials']
YAML files use YAML.safe_load with permitted_classes: [Date, Time] (the engine-wide allow-list) so naked dates and
ISO timestamps round-trip without opening up arbitrary class
instantiation.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/abqari/site_loader.rb', line 153 def load_data result = {} data_dir = File.join(@site.site_root, 'data') return result unless Dir.exist?(data_dir) Dir.glob(File.join(data_dir, '**', '*.{yml,yaml,json}')).sort.each do |path| rel = path.sub("#{data_dir}/", '') key = rel.sub(/\.(yml|yaml|json)\z/, '').tr('/', '_') result[key] = if path.end_with?('.json') begin JSON.parse(File.read(path, encoding: 'UTF-8')) rescue JSON::ParserError => e raise UserError, "data/#{rel} — invalid JSON: #{e..lines.first.to_s.strip}" end else load_yaml_file(path) end end result end |
#load_pages ⇒ Object
Walk content/**/*.md. For each markdown file:
- Skip if it lives under a disabled collection's source dir.
- Skip if it's an external symlink (escapes site_root).
- Skip drafts unless ABQARI_INCLUDE_DRAFTS=true or drafts config
allows it in this env.
- Skip future-dated posts unless we're in dev or the env var
is set.
- Skip pages with `enabled: false` in frontmatter — total opt-out.
Returns the surviving array of Page instances.
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/abqari/site_loader.rb', line 81 def load_pages pattern = File.join(@site.site_root, 'content', '**', '*.md') pages = [] Dir.glob(pattern).sort.each do |path| next if disabled_collection_path?(path) next if @site.external_symlink?(path) next if stray_bundle_markdown?(path) page = Page.new(path, @site) next if page.disabled? next if page.draft? && !include_drafts? next if page.future? && !include_future? pages << page end pages end |
#load_yaml_file(path) ⇒ Object
YAML.safe_load_file with the engine-wide permitted classes, and
a syntax error turned into something a person can act on.
Psych's own message already names the file, line and column — the problem was that it arrived at the top of a ten-frame trace through Psych internals, which reads as an engine crash rather than "line 12 of your config has a stray colon". Keep Psych's detail, drop the frames (ABQARI_DEBUG=1 brings them back).
64 65 66 67 68 69 |
# File 'lib/abqari/site_loader.rb', line 64 def load_yaml_file(path) YAML.safe_load_file(path, permitted_classes: Abqari::YAML_PERMITTED_CLASSES) rescue Psych::SyntaxError => e rel = path.sub("#{@site.site_root}/", '') raise UserError, "#{rel}:#{e.line}:#{e.column} — invalid YAML: #{e.problem} #{e.context}".strip end |
#nav_links ⇒ Object
Nav cascade: data/nav.yml links: array > frontmatter nav: blocks.
176 177 178 |
# File 'lib/abqari/site_loader.rb', line 176 def nav_links explicit_links_from('nav') || frontmatter_links_for('nav') end |
#stray_bundle_markdown?(path) ⇒ Boolean
True for a markdown file sitting INSIDE a bundle directory that
isn't that bundle's index.md — e.g.
content/posts/my-post/notes.md.
A bundle is one content file plus its assets, so a second .md
has no defined role and used to get two contradictory ones: it was
copied verbatim into the output as a "bundle asset" (frontmatter
and all, even when marked published: false), AND loaded here as
a stray page rendering at /posts/my-post/notes/ — because
Page#collection rejects it for not being an index, leaving it
uncollected rather than dropping it. Published twice, in two
forms, neither intended.
The asset half is fixed by the publishable-extension allow-list. This is the page half. Warn rather than skip silently: the author put the file there for a reason and needs to know it does nothing.
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/abqari/site_loader.rb', line 117 def stray_bundle_markdown?(path) return false if File.basename(path) == 'index.md' @site.collections.each do |_name, cfg| next unless cfg.fetch('enabled', true) next if cfg['bundle'] == false # flat collections: `<slug>.md` IS the page source = File.join(@site.site_root, cfg['source'].to_s) next unless path.start_with?(source + File::SEPARATOR) # Depth >= 2 means it's nested inside a slug directory rather # than sitting at the collection root. next unless path.sub("#{source}/", '').split('/').length >= 2 rel = path.sub("#{@site.site_root}/", '') Log.warn "Skipping #{rel} — only `index.md` is rendered inside a bundle " \ 'directory, so this file is neither published nor rendered. Move it ' \ 'out to publish it, or rename it with a leading dot ' \ "(`.#{File.basename(path)}`) to keep it local and silence this." return true end false end |