1pub mod ast;
8pub mod generator;
9pub mod lexer;
10pub mod parser;
11
12pub use ast::{Block, Document, Value};
13pub use generator::{generate, generate_from_map, Generator, GeneratorConfig};
14pub use lexer::{Lexer, Location, Span, Token, TokenWithSpan};
15pub use parser::{parse, ParseError};
16
17pub fn parse_acl(input: &str) -> Result<Document, ParseError> {
40 parse(input)
41}
42
43pub fn generate_acl(doc: &Document) -> String {
67 generate(doc)
68}
69
70pub mod builder {
72 use crate::ast::{Block, Document, Value};
73 use std::collections::HashMap;
74
75 pub struct DocumentBuilder {
77 blocks: Vec<Block>,
78 }
79
80 impl DocumentBuilder {
81 pub fn new() -> Self {
82 Self { blocks: Vec::new() }
83 }
84
85 pub fn block(mut self, block: Block) -> Self {
87 self.blocks.push(block);
88 self
89 }
90
91 pub fn kv_block(mut self, name: &str, key: &str, value: Value) -> Self {
93 let mut attrs = HashMap::new();
94 attrs.insert(key.to_string(), value);
95 self.blocks.push(Block {
96 name: name.to_string(),
97 labels: Vec::new(),
98 blocks: Vec::new(),
99 attributes: attrs,
100 });
101 self
102 }
103
104 pub fn build(self) -> Document {
106 Document {
107 blocks: self.blocks,
108 }
109 }
110 }
111
112 impl Default for DocumentBuilder {
113 fn default() -> Self {
114 Self::new()
115 }
116 }
117
118 pub struct BlockBuilder {
120 name: String,
121 labels: Vec<String>,
122 blocks: Vec<Block>,
123 attributes: HashMap<String, Value>,
124 }
125
126 impl BlockBuilder {
127 pub fn new(name: &str) -> Self {
128 Self {
129 name: name.to_string(),
130 labels: Vec::new(),
131 blocks: Vec::new(),
132 attributes: HashMap::new(),
133 }
134 }
135
136 pub fn label(mut self, label: &str) -> Self {
137 self.labels.push(label.to_string());
138 self
139 }
140
141 pub fn attr(mut self, key: &str, value: Value) -> Self {
142 self.attributes.insert(key.to_string(), value);
143 self
144 }
145
146 pub fn nested_block(mut self, block: Block) -> Self {
147 self.blocks.push(block);
148 self
149 }
150
151 pub fn build(self) -> Block {
152 Block {
153 name: self.name,
154 labels: self.labels,
155 blocks: self.blocks,
156 attributes: self.attributes,
157 }
158 }
159 }
160
161 pub fn string(s: &str) -> Value {
163 Value::String(s.to_string())
164 }
165
166 pub fn number(n: f64) -> Value {
167 Value::Number(n)
168 }
169
170 pub fn integer(n: i64) -> Value {
171 Value::Number(n as f64)
172 }
173
174 pub fn boolean(b: bool) -> Value {
175 Value::Bool(b)
176 }
177
178 pub fn null() -> Value {
179 Value::Null
180 }
181
182 pub fn list(items: Vec<Value>) -> Value {
183 Value::List(items)
184 }
185
186 pub fn call(name: &str, args: Vec<Value>) -> Value {
187 Value::Call(name.to_string(), args)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_parse_acl_function() {
197 let input = r#"
198 name = "test"
199 count = 42
200 "#;
201 let doc = parse_acl(input).unwrap();
202 assert_eq!(doc.blocks.len(), 2);
203 }
204
205 #[test]
206 fn test_generate_acl_function() {
207 let doc = Document {
208 blocks: vec![Block {
209 name: "test".to_string(),
210 labels: vec![],
211 blocks: vec![],
212 attributes: vec![("key".to_string(), Value::String("value".to_string()))]
213 .into_iter()
214 .collect(),
215 }],
216 };
217 let output = generate_acl(&doc);
218 assert!(output.contains("test"));
219 assert!(output.contains("value"));
220 }
221
222 #[test]
223 fn test_builder_document_builder() {
224 let doc = builder::DocumentBuilder::new()
225 .kv_block("config", "key", builder::string("value"))
226 .build();
227
228 assert_eq!(doc.blocks.len(), 1);
229 assert_eq!(doc.blocks[0].name, "config");
230 }
231
232 #[test]
233 fn test_builder_document_builder_with_block() {
234 let block = builder::BlockBuilder::new("provider")
235 .attr("name", builder::string("openai"))
236 .build();
237
238 let doc = builder::DocumentBuilder::new().block(block).build();
239
240 assert_eq!(doc.blocks.len(), 1);
241 assert_eq!(doc.blocks[0].name, "provider");
242 }
243
244 #[test]
245 fn test_builder_document_default() {
246 let doc = builder::DocumentBuilder::default().build();
247 assert!(doc.blocks.is_empty());
248 }
249
250 #[test]
251 fn test_builder_block_builder() {
252 let block = builder::BlockBuilder::new("provider")
253 .label("openai")
254 .attr("api_key", builder::string("sk-xxx"))
255 .attr("enabled", builder::boolean(true))
256 .build();
257
258 assert_eq!(block.name, "provider");
259 assert_eq!(block.labels, vec!["openai"]);
260 assert_eq!(
261 block
262 .attributes
263 .get("api_key")
264 .map(|v| v.to_string())
265 .unwrap(),
266 "sk-xxx"
267 );
268 }
269
270 #[test]
271 fn test_builder_block_builder_nested() {
272 let inner = builder::BlockBuilder::new("model")
273 .attr("name", builder::string("gpt-4"))
274 .build();
275
276 let block = builder::BlockBuilder::new("provider")
277 .label("openai")
278 .nested_block(inner)
279 .build();
280
281 assert_eq!(block.blocks.len(), 1);
282 assert_eq!(block.blocks[0].name, "model");
283 }
284
285 #[test]
286 fn test_builder_value_helpers() {
287 assert_eq!(builder::string("test"), Value::String("test".to_string()));
288 assert_eq!(builder::number(3.14), Value::Number(3.14));
289 assert_eq!(builder::integer(42), Value::Number(42.0));
290 assert_eq!(builder::boolean(true), Value::Bool(true));
291 assert_eq!(builder::boolean(false), Value::Bool(false));
292 assert_eq!(builder::null(), Value::Null);
293 assert_eq!(
294 builder::list(vec![Value::Number(1.0)]),
295 Value::List(vec![Value::Number(1.0)])
296 );
297 }
298
299 #[test]
300 fn test_re_exports() {
301 let _ = Document::default();
303 let _ = Block {
304 name: "test".to_string(),
305 labels: vec![],
306 blocks: vec![],
307 attributes: std::collections::HashMap::new(),
308 };
309 let _ = Value::String("test".to_string());
310 let _ = Generator::new();
311 let _ = GeneratorConfig::default();
312 let _ = Lexer::new("");
313 let _ = Location {
314 line: 1,
315 column: 1,
316 offset: 0,
317 };
318 let _ = Span::default();
319 let _ = Token::Eof;
320 let _ = TokenWithSpan::new(Token::Eof, Location::default(), Location::default());
321 let _ = ParseError {
322 message: "test".to_string(),
323 line: 1,
324 column: 1,
325 };
326 }
327
328 #[test]
329 fn test_lexer_re_exports() {
330 let mut lexer = Lexer::new("test = 42");
331 let tokens = lexer.tokenize();
332 assert!(!tokens.is_empty());
333 }
334
335 #[test]
336 fn test_token_with_span() {
337 let loc = Location {
338 line: 1,
339 column: 1,
340 offset: 0,
341 };
342 let token = TokenWithSpan::new(Token::Ident("test".to_string()), loc, loc);
343 assert!(matches!(token.token, Token::Ident(_)));
344 }
345}