1use std::fmt;
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct Template {
11 pub elements: Vec<Element>,
12}
13
14#[derive(Debug, Clone, PartialEq)]
16pub enum Element {
17 RawText(String),
19 Action(Action),
21}
22
23#[derive(Debug, Clone, PartialEq)]
25pub struct Action {
26 pub trim_left: bool,
28 pub trim_right: bool,
30 pub body: ActionBody,
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub enum ActionBody {
37 Comment(String),
39 If(Pipeline),
41 ElseIf(Pipeline),
43 Else,
45 End,
47 Range {
49 vars: Option<RangeVars>,
51 pipeline: Pipeline,
53 },
54 With(Pipeline),
56 Define(String),
58 Template {
60 name: String,
61 pipeline: Option<Pipeline>,
62 },
63 Block { name: String, pipeline: Pipeline },
65 Pipeline(Pipeline),
67}
68
69#[derive(Debug, Clone, PartialEq)]
71pub struct RangeVars {
72 pub index_var: Option<String>,
74 pub value_var: String,
76}
77
78#[derive(Debug, Clone, PartialEq)]
80pub struct Pipeline {
81 pub decl: Option<String>,
83 pub commands: Vec<Command>,
85}
86
87impl Pipeline {
88 pub fn simple(cmd: Command) -> Self {
90 Self {
91 decl: None,
92 commands: vec![cmd],
93 }
94 }
95
96 pub fn with_decl(var: String, commands: Vec<Command>) -> Self {
98 Self {
99 decl: Some(var),
100 commands,
101 }
102 }
103}
104
105#[derive(Debug, Clone, PartialEq)]
107pub enum Command {
108 Field(FieldAccess),
110 Variable(String),
112 Function { name: String, args: Vec<Argument> },
114 Literal(Literal),
116 Parenthesized(Box<Pipeline>),
118}
119
120#[derive(Debug, Clone, PartialEq)]
122pub struct FieldAccess {
123 pub is_root: bool,
125 pub path: Vec<String>,
127}
128
129impl FieldAccess {
130 pub fn new(path: Vec<String>) -> Self {
131 Self {
132 is_root: false,
133 path,
134 }
135 }
136
137 pub fn root(path: Vec<String>) -> Self {
138 Self {
139 is_root: true,
140 path,
141 }
142 }
143
144 pub fn full_path(&self) -> String {
146 self.path.join(".")
147 }
148}
149
150#[derive(Debug, Clone, PartialEq)]
152pub enum Argument {
153 Field(FieldAccess),
154 Variable(String),
155 Literal(Literal),
156 Pipeline(Box<Pipeline>),
157}
158
159#[derive(Debug, Clone, PartialEq)]
161pub enum Literal {
162 String(String),
163 Char(char),
164 Int(i64),
165 Float(f64),
166 Bool(bool),
167 Nil,
168}
169
170impl fmt::Display for Literal {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 match self {
173 Literal::String(s) => write!(f, "\"{}\"", s),
174 Literal::Char(c) => write!(f, "'{}'", c),
175 Literal::Int(n) => write!(f, "{}", n),
176 Literal::Float(n) => write!(f, "{}", n),
177 Literal::Bool(b) => write!(f, "{}", b),
178 Literal::Nil => write!(f, "nil"),
179 }
180 }
181}
182
183#[derive(Debug, Clone, PartialEq)]
185pub struct SourceLocation {
186 pub line: usize,
187 pub column: usize,
188 pub offset: usize,
189}
190
191impl Default for SourceLocation {
192 fn default() -> Self {
193 Self {
194 line: 1,
195 column: 1,
196 offset: 0,
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_field_access() {
207 let field = FieldAccess::new(vec!["Values".into(), "image".into(), "tag".into()]);
208 assert_eq!(field.full_path(), "Values.image.tag");
209 assert!(!field.is_root);
210 }
211
212 #[test]
213 fn test_field_access_root() {
214 let field = FieldAccess::root(vec!["Values".into(), "x".into()]);
215 assert!(field.is_root);
216 }
217
218 #[test]
219 fn test_pipeline_simple() {
220 let pipeline = Pipeline::simple(Command::Variable("x".into()));
221 assert!(pipeline.decl.is_none());
222 assert_eq!(pipeline.commands.len(), 1);
223 }
224
225 #[test]
226 fn test_literal_display() {
227 assert_eq!(format!("{}", Literal::String("hello".into())), "\"hello\"");
228 assert_eq!(format!("{}", Literal::Int(42)), "42");
229 assert_eq!(format!("{}", Literal::Bool(true)), "true");
230 assert_eq!(format!("{}", Literal::Nil), "nil");
231 }
232}