cu29_rendercfg/
rendercfg.rs1mod config;
2use clap::Parser;
3use config::{ConfigGraphs, read_configuration};
4pub use cu29_traits::*;
5use std::io::Cursor;
6use std::io::Write;
7use std::path::PathBuf;
8use std::process::{Command, Stdio};
9use tempfile::Builder;
10
11#[derive(Parser)]
12#[clap(author, version, about, long_about = None)]
13struct Args {
14 #[clap(value_parser)]
16 config: PathBuf,
17 #[clap(long)]
19 mission: Option<String>,
20 #[clap(long, action)]
22 list_missions: bool,
23 #[clap(long)]
25 open: bool,
26}
27
28fn main() -> std::io::Result<()> {
30 let args = Args::parse();
32
33 let config = read_configuration(args.config.to_str().unwrap())
34 .expect("Failed to read configuration file");
35
36 if args.list_missions {
37 print_mission_list(&config);
38 return Ok(());
39 }
40
41 let mission = match validate_mission_arg(&config, args.mission.as_deref()) {
42 Ok(mission) => mission,
43 Err(err) => {
44 eprintln!("{err}");
45 std::process::exit(1);
46 }
47 };
48
49 let mut content = Vec::<u8>::new();
50 {
51 let mut cursor = Cursor::new(&mut content);
52 config.render(&mut cursor, mission.as_deref()).unwrap();
53 }
54
55 let mut child = Command::new("dot")
57 .arg("-Tsvg")
58 .stdin(Stdio::piped())
59 .stdout(Stdio::piped())
60 .spawn()
61 .expect("Failed to start dot process");
62
63 {
64 let stdin = child.stdin.as_mut().expect("Failed to open stdin");
65 let result = stdin.write_all(&content);
66 if let Err(e) = result {
67 eprintln!("Failed to write to stdin of the dot process: {e}");
68 std::process::exit(1);
69 }
70 }
71
72 let output = child.wait_with_output().expect("Failed to read stdout");
73
74 if !output.status.success() {
75 std::process::exit(1);
76 }
77
78 let graph_svg = output.stdout;
79 if args.open {
80 let mut temp_file = Builder::new().suffix(".svg").tempfile()?;
82 temp_file.write_all(graph_svg.as_slice())?;
83
84 Command::new("inkscape") .arg(temp_file.path())
87 .status()
88 .expect("failed to open SVG file");
89 } else {
90 let mut svg_file = std::fs::File::create("output.svg")?;
92 svg_file.write_all(graph_svg.as_slice())?;
93 }
94 Ok(())
95}
96
97fn validate_mission_arg(
98 config: &config::CuConfig,
99 requested: Option<&str>,
100) -> CuResult<Option<String>> {
101 match (&config.graphs, requested) {
102 (ConfigGraphs::Simple(_), None) => Ok(None),
103 (ConfigGraphs::Simple(_), Some("default")) => Ok(None),
104 (ConfigGraphs::Simple(_), Some(id)) => Err(CuError::from(format!(
105 "Config is not mission-based; remove --mission (received '{id}')"
106 ))),
107 (ConfigGraphs::Missions(graphs), Some(id)) => {
108 if graphs.contains_key(id) {
109 Ok(Some(id.to_string()))
110 } else {
111 Err(CuError::from(format!(
112 "Mission '{id}' not found. Available missions: {}",
113 format_mission_list(graphs)
114 )))
115 }
116 }
117 (ConfigGraphs::Missions(_), None) => Ok(None),
118 }
119}
120
121fn print_mission_list(config: &config::CuConfig) {
122 match &config.graphs {
123 ConfigGraphs::Simple(_) => println!("default"),
124 ConfigGraphs::Missions(graphs) => {
125 let mut missions: Vec<_> = graphs.keys().cloned().collect();
126 missions.sort();
127 for mission in missions {
128 println!("{mission}");
129 }
130 }
131 }
132}
133
134fn format_mission_list(graphs: &hashbrown::HashMap<String, config::CuGraph>) -> String {
135 let mut missions: Vec<_> = graphs.keys().cloned().collect();
136 missions.sort();
137 missions.join(", ")
138}