github.com/charypar/monobuild@v0.0.0-20211122220434-fd884ed50212/rs/src/read.rs (about) 1 use std::collections::{BTreeMap, BTreeSet, HashSet}; 2 use std::fmt::Display; 3 4 use crate::core::Dependency; 5 use crate::graph::Graph; 6 7 #[derive(PartialEq, Debug)] 8 pub enum Warning { 9 Unknown(String, String), // dependent, dependency 10 BadLineFormat(usize, String), // line number, line 11 } 12 13 impl Display for Warning { 14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 match self { 16 Warning::Unknown(of, what) => write!(f, "Unknown dependency {} of {}.", what, of), 17 Warning::BadLineFormat(l, line) => write!(f, "Bad line format: {}: '{}' expected 'component: dependency, dependency, dependency, ...", l, line), 18 } 19 } 20 } 21 22 fn manifest(manifest: &str) -> HashSet<(String, Dependency)> { 23 manifest 24 .lines() 25 .flat_map(|l| match l.trim().trim_end_matches("/") { 26 "" => None, 27 d if d.starts_with("#") => None, 28 d if d.starts_with("!") => Some((d[1..].to_string(), Dependency::Strong)), 29 d => Some((d.to_string(), Dependency::Weak)), 30 }) 31 .collect() 32 } 33 34 pub fn manifests(manifests: BTreeMap<String, String>) -> (Graph<String, Dependency>, Vec<Warning>) { 35 let components: BTreeSet<_> = manifests.keys().cloned().collect(); 36 37 let mut warnings = Vec::new(); 38 let graph = manifests 39 .into_iter() 40 .map(|(c, m)| { 41 let (deps, ws): (HashSet<_>, HashSet<_>) = manifest(&m) 42 .into_iter() 43 .partition(|(to, _)| components.contains(to)); 44 45 let mut warns = ws 46 .into_iter() 47 .map(|(to, _)| Warning::Unknown(c.clone(), to)) 48 .collect(); 49 50 warnings.append(&mut warns); 51 52 (c, deps) 53 }) 54 .into(); 55 56 (graph, warnings) 57 } 58 59 pub fn repo_manifest(manifest: String) -> (Graph<String, Dependency>, Vec<Warning>) { 60 let mut warnings = Vec::new(); 61 62 let lines = manifest 63 .lines() 64 .map(|l| l.trim()) 65 .filter(|l| !l.starts_with("#") && *l != "") 66 .enumerate(); 67 68 let graph = lines 69 .filter_map(|(i, line)| { 70 if let Some((c, ds)) = line.split_once(":") { 71 let component = c.trim().to_owned(); 72 let dependencies: HashSet<_> = ds 73 .trim() 74 .split(",") 75 .map(|d| d.trim()) 76 .filter(|d| *d != "") 77 .map(|d| { 78 if d.starts_with("!") { 79 (d[1..].to_owned(), Dependency::Strong) 80 } else { 81 (d.to_owned(), Dependency::Weak) 82 } 83 }) 84 .collect(); 85 86 Some((component, dependencies)) 87 } else { 88 warnings.push(Warning::BadLineFormat(i, line.to_owned())); 89 None 90 } 91 }) 92 .into(); 93 94 (graph, warnings) 95 } 96 97 #[cfg(test)] 98 mod test { 99 mod manifest { 100 use super::super::*; 101 use std::collections::HashSet; 102 103 #[test] 104 fn empty() { 105 let text = "\n \n \n # comment \n"; 106 107 let actual = manifest(text); 108 let expected = HashSet::new(); 109 110 assert_eq!(actual, expected); 111 } 112 113 #[test] 114 fn single_dependency() { 115 let text = "\n other/\n \n \n"; 116 117 let actual = manifest(text); 118 let expected = vec![("other".to_string(), Dependency::Weak)] 119 .into_iter() 120 .collect(); 121 122 assert_eq!(actual, expected); 123 } 124 125 #[test] 126 fn single_strong_dependency() { 127 let text = "\n !other\n \n \n"; 128 129 let actual = manifest(text); 130 let expected = vec![("other".to_string(), Dependency::Strong)] 131 .into_iter() 132 .collect(); 133 134 assert_eq!(actual, expected) 135 } 136 137 #[test] 138 fn full() { 139 let text = "\n some \n !other\n \none/more \n # comment \n"; 140 141 let actual = manifest(text); 142 let expected = vec![ 143 ("some".to_string(), Dependency::Weak), 144 ("other".to_string(), Dependency::Strong), 145 ("one/more".to_string(), Dependency::Weak), 146 ] 147 .into_iter() 148 .collect(); 149 150 assert_eq!(actual, expected) 151 } 152 } 153 154 mod manifests { 155 use crate::graph::Graph; 156 use std::collections::BTreeMap; 157 158 use super::super::*; 159 160 #[test] 161 fn no_manifests() { 162 let files = BTreeMap::new(); 163 164 let actual = manifests(files); 165 let expected = (Graph::new(), Vec::new()); 166 167 assert_eq!(actual, expected); 168 } 169 170 #[test] 171 fn ignore_unknown_components() { 172 let files = vec![("foo".to_string(), "\n bar\n".to_string())] 173 .into_iter() 174 .collect(); 175 176 let actual = manifests(files); 177 let expected = ( 178 Graph::from([("foo".into(), [])]), 179 vec![Warning::Unknown("foo".to_string(), "bar".to_string())], 180 ); 181 182 assert_eq!(actual, expected); 183 } 184 185 #[test] 186 fn two_manifests() { 187 let files = vec![ 188 ("foo".to_string(), "\n bar\nbaz".to_string()), 189 ("bar".to_string(), "\n baz\n".to_string()), 190 ] 191 .into_iter() 192 .collect(); 193 194 let actual = manifests(files); 195 let expected = ( 196 Graph::from([ 197 ("foo".into(), vec![("bar".into(), Dependency::Weak)]), 198 ("bar".into(), vec![]), 199 ]), 200 vec![ 201 Warning::Unknown("bar".to_string(), "baz".to_string()), 202 Warning::Unknown("foo".to_string(), "baz".to_string()), 203 ], 204 ); 205 206 assert_eq!(actual, expected); 207 } 208 209 #[test] 210 fn complex_manifests() { 211 let files = vec![ 212 ("app1".to_string(), "\nlibs/lib1\nlibs/lib2/".to_string()), 213 ("app2".to_string(), "\nlibs/lib2\n\n\nlibs/lib3".to_string()), 214 ("app3".to_string(), "\n\nlibs/lib3".to_string()), 215 ("app4".to_string(), "\n\n# yo".to_string()), 216 ("libs/lib1".to_string(), "\n libs/lib3\n".to_string()), 217 ("libs/lib2".to_string(), "\n libs/lib3\n".to_string()), 218 ("libs/lib3".to_string(), "".to_string()), 219 ( 220 "stack1".to_string(), 221 "# frontend\n!app1\n\n# backend\n!app2\n!app3".to_string(), 222 ), 223 ] 224 .into_iter() 225 .collect(); 226 227 let actual = manifests(files); 228 let expected = ( 229 Graph::from([ 230 ( 231 "app1".into(), 232 vec![ 233 ("libs/lib1".into(), Dependency::Weak), 234 ("libs/lib2".into(), Dependency::Weak), 235 ], 236 ), 237 ( 238 "app2".into(), 239 vec![ 240 ("libs/lib2".into(), Dependency::Weak), 241 ("libs/lib3".into(), Dependency::Weak), 242 ], 243 ), 244 ("app3".into(), vec![("libs/lib3".into(), Dependency::Weak)]), 245 ("app4".into(), vec![]), 246 ( 247 "libs/lib1".into(), 248 vec![("libs/lib3".into(), Dependency::Weak)], 249 ), 250 ( 251 "libs/lib2".into(), 252 vec![("libs/lib3".into(), Dependency::Weak)], 253 ), 254 ("libs/lib3".into(), vec![]), 255 ( 256 "stack1".into(), 257 vec![ 258 ("app1".into(), Dependency::Strong), 259 ("app2".into(), Dependency::Strong), 260 ("app3".into(), Dependency::Strong), 261 ], 262 ), 263 ]), 264 vec![], 265 ); 266 267 assert_eq!(actual, expected); 268 } 269 } 270 271 mod repo_manifest { 272 use super::super::*; 273 use crate::graph::Graph; 274 275 #[test] 276 fn empty_manifest() { 277 let manifest = "".into(); 278 279 let (actual, _) = repo_manifest(manifest); 280 let expected = Graph::new(); 281 282 assert_eq!(actual, expected); 283 } 284 285 #[test] 286 fn single_component() { 287 let manifest = "lib1:".into(); 288 289 let (actual, _) = repo_manifest(manifest); 290 let expected = Graph::from([("lib1".into(), vec![])]); 291 292 assert_eq!(actual, expected); 293 } 294 295 #[test] 296 fn component_with_depednency() { 297 let manifest = "lib1: lib2\nlib2:".into(); 298 299 let (actual, _) = repo_manifest(manifest); 300 let expected = Graph::from([ 301 ("lib1".into(), vec![("lib2".into(), Dependency::Weak)]), 302 ("lib2".into(), vec![]), 303 ]); 304 305 assert_eq!(actual, expected); 306 } 307 308 #[test] 309 fn component_with_mutlitple_depednencies() { 310 let manifest = "lib1: lib2, lib3\nlib2: \nlib3: ".into(); 311 312 let (actual, _) = repo_manifest(manifest); 313 let expected = Graph::from([( 314 "lib1".into(), 315 vec![ 316 ("lib2".into(), Dependency::Weak), 317 ("lib3".into(), Dependency::Weak), 318 ], 319 )]); // Note that 'new' normalises the graph! 320 321 assert_eq!(actual, expected); 322 } 323 324 #[test] 325 fn component_with_unlisted_dependency() { 326 let manifest = "lib1: lib2, lib3\n".into(); 327 328 let (actual, _) = repo_manifest(manifest); 329 let expected = Graph::from([( 330 "lib1".into(), 331 vec![ 332 ("lib2".into(), Dependency::Weak), 333 ("lib3".into(), Dependency::Weak), 334 ], 335 )]); // Note that 'new' normalises the graph! 336 337 assert_eq!(actual, expected); 338 } 339 340 #[test] 341 fn complex_manifest() { 342 let manifest = "# comment\napp1: lib1, lib2, lib3\napp2: \nlib1: \nlib2: lib3\nlib3: \n\nstack1: !app1, !app2".to_owned(); 343 344 let (actual, ws) = repo_manifest(manifest); 345 let expected = Graph::from([ 346 ( 347 "app1".into(), 348 vec![ 349 ("lib1".into(), Dependency::Weak), 350 ("lib2".into(), Dependency::Weak), 351 ("lib3".into(), Dependency::Weak), 352 ], 353 ), 354 ("lib2".into(), vec![("lib3".into(), Dependency::Weak)]), 355 ( 356 "stack1".into(), 357 vec![ 358 ("app1".into(), Dependency::Strong), 359 ("app2".into(), Dependency::Strong), 360 ], 361 ), 362 ]); 363 364 assert_eq!(actual, expected); 365 assert_eq!(ws, vec![]); 366 } 367 } 368 }