srdf/uml_converter/
uml_converter.rs

1use 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        // show_contents(&tempfile_path).unwrap();
52        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
116/*fn generate_uml_output(
117    &self,
118    maybe_shape: &Option<String>,
119    writer: &mut Box<dyn Write>,
120    mode: &UmlGenerationMode,
121    result_format: &OutputConvertFormat,
122) -> Result<()> {
123    match result_format {
124        OutputConvertFormat::PlantUML => {
125            self.as_plant_uml(writer)?;
126            Ok(())
127        }
128        OutputConvertFormat::SVG => {
129            self.as_image(writer, ImageFormat::SVG, mode)?;
130            Ok(())
131        }
132        OutputConvertFormat::PNG => {
133            self.as_image(writer, ImageFormat::PNG, mode)?;
134            Ok(())
135        }
136        OutputConvertFormat::Default => {
137            self.as_plant_uml(writer)?;
138            Ok(())
139        }
140        _ => Err(anyhow!(
141            "Conversion to UML does not support output format {result_format}"
142        )),
143    }
144}*/
145
146pub enum ImageFormat {
147    SVG,
148    PNG,
149}
150
151#[derive(Debug, Clone, Default)]
152pub enum UmlGenerationMode {
153    /// Show all nodes
154    #[default]
155    AllNodes,
156
157    /// Show only the neighbours of a node
158    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
177/*fn show_dir(path: &path::Path) -> Result<(), io::Error> {
178    let entries = fs::read_dir(path)?;
179    for entry in entries {
180        let entry = entry?;
181        debug!("Entry: {}", entry.path().display());
182    }
183    Ok(())
184}*/
185
186fn copy<W: Write>(file: &mut File, writer: &mut W) -> Result<(), io::Error> {
187    io::copy(file, writer)?;
188    Ok(())
189}