From 9a3d948cbf97576fb22cb70b2afb0575c09a8e57 Mon Sep 17 00:00:00 2001 From: Yannik <> Date: Fri, 17 Nov 2017 17:35:00 +0100 Subject: [PATCH] error-handling, more parsing, dir parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hab das error-handling vereinfacht, es panict jetzt sofort sobald ein fehler gefunden wurde, anstatt die fehler zurückzugeben. ratio ist, dass die fehler eh nicht behandelt werden können, und sonst nur weiter oben im call-stack zum abbruch geführt hätten. parst jetzt ein bischen mehr attribute, es ist im code einstellbar beim fehlen welcher attribute gepanict werden soll und bei welchen nur eine zeile auf stderr gesetzt werden soll; schreibt bei fehlenden attributen jetzt eine warnung auf stderr. kann jetzt auch die in verzeichnisse aufgeteilten einträge verarbeiten --- Cargo.toml | 2 - src/main.rs | 148 +++++++++++++++++++++---------------------------- src/parsing.rs | 77 +++++++++++++++++++++++++ 3 files changed, 141 insertions(+), 86 deletions(-) create mode 100644 src/parsing.rs diff --git a/Cargo.toml b/Cargo.toml index 5016ec8..7328ecb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,3 @@ authors = [""] serde = "*" serde_derive = "*" serde_yaml = "*" -derive-error-chain = "*" -error-chain = "*" diff --git a/src/main.rs b/src/main.rs index 7f9a0df..0c40c96 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,9 @@ #[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; - +mod parsing; fn main() { //read the files @@ -17,10 +12,6 @@ fn main() { 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 @@ -38,55 +29,49 @@ fn main() { 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#"|&(ref name, ref err)| write!(f, "the antenna config of {} is wrong: {}", name, err)"#)] - Structure((String, String)), -} - #[derive(Debug)] -struct Host { +pub struct Host { name: String, ip: String, - mac: Option, kind: HostKind, } impl Host { - fn from_file(path: &Path) -> Result { - RawHost::from_file(path)?.parse() + fn from_file(path: &Path) -> Self { + let (host, hostname) = parsing::RawHost::from_file(path); + host.parse(hostname) } fn to_dot_node(&self) -> String { - let mac = self.mac.clone().unwrap_or("no mac".into()); 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=mac, ipv6="no ipv6") , "style=filled".into() ]; + let record; + // type-specific handling + use HostKind::*; match self.kind { - HostKind::Client {coordinates, ref subnet, .. } => + Client {coordinates, ref subnet, ref mac, .. } => { attributes.push("fillcolor=lightgray".into()) ; if let Some(coordinates) = coordinates { attributes.push(format!("pos=\"{breitengrad},{längengrad}\"" , längengrad=coordinates[0], breitengrad=coordinates[1])) } + ; record = + ( self.name.clone() + , (self.kind.name(), mac.clone()) + , (self.ip.clone(), subnet.clone()) + , ("no ipv6", "no 6-subnet") + ).recordify() }, - _ => (), + AccessPoint { ref mac, .. } => record = + ( self.name.clone() + , (self.kind.name(), mac.clone() ) + , self.ip.clone() + ).recordify(), + _ => record = (self.name.clone(), self.kind.name(), self.ip.clone()).recordify(), }; + attributes.push(format!("label=\"{}\"", record)); let attributes = attributes.join(", "); @@ -94,7 +79,9 @@ impl Host { } fn to_dot_edge(&self) -> Option { match self.kind { - HostKind::Client { ref parent, .. } => + HostKind::Client { ref parent, ref ssid, .. } => + format!("\"{name}\" -- \"{parent}\" [label=\"{ssid}\"]", name=self.name, parent=parent, ssid=ssid).into(), + HostKind::AccessPoint { ref parent, .. } => format!("\"{name}\" -- \"{parent}\"", name=self.name, parent=parent).into(), _ => None, } @@ -103,8 +90,9 @@ impl Host { #[derive(Debug)] enum HostKind { - Client { subnet: String, coordinates: Option<[f64;2]>, parent: String }, - AccessPoint, + Client { mac: String, subnet: String, coordinates: Option<[f64;2]>, parent: String, ssid: String }, + AccessPoint { mac: String, ssid: String, parent: String }, + Service, Other, } @@ -113,56 +101,48 @@ impl HostKind { use HostKind::*; match *self { Client { .. } => "Client", - AccessPoint => "AccessPoint", + AccessPoint { .. } => "AccessPoint", + Service => "Service", Other => "Other", } } } -#[derive(Deserialize,Debug)] -struct RawHost { - #[serde(skip)] - name: Option, - ansible_host: String, - #[serde(rename = "type")] - kind: String, - mac: Option, - client_of: Option, - coordinate: Option<[f64;2]>, - subnet: Option, +/** dieser Trait ermöglicht es verschachtelte tupel in ein label für einen record-shaped-node für +dotfiles umzuwandeln. inputwerte werden nicht escaped + + assert_eq!( + ("a", ("b", "c", "d"), ("e","f"), "g").recordify(), + "{a|{b|c|d}|{e|f}|g}".into()) +*/ +pub trait DotRecord { + fn recordify(self) -> String; } -impl RawHost { - fn parse(self) -> Result { - let name : String = self.name - .ok_or(ParseError::Structure(("???".into(),"got no file name? weird.".into())))? - .into_string().unwrap() - .split('.') - .next().unwrap().into(); - Ok(Host - { name: name.clone() - , 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((name.clone(), "is client, has no subnet".into())))? - //, coordinates: self.coordinate.ok_or(ParseError::Structure("is client, has no coordinates".into()))? - , coordinates: self.coordinate - , parent: self.client_of.ok_or(ParseError::Structure((name.clone(), "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) - } +impl DotRecord for String { + fn recordify(self) -> String { self } +} +impl<'a> DotRecord for &'a str { + fn recordify(self) -> String { self.into() } +} +impl DotRecord for (A,B) { + fn recordify(self) -> String { + format!("{{{}|{}}}", self.0.recordify(), self.1.recordify()) + } +} +impl DotRecord for (A,B,C) { + fn recordify(self) -> String { + format!("{{{}|{}|{}}}", self.0.recordify(), self.1.recordify(), self.2.recordify()) + } +} +impl DotRecord for (A,B,C,D) { + fn recordify(self) -> String { + format!("{{{}|{}|{}|{}}}", self.0.recordify(), self.1.recordify(), self.2.recordify(), self.3.recordify()) + } +} +impl DotRecord for (A,B,C,D,E) { + fn recordify(self) -> String { + format!("{{{}|{}|{}|{}|{}}}", self.0.recordify(), self.1.recordify(), self.2.recordify(), self.3.recordify(), self.4.recordify()) + } } diff --git a/src/parsing.rs b/src/parsing.rs new file mode 100644 index 0000000..49f90e8 --- /dev/null +++ b/src/parsing.rs @@ -0,0 +1,77 @@ +use std::path::Path; +use std::fs::File; +use serde_yaml; +use super::{Host, HostKind}; + +/** parsing funktioniert zweischrittig +zunächst wird in from_file die yaml-datei ausgelesen, das resultat ist ein struct, dass in seinem aufbau der datei entspricht +danach wird in parse dieses in ein struct verwandelt, das dem logischen aufbau entspricht. +um das programm zu erweitern ist es am besten dinge zu Host/HostKind hinzuzufügen und dann den fehlern zu folgen. +*/ +#[derive(Deserialize,Debug)] +pub struct RawHost { + ansible_host: String, + #[serde(rename = "type")] + kind: String, + mac: Option, + client_of: Option, + coordinate: Option<[f64;2]>, + subnet: Option, + link_ssid: Option, +} + +impl RawHost { + /** wandelt mehr oder weniger magisch eine yaml-datei in ein RawHost um. + die magie wird von dem (de)serialisierungsframework serde bereitgestellt */ + pub fn from_file(path: &Path) -> (Self, String) { + //TODO use methods .is_file and .is_dir on path and read path or path/vars depending on result + let filename = path.file_stem().expect("weird path"); + let hostname = filename.to_str() + .expect(&format!("filename '{:?}' is not valid utf-8", filename)).into(); + let fd = if path.is_file() { + File::open(path) + .unwrap_or_else(|err| panic!("{} in file {}", err, hostname)) + } else if path.is_dir() { + let mut buf = path.to_path_buf(); + buf.push("vars"); + File::open(buf) + .unwrap_or_else(|err| panic!("{} in file {}", err, hostname)) + } else { + panic!("{:?} does not point to either a file or a directory", path) + }; + let antenna : Self = serde_yaml::from_reader(&fd) + .unwrap_or_else(|err| panic!("{} in file {}", err, hostname)); + (antenna, hostname) + } + + /** nimmt eine geparste datei und versucht daraus die logische struktur zu extrahieren */ + pub fn parse(self, name: String) -> Host { + // sobald ein felhender eintrag bei allen gefixt ist, bitte unten missingwarn("name") durch + // missingpanic ersetzen + let missingpanic= |attr: &'static str| panic!("{} does not have the required attribute {}", name, attr); + let missingwarn = |attr: &'static str| { + eprintln!("{} does not have the required attribute {}", name, attr); + "MISSING".to_string() + }; + Host + { name: name.clone() + , ip: self.ansible_host + , kind: match self.kind.as_ref() + { "wlan-ap" => HostKind::AccessPoint + { mac: self.mac.unwrap_or_else(|| missingpanic("mac")) + , ssid: self.link_ssid.unwrap_or_else(|| missingwarn("link_ssid")) + , parent: self.client_of.unwrap_or_else(|| missingwarn("client_of")) + } + , "client" => HostKind::Client + { subnet: self.subnet.unwrap_or_else(|| missingpanic("subnet")) + , coordinates: self.coordinate + , parent: self.client_of.unwrap_or_else(|| missingwarn("client_of")) + , mac: self.mac.unwrap_or_else(|| missingpanic("mac")) + , ssid: self.link_ssid.unwrap_or_else(|| missingwarn("link_ssid")) + } + , "service" => HostKind::Service + , _ => HostKind::Other + } + } + } +}