#[macro_use] extern crate serde_derive; #[macro_use] extern crate derive_error_chain; extern crate serde_yaml; use std::fs::File; use std::path::Path; fn main() { //read the files //let paths = [Path::new("test.yml")]; let filenames: Vec<_> = std::env::args_os().skip(1).collect(); let hosts: Vec = filenames.iter() .map(Path::new) .map(|path| Host::from_file(&path)) .map(|result| match result { Ok(val) => val, Err(err) => panic!("{:#}", err), }) .collect(); //build dot-graph let nodes = hosts.iter().map(Host::to_dot_node); let edges = hosts.iter().flat_map(Host::to_dot_edge); // let edges = .. //render it println!("digraph antennen {{"); for node in nodes { println!("{}", node); } for edge in edges { println!("{}", edge); } println!("}}"); } #[derive(Debug, ErrorChain)] pub enum ParseError { #[error_chain(foreign)] IO(std::io::Error), #[error_chain(custom)] #[error_chain(description = r#"|_| "File invalid/incomplete yaml""#)] #[error_chain(display = r#"|&(ref path, ref err)| write!(f, "failed at file {:?} with {}", path, err)"#)] Yaml((std::ffi::OsString, serde_yaml::Error)), #[error_chain(custom)] #[error_chain(description = r#"|_| "Host file structure broken""#)] #[error_chain(display = r#"|t| write!(f, "the antenna config is wrong: {}", t)"#)] Structure(String), } #[derive(Debug)] struct Host { name: String, ip: String, mac: String, kind: HostKind, } impl Host { fn from_file(path: &Path) -> Result { RawHost::from_file(path)?.parse() } fn to_dot_node(&self) -> String { let mut attributes = vec! [ "shape=record".to_string() , format!("label=\"{{\'{name}\'|{typ}|{{{ip}|{mac}}}|{{{ipv6}}}}}\"" , name = self.name, typ=self.kind.name(), ip=self.ip, mac=self.mac, ipv6="no ipv6") , "style=filled".into() ]; // type-specific handling match self.kind { HostKind::Client {coordinates, ref subnet, .. } => attributes.extend_from_slice( &[ format!("pos=\"{breitengrad},{längengrad}\"" , längengrad=coordinates[0], breitengrad=coordinates[1]) , "fillcolor=lightgray".into() ]), _ => (), }; let attributes = attributes.join(", "); format!("\"{name}\" [{attributes}]", name=self.name, attributes=attributes) } fn to_dot_edge(&self) -> Option { match self.kind { HostKind::Client { ref parent, .. } => format!("\"{name}\" -> \"{parent}\"", name=self.name, parent=parent).into(), _ => None, } } } #[derive(Debug)] enum HostKind { Client { subnet: String, coordinates: [f64;2], parent: String }, AccessPoint, Other, } impl HostKind { fn name(&self) -> &'static str { use HostKind::*; match *self { Client { .. } => "Client", AccessPoint => "AccessPoint", Other => "Other", } } } #[derive(Deserialize,Debug)] struct RawHost { #[serde(skip)] name: Option, ansible_host: String, #[serde(rename = "type")] kind: String, mac: String, client_of: Option, coordinate: Option<[f64;2]>, subnet: Option, } impl RawHost { fn parse(self) -> Result { let name = self.name .ok_or(ParseError::Structure("got no file name? weird.".into()))? .into_string().unwrap() .split('.') .next().unwrap().into(); Ok(Host { name: name , ip: self.ansible_host , mac: self.mac , kind: match self.kind.as_ref() { "wlan-ap" => HostKind::AccessPoint , "client" => HostKind::Client { subnet: self.subnet.ok_or(ParseError::Structure("is client, has no subnet".into()))? , coordinates: self.coordinate.ok_or(ParseError::Structure("is client, has no coordinates".into()))? , parent: self.client_of.ok_or(ParseError::Structure("is client, has no client_of".into()))? } , _ => HostKind::Other } }) } fn from_file(path: &Path) -> Result { let fd = File::open(path)?; let mut antenna : Self = match serde_yaml::from_reader(&fd) { Ok(val) => val, Err(err) => return Err(ParseError::Yaml((path.as_os_str().to_owned(), err)).into()), }; antenna.name = path.file_name().map(|a|a.to_owned()); Ok(antenna) } }