use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct Template {
pub elements: Vec<Element>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Element {
RawText(String),
Action(Action),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Action {
pub trim_left: bool,
pub trim_right: bool,
pub body: ActionBody,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ActionBody {
Comment(String),
If(Pipeline),
ElseIf(Pipeline),
Else,
End,
Range {
vars: Option<RangeVars>,
pipeline: Pipeline,
},
With(Pipeline),
Define(String),
Template {
name: String,
pipeline: Option<Pipeline>,
},
Block { name: String, pipeline: Pipeline },
Pipeline(Pipeline),
}
#[derive(Debug, Clone, PartialEq)]
pub struct RangeVars {
pub index_var: Option<String>,
pub value_var: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Pipeline {
pub decl: Option<String>,
pub commands: Vec<Command>,
}
impl Pipeline {
pub fn simple(cmd: Command) -> Self {
Self {
decl: None,
commands: vec![cmd],
}
}
pub fn with_decl(var: String, commands: Vec<Command>) -> Self {
Self {
decl: Some(var),
commands,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Command {
Field(FieldAccess),
Variable(String),
Function { name: String, args: Vec<Argument> },
Literal(Literal),
Parenthesized(Box<Pipeline>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct FieldAccess {
pub is_root: bool,
pub path: Vec<String>,
}
impl FieldAccess {
pub fn new(path: Vec<String>) -> Self {
Self {
is_root: false,
path,
}
}
pub fn root(path: Vec<String>) -> Self {
Self {
is_root: true,
path,
}
}
pub fn full_path(&self) -> String {
self.path.join(".")
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Argument {
Field(FieldAccess),
Variable(String),
Literal(Literal),
Pipeline(Box<Pipeline>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Literal {
String(String),
Char(char),
Int(i64),
Float(f64),
Bool(bool),
Nil,
}
impl fmt::Display for Literal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Literal::String(s) => write!(f, "\"{}\"", s),
Literal::Char(c) => write!(f, "'{}'", c),
Literal::Int(n) => write!(f, "{}", n),
Literal::Float(n) => write!(f, "{}", n),
Literal::Bool(b) => write!(f, "{}", b),
Literal::Nil => write!(f, "nil"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SourceLocation {
pub line: usize,
pub column: usize,
pub offset: usize,
}
impl Default for SourceLocation {
fn default() -> Self {
Self {
line: 1,
column: 1,
offset: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_access() {
let field = FieldAccess::new(vec!["Values".into(), "image".into(), "tag".into()]);
assert_eq!(field.full_path(), "Values.image.tag");
assert!(!field.is_root);
}
#[test]
fn test_field_access_root() {
let field = FieldAccess::root(vec!["Values".into(), "x".into()]);
assert!(field.is_root);
}
#[test]
fn test_pipeline_simple() {
let pipeline = Pipeline::simple(Command::Variable("x".into()));
assert!(pipeline.decl.is_none());
assert_eq!(pipeline.commands.len(), 1);
}
#[test]
fn test_literal_display() {
assert_eq!(format!("{}", Literal::String("hello".into())), "\"hello\"");
assert_eq!(format!("{}", Literal::Int(42)), "42");
assert_eq!(format!("{}", Literal::Bool(true)), "true");
assert_eq!(format!("{}", Literal::Nil), "nil");
}
}