commit 5e7abdb2d822a0aad780db231089c2a0c97e2a06 Author: yannik Date: Fri Nov 17 00:18:59 2017 +0100 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eccd7b4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5016ec8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "doku2dot" +version = "0.1.0" +authors = [""] + +[dependencies] +serde = "*" +serde_derive = "*" +serde_yaml = "*" +derive-error-chain = "*" +error-chain = "*" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..addb8d3 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,167 @@ +#[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) + } +}