///|
struct TemplateCatalog {
entries : Map[String, Template]
ordered_names : Array[String]
metadata : Array[CatalogMetadata]
}
///|
priv struct CatalogMetadata {
expression_count : Int32
variable_count : Int32
max_level : Int32
}
///|
pub(all) struct CatalogError {
name : String
error : TemplateError
} derive(Debug)
///|
/// Compile a bounded directory once; later lookup reuses parsed templates.
pub fn compile_catalog(
entries : Array[(String, String)],
max_templates~ : Int32 = 256,
max_total_codepoints~ : Int32 = 4194304,
limits~ : ParseLimits = ParseLimits::new(),
strict~ : Bool = false,
) -> Result[TemplateCatalog, CatalogError] {
if max_templates < 0 || max_total_codepoints < 0 {
return Err({
name: "",
error: problem("InvalidLimit", "negative catalog budget", 0, 0),
})
}
if entries.length() > max_templates {
return Err({
name: "",
error: problem("CatalogLimit", "too many templates", 0, 0),
})
}
match parse_with_limits("", limits, strict~) {
Err(error) => return Err({ name: "", error, })
Ok(_) => ()
}
let compiled : Map[String, Template] = Map([])
let mut remaining = max_total_codepoints
for (name, source) in entries {
if name == "" || compiled.contains(name) {
return Err({
name,
error: problem("CatalogName", "empty or duplicate template name", 0, 0),
})
}
for text in [name, source] {
for _ in text.iter() {
if remaining == 0 {
return Err({
name,
error: problem(
"CatalogInputLimit", "catalog source exceeds total budget", 0, 0,
),
})
}
remaining = remaining - 1
}
}
match parse_with_limits(source, limits, strict~) {
Err(error) => return Err({ name, error, })
Ok(t) => compiled[name] = t
}
}
let ordered_names = compiled.keys().to_array()
ordered_names.sort()
let metadata = ordered_names.map(fn(name) {
let t = compiled[name]
let mut expression_count = 0
let mut max_level = 0
for segment in t.segments {
if segment is Expression(op, specs) {
expression_count = expression_count + 1
max_level = Int32::max(max_level, expression_level(op, specs))
}
}
{ expression_count, variable_count: t.variables().length(), max_level, }
})
Ok({ entries: compiled, ordered_names, metadata, })
}
///|
pub fn TemplateCatalog::get(
self : TemplateCatalog,
name : String,
) -> Result[Template, CatalogError] {
match self.entries.get(name) {
Some(t) => Ok(t)
None =>
Err({
name,
error: problem(
"UnknownTemplate", "template name is not in catalog", 0, 0,
),
})
}
}
///|
pub fn TemplateCatalog::names(self : TemplateCatalog) -> Array[String] {
self.ordered_names.copy()
}
///|
pub fn TemplateCatalog::templates_using(
self : TemplateCatalog,
variable : String,
) -> Array[String] {
self.names().filter(name => self.entries[name].variables().contains(variable))
}
///|
pub fn TemplateCatalog::measure(
self : TemplateCatalog,
name : String,
variables : Variables,
required~ : Array[String] = [],
limits~ : ExpansionLimits = ExpansionLimits::new(),
) -> Result[Int32, CatalogError] {
match self.get(name) {
Err(e) => Err(e)
Ok(t) =>
match t.measure(variables, required~, limits~) {
Ok(n) => Ok(n)
Err(error) => Err({ name, error, })
}
}
}
///|
pub fn TemplateCatalog::expand(
self : TemplateCatalog,
name : String,
variables : Variables,
required~ : Array[String] = [],
reject_unused~ : Bool = false,
limits~ : ExpansionLimits = ExpansionLimits::new(),
) -> Result[String, CatalogError] {
match self.get(name) {
Err(e) => Err(e)
Ok(t) =>
match t.expand(variables, required~, reject_unused~, limits~) {
Ok(s) => Ok(s)
Err(error) => Err({ name, error, })
}
}
}
///|
pub extend CatalogError with Debug::{to_repr}