class

Krikri::VariableSubstitutor::LazyCrinjaContext

Inherits Crinja::Context < Crinja::Util::ScopeMap < Reference < Object

A Crinja::Context backed directly by the raw Hash(String, JSON::Any) variable scope, converting one entry to Crinja::Value only when a template actually reads it - see CrinjaRenderer#build_lazy_context's own comment for why (turns O(all vars) per renderer into O(vars a template actually references), typically 1-5 out of a context that can run into the thousands on a real hardening/collection role).

Safe to subclass this way because Crinja::Context < Crinja::Util:: ScopeMap(String, Crinja::Value), and #[]/#has_key? are the ONLY two ScopeMap methods anything in lib/crinja ever calls on a context (Resolver#resolve for #[]; the {% from %} import tag for #has_key? - checked directly, not assumed, via grep -rn 'context\[|context\.has_key\?' lib/crinja/src) - keys/values/ entries/session_bindings are never read, so a context that only implements those two lazily is behaviorally identical to one that eagerly materializes the whole scope up front.

Constructors

new(raw_vars : Hash(String, JSON::Any), substitutor : VarSubstitutor, parent : Crinja::Context | Nil = nil)
Source

Instance methods

[](key : String) : Crinja::Value

Precedence, matching the eager version's exact behavior (git history - finish_crinja_vars): the "vars" magic dict always wins over a real variable literally named "vars" (checked first); a real variable always wins over the "omit" sentinel default (checked only once @raw_vars itself doesn't have the key, i.e. the old vars["omit"] ||= ...); a real variable is otherwise looked up and memoized into scope on first read; anything else falls through to the parent context (normally shared_env.context, the process root - practically always empty) or, failing that, Undefined.

Source
has_key?(key : String) : Bool
Source
keys

ScopeMap#keys (and, in terms of it, the base class's own values/entries/has_value?) is real - lookup('varnames', pattern) (jinja_filters.cr's own env.context.keys.select { ... }) genuinely needs every variable NAME in scope to pattern-match against, not just the ones some earlier expression happened to read. The original "nothing in lib/crinja ever calls keys/entries/values on a context" audit only grepped lib/crinja/src - true for the vendored library itself, but missed this codebase's OWN lookup('varnames', ...) implementation, found the hard way when crystal spec's full suite (not this file in isolation - the pre-existing require-ordering gap the isolation run hits masked it) caught 2 real failures. Returning just the NAMES (not converting every value) keeps this cheap - O(N) string collection, not O(N) full per-key conversion - so varnames stays correct without reintroducing the eager-conversion cost this whole class exists to avoid.

Source