srdf/uml_converter/
uml_converter.rs1use std::{
2 fs::{self, File},
3 io::{self, Write},
4 path::{self, Path},
5 process::Command,
6};
7
8use tempfile::TempDir;
9use tracing::{debug, Level};
10
11use crate::UmlConverterError;
12
13pub trait UmlConverter {
14 fn as_plantuml<W: Write>(
15 &self,
16 writer: &mut W,
17 mode: &UmlGenerationMode,
18 ) -> Result<(), UmlConverterError>;
19
20 fn as_image<W: Write, P: AsRef<Path>>(
21 &self,
22 writer: &mut W,
23 image_format: ImageFormat,
24 mode: &UmlGenerationMode,
25 plantuml_path: P,
26 ) -> Result<(), UmlConverterError> {
27 if let Err(e) = plantuml_path.as_ref().try_exists() {
28 return Err(UmlConverterError::NoPlantUMLFile {
29 path: plantuml_path.as_ref().display().to_string(),
30 error: e.to_string(),
31 });
32 }
33 let tempdir = TempDir::new().map_err(|e| UmlConverterError::TempFileError {
34 error: e.to_string(),
35 })?;
36
37 let tempdir_path = tempdir.path();
38 let tempfile_path = tempdir_path.join("temp.uml");
39 let tempfile_name = tempfile_path.display().to_string();
40 self.save_uml_to_tempfile(&tempfile_path, &tempfile_name, mode)?;
41 debug!("ShEx contents stored in temporary file:{}", tempfile_name);
42 if tracing::enabled!(Level::DEBUG) {
43 show_contents(&tempfile_path).unwrap();
44 }
45
46 let (out_param, out_file_name) = match image_format {
47 ImageFormat::PNG => ("-png", tempdir_path.join("temp.png")),
48 ImageFormat::SVG => ("-svg", tempdir_path.join("temp.svg")),
49 };
50
51 let mut command = Command::new("java");
53 let output = command
54 .arg("-jar")
55 .arg(plantuml_path.as_ref().display().to_string())
56 .arg("-o")
57 .arg(tempdir_path.to_string_lossy().to_string())
58 .arg(out_param)
59 .arg("--verbose")
60 .arg(tempfile_name)
61 .output()
62 .expect("Error executing PlantUML command");
63 let stdout = String::from_utf8_lossy(&output.stdout);
64 debug!("stdout:\n{}", stdout);
65
66 let stderr = String::from_utf8_lossy(&output.stderr);
67 debug!("stderr:\n{}", stderr);
68 let command_name = format!("{:?}", &command);
69 debug!("PLANTUML COMMAND:\n{command_name}");
70 let result = command.output();
71 match result {
72 Ok(_) => {
73 let mut temp_file = File::open(out_file_name.as_path()).map_err(|e| {
74 UmlConverterError::CantOpenGeneratedTempFile {
75 generated_name: out_file_name.display().to_string(),
76 error: e,
77 }
78 })?;
79 copy(&mut temp_file, writer).map_err(|e| UmlConverterError::CopyingTempFile {
80 temp_name: out_file_name.display().to_string(),
81 error: e,
82 })?;
83 Ok(())
84 }
85 Err(e) => Err(UmlConverterError::PlantUMLCommandError {
86 command: command_name,
87 error: e.to_string(),
88 }),
89 }
90 }
91
92 fn save_uml_to_tempfile(
93 &self,
94 tempfile_path: &std::path::Path,
95 tempfile_name: &str,
96 mode: &UmlGenerationMode,
97 ) -> Result<(), UmlConverterError> {
98 let mut file =
99 File::create(tempfile_path).map_err(|e| UmlConverterError::CreatingTempUMLFile {
100 tempfile_name: tempfile_name.to_string(),
101 error: e.to_string(),
102 })?;
103 self.as_plantuml(&mut file, mode)
104 .map_err(|e| UmlConverterError::UmlError {
105 error: e.to_string(),
106 })?;
107 file.flush()
108 .map_err(|e| UmlConverterError::FlushingTempUMLFile {
109 tempfile_name: tempfile_name.to_string(),
110 error: e.to_string(),
111 })?;
112 Ok(())
113 }
114}
115
116pub enum ImageFormat {
147 SVG,
148 PNG,
149}
150
151#[derive(Debug, Clone, Default)]
152pub enum UmlGenerationMode {
153 #[default]
155 AllNodes,
156
157 Neighs(String),
159}
160
161impl UmlGenerationMode {
162 pub fn all() -> UmlGenerationMode {
163 UmlGenerationMode::AllNodes
164 }
165
166 pub fn neighs(node: &str) -> UmlGenerationMode {
167 UmlGenerationMode::Neighs(node.to_string())
168 }
169}
170
171fn show_contents(path: &path::Path) -> Result<(), io::Error> {
172 let contents = fs::read_to_string(path)?;
173 debug!("Contents of {}:\n{}", path.display(), contents);
174 Ok(())
175}
176
177fn copy<W: Write>(file: &mut File, writer: &mut W) -> Result<(), io::Error> {
187 io::copy(file, writer)?;
188 Ok(())
189}