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:
parent
f55e135b53
commit
9a3d948cbf
3 changed files with 141 additions and 86 deletions
|
@ -7,5 +7,3 @@ authors = [""]
|
|||
serde = "*"
|
||||
serde_derive = "*"
|
||||
serde_yaml = "*"
|
||||
derive-error-chain = "*"
|
||||
error-chain = "*"
|
||||
|
|
148
src/main.rs
148
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<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
77
src/parsing.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue