dazzle_backend_sgml/
lib.rs1use dazzle_core::fot::FotBuilder;
44use std::collections::HashSet;
45use std::fs;
46use std::io::{Result, Write};
47use std::path::{Path, PathBuf};
48
49#[derive(Debug)]
56pub struct SgmlBackend {
57 output_dir: PathBuf,
59
60 current_dir: Option<PathBuf>,
63
64 current_buffer: String,
66
67 written_files: HashSet<PathBuf>,
69}
70
71impl SgmlBackend {
72 pub fn new<P: AsRef<Path>>(output_dir: P) -> Self {
86 SgmlBackend {
87 output_dir: output_dir.as_ref().to_path_buf(),
88 current_dir: None,
89 current_buffer: String::new(),
90 written_files: HashSet::new(),
91 }
92 }
93
94 pub fn written_files(&self) -> &HashSet<PathBuf> {
96 &self.written_files
97 }
98}
99
100impl FotBuilder for SgmlBackend {
101 fn entity(&mut self, system_id: &str, content: &str) -> Result<()> {
123 let relative_path = if let Some(ref current_dir) = self.current_dir {
125 current_dir.join(system_id)
126 } else {
127 PathBuf::from(system_id)
128 };
129
130 let output_path = self.output_dir.join(relative_path);
132
133 if let Some(parent) = output_path.parent() {
135 fs::create_dir_all(parent)?;
136 }
137
138 let mut file = fs::File::create(&output_path)?;
140 file.write_all(content.as_bytes())?;
141
142 self.written_files.insert(output_path);
144
145 Ok(())
146 }
147
148 fn formatting_instruction(&mut self, data: &str) -> Result<()> {
171 self.current_buffer.push_str(data);
172 Ok(())
173 }
174
175 fn current_output(&self) -> &str {
179 &self.current_buffer
180 }
181
182 fn clear_buffer(&mut self) {
186 self.current_buffer.clear();
187 }
188
189 fn literal(&mut self, text: &str) -> Result<()> {
190 self.formatting_instruction(text)
192 }
193
194 fn directory(&mut self, path: &str) -> Result<()> {
218 let relative_path = if let Some(ref current_dir) = self.current_dir {
220 current_dir.join(path)
221 } else {
222 PathBuf::from(path)
223 };
224
225 let dir_path = self.output_dir.join(&relative_path);
227 fs::create_dir_all(&dir_path)
228 .map_err(|e| std::io::Error::new(
229 e.kind(),
230 format!("Failed to create directory {}: {}", dir_path.display(), e)
231 ))?;
232
233 let canonical = dir_path.canonicalize()
236 .map_err(|e| std::io::Error::new(
237 e.kind(),
238 format!("Failed to canonicalize directory {}: {}", dir_path.display(), e)
239 ))?;
240 let output_canonical = self.output_dir.canonicalize()
241 .map_err(|e| std::io::Error::new(
242 e.kind(),
243 format!("Failed to canonicalize output_dir {}: {}", self.output_dir.display(), e)
244 ))?;
245 let normalized_relative = canonical.strip_prefix(&output_canonical)
246 .map(|p| p.to_path_buf())
247 .unwrap_or_else(|_| relative_path.clone());
248
249 self.set_current_directory(Some(normalized_relative.to_string_lossy().to_string()));
251
252 Ok(())
253 }
254
255 fn current_directory(&self) -> Option<&str> {
257 self.current_dir.as_ref().and_then(|p| p.to_str())
258 }
259
260 fn set_current_directory(&mut self, path: Option<String>) {
262 self.current_dir = path.map(PathBuf::from);
263 }
264
265 fn start_simple_page_sequence(&mut self) -> Result<()> {
270 Ok(())
272 }
273
274 fn end_simple_page_sequence(&mut self) -> Result<()> {
276 Ok(())
278 }
279
280 fn start_sequence(&mut self) -> Result<()> {
282 Ok(())
284 }
285
286 fn end_sequence(&mut self) -> Result<()> {
288 Ok(())
290 }
291
292 fn start_paragraph(&mut self) -> Result<()> {
294 Ok(())
296 }
297
298 fn end_paragraph(&mut self) -> Result<()> {
300 Ok(())
302 }
303
304 fn start_display_group(&mut self) -> Result<()> {
306 Ok(())
308 }
309
310 fn end_display_group(&mut self) -> Result<()> {
312 Ok(())
314 }
315
316 fn start_line_field(&mut self) -> Result<()> {
318 Ok(())
320 }
321
322 fn end_line_field(&mut self) -> Result<()> {
324 Ok(())
326 }
327}
328
329#[cfg(test)]
330mod tests {
331 use super::*;
332 use std::fs;
333 use tempfile::TempDir;
334
335 #[test]
336 fn test_new_backend() {
337 let backend = SgmlBackend::new("output");
338 assert_eq!(backend.current_output(), "");
339 assert_eq!(backend.written_files().len(), 0);
340 }
341
342 #[test]
343 fn test_formatting_instruction() {
344 let mut backend = SgmlBackend::new("output");
345
346 backend.formatting_instruction("Hello ").unwrap();
347 backend.formatting_instruction("World").unwrap();
348
349 assert_eq!(backend.current_output(), "Hello World");
350 }
351
352 #[test]
353 fn test_clear_buffer() {
354 let mut backend = SgmlBackend::new("output");
355
356 backend.formatting_instruction("Some text").unwrap();
357 assert_eq!(backend.current_output(), "Some text");
358
359 backend.clear_buffer();
360 assert_eq!(backend.current_output(), "");
361 }
362
363 #[test]
364 fn test_entity_creates_file() {
365 let temp_dir = TempDir::new().unwrap();
366 let mut backend = SgmlBackend::new(temp_dir.path());
367
368 backend
369 .entity("test.txt", "Hello, World!")
370 .unwrap();
371
372 let file_path = temp_dir.path().join("test.txt");
373 assert!(file_path.exists());
374
375 let content = fs::read_to_string(&file_path).unwrap();
376 assert_eq!(content, "Hello, World!");
377 }
378
379 #[test]
380 fn test_entity_creates_nested_directories() {
381 let temp_dir = TempDir::new().unwrap();
382 let mut backend = SgmlBackend::new(temp_dir.path());
383
384 backend
385 .entity("src/main/java/Foo.java", "public class Foo {}")
386 .unwrap();
387
388 let file_path = temp_dir.path().join("src/main/java/Foo.java");
389 assert!(file_path.exists());
390 }
391
392 #[test]
393 fn test_entity_tracks_written_files() {
394 let temp_dir = TempDir::new().unwrap();
395 let mut backend = SgmlBackend::new(temp_dir.path());
396
397 backend.entity("file1.txt", "content1").unwrap();
398 backend.entity("file2.txt", "content2").unwrap();
399
400 assert_eq!(backend.written_files().len(), 2);
401 assert!(backend
402 .written_files()
403 .contains(&temp_dir.path().join("file1.txt")));
404 assert!(backend
405 .written_files()
406 .contains(&temp_dir.path().join("file2.txt")));
407 }
408
409 #[test]
410 fn test_combined_workflow() {
411 let temp_dir = TempDir::new().unwrap();
412 let mut backend = SgmlBackend::new(temp_dir.path());
413
414 backend.formatting_instruction("public class Foo {\n").unwrap();
416 backend.formatting_instruction(" // generated\n").unwrap();
417 backend.formatting_instruction("}\n").unwrap();
418
419 let content = backend.current_output().to_string();
421 backend.entity("Foo.java", &content).unwrap();
422
423 backend.clear_buffer();
425
426 let file_path = temp_dir.path().join("Foo.java");
428 let content = fs::read_to_string(&file_path).unwrap();
429 assert_eq!(content, "public class Foo {\n // generated\n}\n");
430 }
431
432 #[test]
433 fn test_directory_creates_dir() {
434 let temp_dir = TempDir::new().unwrap();
435 let mut backend = SgmlBackend::new(temp_dir.path());
436
437 backend.directory("src/models").unwrap();
438
439 let dir_path = temp_dir.path().join("src/models");
440 assert!(dir_path.exists());
441 assert!(dir_path.is_dir());
442 }
443
444 #[test]
445 fn test_directory_creates_nested_dirs() {
446 let temp_dir = TempDir::new().unwrap();
447 let mut backend = SgmlBackend::new(temp_dir.path());
448
449 backend.directory("src/main/java/models").unwrap();
450
451 let dir_path = temp_dir.path().join("src/main/java/models");
452 assert!(dir_path.exists());
453 assert!(dir_path.is_dir());
454 }
455
456 #[test]
457 fn test_directory_idempotent() {
458 let temp_dir = TempDir::new().unwrap();
459 let mut backend = SgmlBackend::new(temp_dir.path());
460
461 backend.directory("src/models").unwrap();
463 backend.directory("src/models").unwrap();
464
465 let dir_path = temp_dir.path().join("src/models");
466 assert!(dir_path.exists());
467 assert!(dir_path.is_dir());
468 }
469
470 #[test]
471 fn test_directory_sets_current_directory() {
472 let temp_dir = TempDir::new().unwrap();
473 let mut backend = SgmlBackend::new(temp_dir.path());
474
475 assert!(backend.current_directory().is_none());
476
477 backend.directory("src/models").unwrap();
478 assert_eq!(backend.current_directory(), Some("src/models"));
479
480 backend.set_current_directory(None);
482 backend.directory("src/controllers").unwrap();
483 assert_eq!(backend.current_directory(), Some("src/controllers"));
484 }
485
486 #[test]
487 fn test_entity_with_relative_path() {
488 let temp_dir = TempDir::new().unwrap();
489 let mut backend = SgmlBackend::new(temp_dir.path());
490
491 backend.directory("generated/models").unwrap();
493
494 backend.entity("User.rs", "pub struct User {}").unwrap();
496
497 let file_path = temp_dir.path().join("generated/models/User.rs");
499 assert!(file_path.exists());
500 let content = fs::read_to_string(&file_path).unwrap();
501 assert_eq!(content, "pub struct User {}");
502 }
503
504 #[test]
505 fn test_entity_with_relative_path_multiple_files() {
506 let temp_dir = TempDir::new().unwrap();
507 let mut backend = SgmlBackend::new(temp_dir.path());
508
509 backend.directory("generated/models").unwrap();
511
512 backend.entity("User.rs", "pub struct User {}").unwrap();
514 backend.entity("Post.rs", "pub struct Post {}").unwrap();
515 backend.entity("Comment.rs", "pub struct Comment {}").unwrap();
516
517 assert!(temp_dir.path().join("generated/models/User.rs").exists());
519 assert!(temp_dir.path().join("generated/models/Post.rs").exists());
520 assert!(temp_dir.path().join("generated/models/Comment.rs").exists());
521 }
522
523 #[test]
524 fn test_directory_switching() {
525 let temp_dir = TempDir::new().unwrap();
526 let mut backend = SgmlBackend::new(temp_dir.path());
527
528 backend.directory("src/models").unwrap();
530 backend.entity("User.rs", "pub struct User {}").unwrap();
531
532 backend.set_current_directory(None);
534 backend.directory("src/controllers").unwrap();
535 backend.entity("UserController.rs", "pub struct UserController {}").unwrap();
536
537 assert!(temp_dir.path().join("src/models/User.rs").exists());
539 assert!(temp_dir.path().join("src/controllers/UserController.rs").exists());
540 }
541
542 #[test]
543 fn test_reset_current_directory() {
544 let temp_dir = TempDir::new().unwrap();
545 let mut backend = SgmlBackend::new(temp_dir.path());
546
547 backend.directory("src/models").unwrap();
549 assert_eq!(backend.current_directory(), Some("src/models"));
550
551 backend.set_current_directory(None);
553 assert!(backend.current_directory().is_none());
554
555 backend.entity("root.txt", "content").unwrap();
557 assert!(temp_dir.path().join("root.txt").exists());
558 }
559
560 #[test]
561 fn test_directory_with_relative_path() {
562 let temp_dir = TempDir::new().unwrap();
563 let mut backend = SgmlBackend::new(temp_dir.path());
564
565 backend.directory("generated").unwrap();
567 assert_eq!(backend.current_directory(), Some("generated"));
568
569 backend.directory("models").unwrap();
571 assert_eq!(backend.current_directory(), Some("generated/models"));
572
573 let dir_path = temp_dir.path().join("generated/models");
575 assert!(dir_path.exists());
576 assert!(dir_path.is_dir());
577 }
578
579 #[test]
580 fn test_directory_nested_relative_paths() {
581 let temp_dir = TempDir::new().unwrap();
582 let mut backend = SgmlBackend::new(temp_dir.path());
583
584 backend.directory("src").unwrap();
586 backend.directory("main").unwrap();
587 backend.directory("java").unwrap();
588 backend.directory("models").unwrap();
589
590 assert_eq!(backend.current_directory(), Some("src/main/java/models"));
592
593 let dir_path = temp_dir.path().join("src/main/java/models");
595 assert!(dir_path.exists());
596 assert!(dir_path.is_dir());
597 }
598
599 #[test]
600 fn test_directory_relative_then_entity() {
601 let temp_dir = TempDir::new().unwrap();
602 let mut backend = SgmlBackend::new(temp_dir.path());
603
604 backend.directory("generated").unwrap();
606 backend.directory("models").unwrap();
607
608 backend.entity("User.rs", "pub struct User {}").unwrap();
610
611 let file_path = temp_dir.path().join("generated/models/User.rs");
613 assert!(file_path.exists());
614 let content = fs::read_to_string(&file_path).unwrap();
615 assert_eq!(content, "pub struct User {}");
616 }
617
618 #[test]
619 fn test_directory_absolute_path_resets() {
620 let temp_dir = TempDir::new().unwrap();
621 let mut backend = SgmlBackend::new(temp_dir.path());
622
623 backend.directory("src").unwrap();
625 backend.directory("models").unwrap();
626 assert_eq!(backend.current_directory(), Some("src/models"));
627
628 backend.set_current_directory(None);
630 backend.directory("tests").unwrap();
631 assert_eq!(backend.current_directory(), Some("tests"));
632
633 assert!(temp_dir.path().join("src/models").exists());
635 assert!(temp_dir.path().join("tests").exists());
636 }
637
638 #[test]
639 fn test_directory_parent_navigation() {
640 let temp_dir = TempDir::new().unwrap();
641 let mut backend = SgmlBackend::new(temp_dir.path());
642
643 backend.directory("src").unwrap();
645 backend.directory("models").unwrap();
646 assert_eq!(backend.current_directory(), Some("src/models"));
647
648 backend.directory("..").unwrap();
650 assert_eq!(backend.current_directory(), Some("src"));
651 backend.directory("controllers").unwrap();
652 assert_eq!(backend.current_directory(), Some("src/controllers"));
653
654 assert!(temp_dir.path().join("src/models").exists());
656 assert!(temp_dir.path().join("src/controllers").exists());
657
658 backend.entity("UserController.rs", "pub struct UserController {}").unwrap();
660 assert!(temp_dir.path().join("src/controllers/UserController.rs").exists());
661 }
662
663 #[test]
664 fn test_directory_natural_nesting() {
665 let temp_dir = TempDir::new().unwrap();
666 let mut backend = SgmlBackend::new(temp_dir.path());
667
668 backend.directory("generated").unwrap();
670
671 backend.directory("models").unwrap();
673 backend.entity("User.rs", "pub struct User {}").unwrap();
674 backend.entity("Post.rs", "pub struct Post {}").unwrap();
675
676 backend.directory("..").unwrap();
678 backend.directory("controllers").unwrap();
679 backend.entity("UserController.rs", "pub struct UserController {}").unwrap();
680
681 backend.directory("..").unwrap();
683 backend.directory("views").unwrap();
684 backend.entity("user.html", "<div>User</div>").unwrap();
685
686 assert!(temp_dir.path().join("generated/models/User.rs").exists());
688 assert!(temp_dir.path().join("generated/models/Post.rs").exists());
689 assert!(temp_dir.path().join("generated/controllers/UserController.rs").exists());
690 assert!(temp_dir.path().join("generated/views/user.html").exists());
691 }
692}