1use std::env;
2
3use log::error;
4use petgraph::stable_graph::StableDiGraph;
5
6pub const MINIMUM_LENGTH_DEFAULT: u32 = 1;
8pub const VERTEX_SPACING_DEFAULT: f64 = 10.0;
9pub const DUMMY_VERTICES_DEFAULT: bool = true;
10pub const RANKING_TYPE_DEFAULT: RankingType = RankingType::MinimizeEdgeLength;
11pub const C_MINIMIZATION_DEFAULT: CrossingMinimization = CrossingMinimization::Barycenter;
12pub const TRANSPOSE_DEFAULT: bool = true;
13pub const DUMMY_SIZE_DEFAULT: f64 = 1.0;
14
15const ENV_MINIMUM_LENGTH: &str = "RUST_GRAPH_MIN_LEN";
16const ENV_VERTEX_SPACING: &str = "RUST_GRAPH_V_SPACING";
17const ENV_DUMMY_VERTICES: &str = "RUST_GRAPH_DUMMIES";
18const ENV_RANKING_TYPE: &str = "RUST_GRAPH_R_TYPE";
19const ENV_CROSSING_MINIMIZATION: &str = "RUST_GRAPH_CROSS_MIN";
20const ENV_TRANSPOSE: &str = "RUST_GRAPH_TRANSPOSE";
21const ENV_DUMMY_SIZE: &str = "RUST_GRAPH_DUMMY_SIZE";
22
23pub trait IntoCoordinates {}
24
25impl<V, E> IntoCoordinates for StableDiGraph<V, E> {}
26impl IntoCoordinates for &[(u32, u32)] {}
27impl IntoCoordinates for (&[u32], &[(u32, u32)]) {}
28
29macro_rules! read_env {
30 ($field:expr, $cb:tt, $env:ident) => {
31 #[allow(unused_parens)]
32 match env::var($env).map($cb) {
33 Ok(Ok(v)) => $field = v,
34 Ok(Err(e)) => {
35 error!(target: "initialization", "{e}");
36 }
37 _ => (),
38 }
39 };
40}
41
42#[derive(Clone, Copy, Debug)]
44pub struct Config {
45 pub minimum_length: u32,
47 pub vertex_spacing: f64,
50 pub dummy_vertices: bool,
52 pub dummy_size: f64,
55 pub ranking_type: RankingType,
57 pub c_minimization: CrossingMinimization,
59 pub transpose: bool,
62}
63
64impl Config {
65 pub fn new_from_env() -> Self {
79 let mut config = Self::default();
80
81 let parse_bool = |x: String| match x.as_str() {
82 "y" => Ok(true),
83 "n" => Ok(false),
84 v => Err(format!("Invalid argument for dummy vertex env: {v}")),
85 };
86
87 read_env!(
88 config.minimum_length,
89 (|x| x.parse::<u32>()),
90 ENV_MINIMUM_LENGTH
91 );
92
93 read_env!(
94 config.c_minimization,
95 (TryFrom::try_from),
96 ENV_CROSSING_MINIMIZATION
97 );
98
99 read_env!(config.ranking_type, (TryFrom::try_from), ENV_RANKING_TYPE);
100
101 read_env!(
102 config.vertex_spacing,
103 (|x| x.parse::<f64>()),
104 ENV_VERTEX_SPACING
105 );
106
107 read_env!(config.dummy_vertices, parse_bool, ENV_DUMMY_VERTICES);
108
109 read_env!(config.dummy_size, (|x| x.parse::<f64>()), ENV_DUMMY_SIZE);
110
111 read_env!(config.transpose, parse_bool, ENV_TRANSPOSE);
112
113 config
114 }
115}
116
117impl Default for Config {
118 fn default() -> Self {
119 Self {
120 minimum_length: MINIMUM_LENGTH_DEFAULT,
121 vertex_spacing: VERTEX_SPACING_DEFAULT,
122 dummy_vertices: DUMMY_VERTICES_DEFAULT,
123 ranking_type: RANKING_TYPE_DEFAULT,
124 c_minimization: C_MINIMIZATION_DEFAULT,
125 transpose: TRANSPOSE_DEFAULT,
126 dummy_size: DUMMY_SIZE_DEFAULT,
127 }
128 }
129}
130
131#[derive(Clone, Copy, Debug, PartialEq, Eq)]
133pub enum RankingType {
134 Original,
136 MinimizeEdgeLength,
138 Up,
140 Down,
142}
143
144impl TryFrom<String> for RankingType {
145 type Error = String;
146
147 fn try_from(value: String) -> Result<Self, Self::Error> {
148 match value.as_str() {
149 "original" => Ok(Self::Original),
150 "minimize" => Ok(Self::MinimizeEdgeLength),
151 "up" => Ok(Self::Up),
152 "down" => Ok(Self::Down),
153 s => Err(format!("invalid value for ranking type: {s}")),
154 }
155 }
156}
157
158impl From<RankingType> for &'static str {
159 fn from(value: RankingType) -> Self {
160 match value {
161 RankingType::Up => "up",
162 RankingType::Down => "down",
163 RankingType::Original => "original",
164 RankingType::MinimizeEdgeLength => "minimize",
165 }
166 }
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
173pub enum CrossingMinimization {
174 Barycenter,
176 Median,
178}
179
180impl TryFrom<String> for CrossingMinimization {
181 type Error = String;
182
183 fn try_from(value: String) -> Result<Self, Self::Error> {
184 match value.as_str() {
185 "barycenter" => Ok(Self::Barycenter),
186 "median" => Ok(Self::Median),
187 s => Err(format!("invalid value for crossing minimization: {s}")),
188 }
189 }
190}
191
192impl From<CrossingMinimization> for &'static str {
193 fn from(value: CrossingMinimization) -> Self {
194 match value {
195 CrossingMinimization::Median => "median",
196 CrossingMinimization::Barycenter => "barycenter",
197 }
198 }
199}
200
201#[test]
202fn from_env_all_valid() {
203 use std::env;
204 env::set_var(ENV_MINIMUM_LENGTH, "5");
205 env::set_var(ENV_DUMMY_VERTICES, "y");
206 env::set_var(ENV_DUMMY_SIZE, "0.1");
207 env::set_var(ENV_RANKING_TYPE, "up");
208 env::set_var(ENV_CROSSING_MINIMIZATION, "median");
209 env::set_var(ENV_TRANSPOSE, "n");
210 env::set_var(ENV_VERTEX_SPACING, "20");
211 let cfg = Config::new_from_env();
212 assert_eq!(cfg.minimum_length, 5);
213 assert_eq!(cfg.dummy_vertices, true);
214 assert_eq!(cfg.dummy_size, 0.1);
215 assert_eq!(cfg.ranking_type, RankingType::Up);
216 assert_eq!(cfg.c_minimization, CrossingMinimization::Median);
217 assert_eq!(cfg.transpose, false);
218 assert_eq!(cfg.vertex_spacing, 20.0);
219}
220
221#[test]
222fn from_env_invalid_value() {
223 use std::env;
224
225 env::set_var(ENV_CROSSING_MINIMIZATION, "flubbeldiflap");
226 env::set_var(ENV_VERTEX_SPACING, "1bleh0");
227 let cfg = Config::new_from_env();
228 let default = Config::default();
229 assert_eq!(default.c_minimization, cfg.c_minimization);
230 assert_eq!(default.vertex_spacing, cfg.vertex_spacing);
231}