module

KrikriJinja

Constants

BIGINT_SENTINEL_PREFIX = "\uE000bigint:"

Prefix marking a string as the decimal text of an integer too wide for Int64 (RSA moduli, EC coordinates, big certificate serial numbers), carried through the JSON::Any world krikri's engine boundary speaks. JSON::Any's raw union tops out at Int64, so a host cannot hand the engine a real wide integer directly: the host stores the value as this marked string instead, #from_json_any turns it into a BigIntValue (so templates, tests and comparisons see a real int), and #to_json_any turns a BigIntValue back into the marked string for the return trip. The private-use control character makes a false positive on real string data effectively impossible. krikri keeps a byte-identical constant in Krikri::BIGINT_SENTINEL_PREFIX for the plugin binaries, which link neither this shard nor the jinja engine and so cannot reference it here.

BUILTIN_FILTERS = {} of String => FilterFn
BUILTIN_GLOBALS = {} of String => AnyValue
BUILTIN_TESTS = {} of String => TestFn
KEY_MARKER = "\u0000"

Dict keys are stored as strings; non-string Python keys get a marker prefix so repr/lookup/json can recover their true type and value.

KEYWORDS = ["and", "or", "not", "in", "is", "if", "else", "true", "false", "True", "False", "none", "None", "recursive", "ignore", "missing", "context", "macro"] of ::String

The reserved words that are tokenized as idents but mean something to the parser.

KWARG_FILTERS = ["default", "dictsort", "filesizeformat", "float", "indent", "int", "join", "map", "max", "min", "reject", "rejectattr", "replace", "round", "select", "selectattr", "slice", "sort", "sum", "tojson", "trim", "truncate", "unique", "urlencode", "wordwrap", "groupby", "batch", "urlize", "wordcount", "format", "random"] of ::String

Filters that accept keyword arguments in real Jinja2; map/filter passing must raise for any others (Python raises TypeError instead).

MAX_POSITIONAL = {"center" => 1, "trim" => 1, "indent" => 2, "round" => 2, "batch" => 2, "slice" => 2, "join" => 1, "int" => 2, "float" => 1, "default" => 2, "truncate" => 4, "wordwrap" => 2, "filesizeformat" => 1, "sum" => 2}

Strict positional arity for filters python raises on when over-called.

VERSION = "0.4.36"

Class methods

ansible_strict_undefined

Ansible's strict undefined: raises when an undefined value is USED, and chains through attribute/subscript access on the way there.

Source
assign_one(ctx : Context, target : TargetSpec, value : AnyValue)
Source
assign_targets(ctx : Context, targets : Array(TargetSpec), value : AnyValue)
Source
big_add(a : String, b : String) : String
Source
big_cmp(a : String, b : String) : Int32
Source
big_divmod(a : String, b : String) : Tuple(String, String)

Truncating quotient/remainder with Python's floor adjustment.

Source
big_int_string?(s : String) : Bool

Int64-overflowing decimal strings are the engine's big-int representation; Python reprs them unquoted.

Source
big_mul(a : String, b : String) : String
Source
big_pow(base : Int64, exp : Int64) : String

Decimal-string big-int helpers shared by pow and add overflow paths. Decimal-string bignum: needed when Int64 pow overflows (Python has arbitrary-precision ints).

Source
big_pow_str(base : String, exp : Int64) : String
Source
big_string_cmp(a : String, b : String) : Int32
Source
big_string_float_cmp(s : String, f : Float64) : Int32

Exact comparison of a decimal-string integer against a Float64. Python's int/float comparison is exact arithmetic; converting the digits to Float64 instead both rounds and overflows past ~1.8e308 (an RSA modulus raises on the conversion outright). The float decomposes EXACTLY as mantissa * 2^exponent (Math.frexp is exact for IEEE doubles); both sides are scaled to plain integers by a power of two - computed by repeated doubling with the big-multiplication helper, since the exponent reaches +-1074 - and compared as decimal strings. Returns 0 for a NaN operand only because a total order cannot express Python's all-False NaN comparisons; equality checks NaN separately.

Source
big_string_int_cmp(s : String, i : Int64) : Int32

Exact comparison of a decimal-string integer against an Int64.

Source
big_udivmod(da : String, db : String) : Tuple(String, String)

Unsigned long division of decimal strings; a and b must be non-negative.

Source
big_usub(a : String, b : String) : String
Source
check_int_str_limit(s : String)

CPython 3.11+ refuses int->str beyond 4300 digits.

Source
compare_values(a : AnyValue, b : AnyValue) : Int32

Ordering: returns -1, 0, 1. Raises on incomparable types (Python-like).

Source
contains?(container : AnyValue, item : AnyValue) : Bool

Containment (in).

Source
context(h : Hash(String, V)) : Hash(String, AnyValue) forall V

Builds a boxed context hash from plain Crystal values.

Source
decode_key(k : String) : AnyValue
Source
default_engine
Source
default_globals
Source
default_known_filter?(name : String) : Bool
Source
default_known_test?(name : String) : Bool
Source
derive_engine(loader : Loader | Nil = nil, options : LexerOptions = LexerOptions.new, undefined : Undefined = Undefined.new, host_context : HostContext | Nil = nil) : Engine

Builds an engine that inherits the shared default engine's registered filters, tests, and globals, with the caller's loader, lexer options, and undefined behavior. Use this instead of Engine.new when host extensions must stay visible (for example a .j2 render with its own loader).

Source
dict_key(v : AnyValue) : String
Source
dict_key_alt(v : AnyValue) : String | Nil

Alternate encoding for python's 1 == True key equivalence, or nil.

Source
dict_key_plain(v : AnyValue) : String | Nil

Plain string form of a numeric key, or nil for anything else. Hosts that hand the engine pre-built dicts through a JSON round trip lose their YAML key types (a JSON object's keys are always strings), so an integer YAML key (7:) arrives as the plain string "7" while the subscript still indexes with the real int - the type-preserving encodings above miss it and the lookup wrongly reports undefined (robertdebock.tomcat's _tomcat_unarchive_urls[instance.version] became the literal string "undefined" instead of a download URL). Real Jinja2 matches dict keys by value, so the plain form is tried as a last resort. Strings deliberately get no fallback here: the caller has already tried the string itself, and coercing a string key into a numeric encoding would fabricate matches real Jinja2 does not make.

Source
dict_key_plain_integral(v : AnyValue) : String | Nil

The integral-truncation form of a numeric key ("8" for 8.0) - the int/float key-equivalence fallback dict_lookup tries after the plain string form (Python: d[8] also hits a float 8.0 key, d[8.0] hits an int 8 key).

Source
dict_key_repr(k : String) : String
Source
dict_lookup(raw : Hash(String, AnyValue), key : AnyValue) : AnyValue | Nil

One dict lookup for every call site: the type-preserving encoding, then the True/1 equivalence alternate, then the plain string form, then the integral-truncation form.

Source
escape_html(s : String) : String
Source
evaluate_expression(source : String, variables : Hash(String, JSON::Any) = {} of String => JSON::Any, strict : Bool = false, loader : Loader | Nil = nil, host_context : HostContext | Nil = nil) : JSON::Any | Nil
Source
evaluate_expression_result(source : String, variables : Hash(String, JSON::Any) = {} of String => JSON::Any, strict : Bool = false, loader : Loader | Nil = nil, host_context : HostContext | Nil = nil) : ExpressionResult

Like evaluate_expression, but reports undefined results explicitly so callers can tell them apart from a JSON null value.

Source
evaluate_expression_value(source : String, variables : Hash(String, JSON::Any) = {} of String => JSON::Any, strict : Bool = false, loader : Loader | Nil = nil, host_context : HostContext | Nil = nil) : AnyValue
Source
filesizeformat(bytes : AnyV, base : Int64, prefixes : Array(String), binary : Bool) : String
Source
format_float(v : Float64) : String
Source
from_json_any(x : JSON::Any) : AnyValue
Source
get_attr(obj : AnyValue, name : String | Nil) : AnyValue | Nil
Source
istitle_check(s : String) : Bool

python str.istitle: each cased word starts upper, continues lower

Source
length_of(v : AnyValue) : Int64
Source
miss_count
Source
norm_decimal(s : String) : AnyV
Source
note_miss
Source
parse_expression(source : String, options : LexerOptions = LexerOptions.new) : Nodes::ExprNode
Source
percent_encode(s : String, extra_safe : String = "") : String

Percent-encoding matching urllib.parse.quote (space becomes %20).

Source
py_format(fmt : String, args : Array(AnyValue), tuple_arg : Bool = true) : String

Python %-style formatting (the format filter). tuple_arg marks a parenthesized right operand; CPython only errors on leftover args for tuples and scalars, never for lists/dicts/undefined.

Source
py_repr_string(s : String) : String

CPython str repr: single quotes preferred, double when the value contains ' but not ", backslash/quote/control chars escaped.

Source
quote_plus(s : String) : String

Percent-encoding matching urllib.parse.quote_plus (space becomes +).

Source
register_default_filter(name : String, &block : FilterFn) : Engine
Source
register_default_function(name : String, &block : FunctionFn) : Engine
Source
register_default_global(name : String, value) : Engine
Source
register_default_json_filter(name : String, &block : JsonFilterFn) : Engine
Source
register_default_json_function(name : String, &block : JsonFunctionFn) : Engine
Source
register_default_json_test(name : String, &block : JsonTestFn) : Engine
Source
register_default_loader_function(name : String, template_name : String) : Engine
Source
register_default_test(name : String, &block : TestFn) : Engine
Source
render(source : String, variables : Hash(String, AnyValue), loader : Loader | Nil = nil, host_context : HostContext | Nil = nil) : String
Source
render(source : String, variables : Hash(String, V) = {} of String => String, loader : Loader | Nil = nil, host_context : HostContext | Nil = nil) : String forall V
Source
replace_limited(s : String, old : String, new : String, count : Int64) : String
Source
reset_default_engine
Source
str_format(fmt : String, args : Array(AnyValue)) : String

str.format: positional ({} and {N}) replacement with {{ }} escapes.

Source
stringify(value : AnyValue, escape : Bool = false) : String
Source
stringify_repr(value : AnyValue) : String

repr-style for lists/dicts inside stringification.

Source
to_iterable(v : AnyValue) : Array(AnyValue)
Source
to_json_any(value : AnyValue) : JSON::Any

Converts a JSON::Any value into a boxed template value.

Source
to_json_value(v : AnyValue, indent : Int64 | Nil = nil) : String
Source
truthy?(value : AnyValue) : Bool

Truthiness: Python-style (0, "", [], {}, nil are falsy).

Source
type_label(value : AnyValue) : String

Python's type name for a value, as error messages report it.

Source
undefined?(value : AnyValue) : Bool
Source
undefined_message(value : Undefined) : String

The message real Jinja2/Ansible reports names the variable that was missing; fall back to a generic word when the name is unknown.

Source
values_equal(a : AnyValue, b : AnyValue) : Bool

Python-like equality/comparison across values.

Source
wrap_value(x) : AnyValue

Deep-converts plain Crystal values (nested hashes/arrays of mixed types) into boxed template values.

Source

Nested types