Class: Abqari::RenderContext
- Inherits:
-
Object
- Object
- Abqari::RenderContext
- Includes:
- Helpers, CoverHelpers, PictureHelpers, PublicationHelpers
- Defined in:
- lib/abqari/render_context.rb,
lib/abqari/render_context/cover_helpers.rb,
lib/abqari/render_context/picture_helpers.rb,
lib/abqari/render_context/publication_helpers.rb
Overview
The binding ERB templates execute against. Holds references to the current page and site, includes shared helper mixins.
Decomposed for readability — the helpers split into:
- `Helpers` : core scalar helpers (h, sanitized_url,
local_time, pluralize, truncate, link_to,
slugify, …). Engine-wide.
- `PublicationHelpers` : publication/bundle/series accessors,
buy/cart buttons. Publisher-agnostic —
see lib/abqari/render_context/
publication_helpers.rb.
- `PictureHelpers` : asset_path, icon, heroicon, picture_tag,
bundle_picture_tag, bundle_asset_url.
Templating-specific surface (render, render_layout, render_string, yield_content, get_binding) stays here.
Defined Under Namespace
Modules: CoverHelpers, PictureHelpers, PublicationHelpers
Constant Summary collapse
- TEMPLATE_PUBLIC_IVARS =
Instance variables intentionally exposed to templates via the
bindingERB receives. Anything outside this set should use the@_convention so a stray<%= @foo %>is clearly out-of- contract. The audit testRenderContextInternalIvarTestreads this list and fails if RenderContext grows an ivar that's neither in the allow-list nor@_-prefixed. %i[@page @site].freeze
- RESERVED_LOCALS =
Local names that must never be shadowed by a
render(..., key: v)local — doing so would silently replace the escaping function or a core API method for that partial.pageis deliberately NOT here: rebinding it (render 'partials/card', page: item) is a supported pattern. %i[h sanitized_url site render render_layout render_string script_safe_json get_binding yield_content].freeze
Constants included from CoverHelpers
CoverHelpers::COVER_MODES, CoverHelpers::COVER_PALETTES, CoverHelpers::DEFAULT_COVER_MODE, CoverHelpers::THEME_COVER_PALETTES
Constants included from PictureHelpers
PictureHelpers::HEROICON_VARIANTS
Constants included from Helpers
Helpers::NON_DECOMPOSING_LETTERS, Helpers::NON_DECOMPOSING_LETTERS_RE, Helpers::SCRIPT_JSON_ESCAPE, Helpers::SCRIPT_JSON_RE
Instance Attribute Summary collapse
-
#page ⇒ Object
readonly
Returns the value of attribute page.
-
#site ⇒ Object
readonly
Returns the value of attribute site.
Instance Method Summary collapse
-
#collection_index_label(target = page) ⇒ Object
Display label for the collection index — slug if set, canonical name otherwise.
-
#collection_index_url(target = page) ⇒ Object
Slug-aware "back to index" data for a show-view page.
-
#document_title ⇒ Object
The
text: "Page · Site", except when the two are the same string — the home page usually titles itself after the site, and "Mysite · Mysite" reads like a bug in the browser tab. - #get_binding ⇒ Object
-
#initialize(page, site, locals = {}) ⇒ RenderContext
constructor
A new instance of RenderContext.
- #render(partial, **locals) ⇒ Object
-
#render_layout(name, seen = []) ⇒ Object
Render a layout, with optional chaining via the layout's own frontmatter.
-
#render_string(template) ⇒ Object
Render an ERB string against this context.
-
#series_url(term, target = page) ⇒ Object
URL of the series index that covers
target's collection. -
#taxonomy_url(taxonomy_name, term) ⇒ Object
URL of a taxonomy term page, honouring the configured permalink so slug-aliasing works.
-
#yield_content ⇒ Object
The output of the inner layout, available inside a wrapping layout as
<%== yield_content %>.
Methods included from CoverHelpers
#balanced_word_partition, #cover_choice_for, #cover_image_path, #cover_mode_for, #cover_palette, #cover_svg_for, #enumerate_splits, #partition_by_sizes, #svg_title_font_size, #svg_wrap_title
Methods included from PictureHelpers
#asset_path, #bundle_asset_url, #bundle_asset_url_for, #bundle_picture_tag, #heroicon, #icon, #optional_asset_path, #picture_tag
Methods included from PublicationHelpers
#bundle_for, #buy_button, #cart_button, #publication_card_data, #publication_for, #series_for
Methods included from Helpers
#excerpt, #external_http_url, #h, #link_to, #local_time, #markdown, #pluralize, #reading_time, #sanitized_url, #script_safe_json, #slugify, #time_ago, #time_until, #to_time, #truncate, #word_count, #years_since
Constructor Details
#initialize(page, site, locals = {}) ⇒ RenderContext
Returns a new instance of RenderContext.
52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/abqari/render_context.rb', line 52 def initialize(page, site, locals = {}) @page = page @site = site locals.each do |key, value| if RESERVED_LOCALS.include?(key.to_sym) raise ArgumentError, "render local #{key.inspect} would shadow the core helper of the same name — rename it" end define_singleton_method(key) { value } end end |
Instance Attribute Details
#page ⇒ Object (readonly)
Returns the value of attribute page.
34 35 36 |
# File 'lib/abqari/render_context.rb', line 34 def page @page end |
#site ⇒ Object (readonly)
Returns the value of attribute site.
34 35 36 |
# File 'lib/abqari/render_context.rb', line 34 def site @site end |
Instance Method Details
#collection_index_label(target = page) ⇒ Object
Display label for the collection index — slug if set, canonical name otherwise.
170 171 172 173 174 175 176 177 |
# File 'lib/abqari/render_context.rb', line 170 def collection_index_label(target = page) name = target.is_a?(String) ? target : effective_collection_name(target) return nil unless name cfg = site.collection_config(name) slug = cfg['slug'].to_s slug.empty? ? name : slug end |
#collection_index_url(target = page) ⇒ Object
Slug-aware "back to index" data for a show-view page. Returns the
URL of the page's collection index, honoring slug: aliasing —
e.g. a post in the posts collection with slug: stories returns
/stories/. Returns nil for pages outside any collection.
155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/abqari/render_context.rb', line 155 def collection_index_url(target = page) # Accept either a Page (resolve via `effective_collection_name`) # or a collection name string directly — the latter is useful # from contexts that aren't in any collection (e.g. the home # page's photo modules linking to /photos/). name = target.is_a?(String) ? target : effective_collection_name(target) return nil unless name cfg = site.collection_config(name) slug = cfg['slug'].to_s "/#{slug.empty? ? name : slug}/" end |
#document_title ⇒ Object
The
135 136 137 138 139 140 141 |
# File 'lib/abqari/render_context.rb', line 135 def document_title page_title = page.title.to_s site_title = site.config['title'].to_s return site_title if page_title.empty? || page_title == site_title "#{page_title} · #{site_title}" end |
#get_binding ⇒ Object
205 206 207 |
# File 'lib/abqari/render_context.rb', line 205 def get_binding binding end |
#render(partial, **locals) ⇒ Object
64 65 66 67 |
# File 'lib/abqari/render_context.rb', line 64 def render(partial, **locals) context = RenderContext.new(page, site, locals) site.compiled_template(partial_path(partial)).result(context.get_binding) end |
#render_layout(name, seen = []) ⇒ Object
Render a layout, with optional chaining via the layout's own
frontmatter. A layout can declare layout: <parent> at the top
of its file (Jekyll-style); the engine renders the inner layout
first, then wraps the result in the parent layout, and so on
recursively until it reaches a layout with no parent.
The wrapping layout reads the inner output via <%== yield_content %>
(raw — the inner layout's output is already escaped HTML, escaping
it again would render tags as text).
Frontmatter on layouts:
layout: application # this layout extends application.html.erb
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/abqari/render_context.rb', line 83 def render_layout(name, seen = []) # Cycle guard. A layout chain that loops (`a` extends `b` extends # `a`, or a layout that extends itself) would recurse forever and # blow the stack with a `SystemStackError` — which isn't a # `StandardError`, so it escapes the renderer's per-page rescue # and crashes a worker thread with no layout name attached. Fail # with the actual cycle instead. if seen.include?(name) raise "layout cycle detected: #{(seen + [name]).join(' -> ')}" end seen = seen + [name] # Layouts are hybrid: site override wins, engine version is the # fallback. Users override a layout by dropping their own copy # at `app/views/layouts/<name>.html.erb` in the site root. path = site.find_in_paths(File.join('app', 'views', 'layouts', "#{name}.html.erb")) unless File.exist?(path) raise "layout #{name.inspect} not found (searched site root then engine root: " \ "#{File.join('app', 'views', 'layouts', "#{name}.html.erb")})" end raw = File.read(path, encoding: 'UTF-8') layout_fm, body = parse_layout_frontmatter(raw) template = ErbiTemplate.new(body, path) inner = template.result(get_binding) parent = layout_fm['layout'] return inner unless parent @_yielded_content = inner render_layout(parent.to_s, seen) end |
#render_string(template) ⇒ Object
Render an ERB string against this context. Used by Page#rendered_body
to support erb: true opt-in templating in markdown bodies. No
template caching — these strings are one-off per build.
146 147 148 149 |
# File 'lib/abqari/render_context.rb', line 146 def render_string(template) label = page.respond_to?(:source_path) && page.source_path ? "#{page.source_path} (erb body)" : '(erb body)' ErbiTemplate.new(template, label).result(get_binding) end |
#series_url(term, target = page) ⇒ Object
URL of the series index that covers target's collection.
Resolves the owning taxonomy first, so a layout shared between
collections links each one to its own series index:
series_url('Inside Abqari') → "/guides/series/inside-abqari/"
series_url('Module 1', lesson_page) → "/lessons/module/module-1/"
Returns nil when the page's collection has no series taxonomy configured — callers render the series name as plain text rather than linking it somewhere that was never built.
200 201 202 203 |
# File 'lib/abqari/render_context.rb', line 200 def series_url(term, target = page) name = site.series_taxonomy_for(effective_collection_name(target)) name && taxonomy_url(name, term) end |
#taxonomy_url(taxonomy_name, term) ⇒ Object
URL of a taxonomy term page, honouring the configured permalink
so slug-aliasing works. Replaces hardcoded patterns like
"/tags/#{slugify(term)}/" in show-view layouts.
taxonomy_url('tags', 'fiction') → "/tags/fiction/"
taxonomy_url('countries', 'Syria') → "/country/syria/" (per config)
184 185 186 187 188 |
# File 'lib/abqari/render_context.rb', line 184 def taxonomy_url(taxonomy_name, term) cfg = site.taxonomy_config(taxonomy_name.to_s) permalink = cfg['permalink'] || "/#{taxonomy_name}/:slug/" permalink.sub(':slug', slugify(term)) end |
#yield_content ⇒ Object
The output of the inner layout, available inside a wrapping layout
as <%== yield_content %>. Returns '' at the leaf level (the layout
being rendered direct from a page, with no inner layout wrapping it).
The backing ivar is intentionally @_yielded_content (leading
underscore = "internal, don't touch"). ERB templates get the full
binding via get_binding, so every RenderContext ivar is one
<%= @whatever %> typo away from being usable. The underscore
convention plus the RenderContextInternalIvarTest audit pin
the contract: only page and site are intended template-
accessible state; helper method results carry the rest.
128 129 130 |
# File 'lib/abqari/render_context.rb', line 128 def yield_content @_yielded_content.to_s end |