// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// Represents an attribute in a Pug element
pub struct Attribute {
name : String
value : String? // None for boolean attributes, Some("") for empty values
unescaped : Bool // True for != syntax, false for = syntax
} derive(Show, Eq, ToJson)
///|
/// Locals map for template interpolation
pub struct Locals(Map[String, String])
///|
pub fn Locals::new() -> Locals {
Locals({})
}
///|
pub fn Locals::get(self : Locals, key : String) -> String? {
self.0.get(key)
}
///|
pub fn Locals::set(self : Locals, key : String, value : String) -> Unit {
self.0[key] = value
}
///|
/// Set an array value (stub for future implementation)
pub fn Locals::set_array(
self : Locals,
key : String,
values : Array[String],
) -> Unit {
// Store as JSON-like array representation for now
let buf = StringBuilder::new()
buf.write_string("[")
for i, v in values {
if i > 0 {
buf.write_string(",")
}
buf.write_string("\"")
buf.write_string(v)
buf.write_string("\"")
}
buf.write_string("]")
self.0[key] = buf.to_string()
}
///|
/// Set an object value (stub for future implementation)
pub fn Locals::set_object(
self : Locals,
key : String,
obj : Map[String, String],
) -> Unit {
// Store as JSON-like object representation for now
let buf = StringBuilder::new()
buf.write_string("{")
let mut first = true
for k, v in obj {
if not(first) {
buf.write_string(",")
}
first = false
buf.write_string("\"")
buf.write_string(k)
buf.write_string("\":\"")
buf.write_string(v)
buf.write_string("\"")
}
buf.write_string("}")
self.0[key] = buf.to_string()
}
///|
/// Set a nested array value (stub for future implementation)
pub fn Locals::set_nested_array(
self : Locals,
key : String,
rows : Array[Array[String]],
) -> Unit {
// Store as JSON-like nested array representation for now
let buf = StringBuilder::new()
buf.write_string("[")
for i, row in rows {
if i > 0 {
buf.write_string(",")
}
buf.write_string("[")
for j, cell in row {
if j > 0 {
buf.write_string(",")
}
buf.write_string("\"")
buf.write_string(cell)
buf.write_string("\"")
}
buf.write_string("]")
}
buf.write_string("]")
self.0[key] = buf.to_string()
}
///|
/// Represents a node in the Pug AST
pub enum Node {
/// An HTML element with tag, id, classes, attributes, and children
Element(
tag~ : String,
id~ : String,
classes~ : Array[String],
attributes~ : Array[Attribute],
children~ : Array[Node],
self_closing~ : Bool
)
/// Plain text content
Text(String)
/// Interpolated variable #{name} (escaped)
Interpolation(String)
/// Unescaped interpolated variable !{name}
UnescapedInterpolation(String)
/// A comment (can be rendered or not)
Comment(text~ : String, render~ : Bool)
/// Document type declaration
Doctype(String)
/// Conditional (if/unless with optional else)
Conditional(
condition~ : String,
if_true~ : Array[Node],
if_false~ : Array[Node],
is_unless~ : Bool
)
/// Each loop (each item in collection)
Each(
item_var~ : String,
index_var~ : String, // Empty string if no index variable
collection~ : String,
body~ : Array[Node],
else_body~ : Array[Node]
) // For "each ... else" when collection is empty
/// Case/switch statement
Case(
expr~ : String, // Variable to match against
cases~ : Array[(String, Array[Node])], // (value, body) pairs for when clauses
default~ : Array[Node]
) // Default case body
/// When clause (used during parsing, not in final AST)
When(value~ : String, body~ : Array[Node])
/// Default clause (used during parsing, not in final AST)
Default(body~ : Array[Node])
/// Mixin definition (params are (name, default_value) pairs, rest params prefixed with "...")
MixinDef(
name~ : String,
params~ : Array[(String, String)],
body~ : Array[Node]
)
/// Mixin call (block is content passed to the mixin, attrs are extra attributes)
MixinCall(
name~ : String,
args~ : Array[String],
block~ : Array[Node],
attrs~ : Array[(String, String)]
)
/// Block placeholder (renders mixin block content)
Block
/// Variable assignment (- var name = value)
VarAssign(name~ : String, value~ : String)
/// While loop
While(condition~ : String, body~ : Array[Node])
/// Named block (for template inheritance)
/// mode: "replace" (default), "append", or "prepend"
NamedBlock(name~ : String, body~ : Array[Node], mode~ : String)
/// Include statement (for template inclusion)
Include(path~ : String)
/// Include with filter (for filtered template inclusion)
IncludeFiltered(filter~ : String, path~ : String)
/// Extends statement (for template inheritance)
Extends(path~ : String)
/// Filter statement (:filter_name content)
Filter(name~ : String, content~ : String)
} derive(Show, Eq, ToJson)
///|
/// A Pug document is a list of nodes
pub struct Document {
nodes : Array[Node]
} derive(ToJson)
///|
pub fn Document::new() -> Document {
{ nodes: [] }
}
///|
pub fn Document::push(self : Document, node : Node) -> Unit {
self.nodes.push(node)
}
///|
pub fn Document::iter(self : Document) -> Iter[Node] {
self.nodes.iter()
}
///|
/// Template registry for storing and resolving templates
/// Used for extends and include functionality
pub struct TemplateRegistry {
templates : Map[String, String] // path -> source code
}
///|
pub fn TemplateRegistry::new() -> TemplateRegistry {
{ templates: {} }
}
///|
/// Register a template with a path
pub fn TemplateRegistry::register(
self : TemplateRegistry,
path : String,
source : String,
) -> Unit {
self.templates[path] = source
}
///|
/// Get a template by path
pub fn TemplateRegistry::get(self : TemplateRegistry, path : String) -> String? {
self.templates.get(path)
}
///|
/// Check if a template exists
pub fn TemplateRegistry::contains(
self : TemplateRegistry,
path : String,
) -> Bool {
self.templates.contains(path)
}
///|
/// Load a template from file and register it
pub fn TemplateRegistry::load_file(
self : TemplateRegistry,
path : String,
) -> Unit raise @fs.IOError {
let source = @fs.read_file_to_string(path)
self.templates[path] = source
}
///|
/// Load a template from file if not already loaded
pub fn TemplateRegistry::ensure_loaded(
self : TemplateRegistry,
path : String,
) -> Unit raise @fs.IOError {
if not(self.templates.contains(path)) {
self.load_file(path)
}
}