commit
5e7abdb2d8
@ -0,0 +1,2 @@ |
||||
/target/ |
||||
**/*.rs.bk |
@ -0,0 +1,11 @@ |
||||
[package] |
||||
name = "doku2dot" |
||||
version = "0.1.0" |
||||
authors = [""] |
||||
|
||||
[dependencies] |
||||
serde = "*" |
||||
serde_derive = "*" |
||||
serde_yaml = "*" |
||||
derive-error-chain = "*" |
||||
error-chain = "*" |
@ -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) |
||||
} |
||||
} |
Loading…
Reference in new issue