error-handling, more parsing, dir parsing

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
This commit is contained in:
Yannik 2017-11-17 17:35:00 +01:00
parent f55e135b53
commit 9a3d948cbf
3 changed files with 141 additions and 86 deletions

View File

@ -7,5 +7,3 @@ authors = [""]
serde = "*"
serde_derive = "*"
serde_yaml = "*"
derive-error-chain = "*"
error-chain = "*"

View File

@ -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<Host> = 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<String>,
kind: HostKind,
}
impl Host {
fn from_file(path: &Path) -> Result<Self> {
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<String> {
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<std::ffi::OsString>,
ansible_host: String,
#[serde(rename = "type")]
kind: String,
mac: Option<String>,
client_of: Option<String>,
coordinate: Option<[f64;2]>,
subnet: Option<String>,
/** 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<Host> {
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<Self> {
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<A: DotRecord, B: DotRecord> DotRecord for (A,B) {
fn recordify(self) -> String {
format!("{{{}|{}}}", self.0.recordify(), self.1.recordify())
}
}
impl<A: DotRecord, B: DotRecord, C: DotRecord> DotRecord for (A,B,C) {
fn recordify(self) -> String {
format!("{{{}|{}|{}}}", self.0.recordify(), self.1.recordify(), self.2.recordify())
}
}
impl<A: DotRecord, B: DotRecord, C: DotRecord, D: DotRecord> DotRecord for (A,B,C,D) {
fn recordify(self) -> String {
format!("{{{}|{}|{}|{}}}", self.0.recordify(), self.1.recordify(), self.2.recordify(), self.3.recordify())
}
}
impl<A: DotRecord, B: DotRecord, C: DotRecord, D: DotRecord, E: DotRecord> 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())
}
}

77
src/parsing.rs Normal file
View File

@ -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<String>,
client_of: Option<String>,
coordinate: Option<[f64;2]>,
subnet: Option<String>,
link_ssid: Option<String>,
}
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
}
}
}
}