This commit is contained in:
yannik 2017-11-17 00:18:59 +01:00
commit 5e7abdb2d8
3 changed files with 180 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target/
**/*.rs.bk

11
Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "doku2dot"
version = "0.1.0"
authors = [""]
[dependencies]
serde = "*"
serde_derive = "*"
serde_yaml = "*"
derive-error-chain = "*"
error-chain = "*"

167
src/main.rs Normal file
View file

@ -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<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
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<Self> {
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<String> {
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<std::ffi::OsString>,
ansible_host: String,
#[serde(rename = "type")]
kind: String,
mac: String,
client_of: Option<String>,
coordinate: Option<[f64;2]>,
subnet: Option<String>,
}
impl RawHost {
fn parse(self) -> Result<Host> {
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<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)
}
}