use std::borrow::Cow;
use std::fmt;
use std::io::{self, Write};
#[derive(Clone, Default)]
pub struct Node {
pub label: Option<Id>,
pub peripheries: Option<usize>,
pub _non_exhaustive: (),
}
#[derive(Clone, Default)]
pub struct Edge {
pub label: Option<Id>,
pub _non_exhaustive: (),
}
pub struct GraphWriter<W: Write> {
inner: Option<W>,
edgeop: Family,
}
#[derive(Clone, Copy, Debug)]
pub enum Family {
Directed,
Undirected,
}
#[derive(Clone, Debug)]
pub struct Id(IdEnum);
#[derive(Clone, Debug)]
enum IdEnum {
Raw(Cow<'static, str>),
Numeral(usize),
INumeral(isize),
Str(Cow<'static, str>),
}
pub trait DotGraph {
fn dot_graph<W>(&self, to: W) -> io::Result<()>;
}
pub trait WriteDot: io::Write {
fn write_dot<D: DotGraph>(&mut self, graph: D) -> io::Result<()> {
graph.dot_graph(self)
}
}
impl<W: Write> GraphWriter<W> {
pub fn new(mut inner: W, family: Family, name: Option<Id>) -> io::Result<Self> {
if let Some(name) = name {
writeln!(&mut inner, "{} {} {{", family.name(), name)?;
} else {
writeln!(&mut inner, "{} {{", family.name())?;
}
Ok(GraphWriter {
inner: Some(inner),
edgeop: family,
})
}
pub fn default_node(&mut self, default_node: Node) -> io::Result<()> {
let fmt = self.inner.as_mut().unwrap();
write!(fmt, "\tnode [{}];", default_node)
}
pub fn default_edge(&mut self, default_edge: Edge) -> io::Result<()> {
let fmt = self.inner.as_mut().unwrap();
write!(fmt, "\tedge [{}];", default_edge)
}
pub fn segment<I, T>(&mut self, iter: I, options: Option<Edge>) -> io::Result<()>
where I: IntoIterator<Item=T>, T: Into<Id>
{
let fmt = self.inner.as_mut().unwrap();
let mut iter = iter.into_iter();
let begin = iter.next().unwrap();
let end = iter.next().unwrap();
write!(fmt, "\t{} {} {} ", begin.into(), self.edgeop.edgeop(), end.into())?;
for next in iter {
write!(fmt, "{} {} ", self.edgeop.edgeop(), next.into())?;
}
if let Some(options) = options {
writeln!(fmt, "[{}];", options)
} else {
writeln!(fmt, ";")
}
}
pub fn node(&mut self, id: Id, node: Option<Node>) -> io::Result<()> {
let fmt = self.inner.as_mut().unwrap();
write!(fmt, "\t{} ", id)?;
if let Some(options) = node {
writeln!(fmt, "[{}];", options)
} else {
writeln!(fmt, ";")
}
}
pub fn end_into_inner(mut self) -> (W, io::Result<()>) {
let mut inner = self.inner.take().unwrap();
let result = inner.write_all(b"}\n");
(inner, result)
}
}
impl<W: io::Write> Drop for GraphWriter<W> {
fn drop(&mut self) {
if let Some(writer) = self.inner.as_mut() {
writer.write_all(b"}\n").unwrap();
}
}
}
impl<'a, W: Write> GraphWriter<&'a mut W> {
pub fn subgraph(&mut self, _name: Option<String>) -> GraphWriter<&mut W> {
unimplemented!()
}
}
impl Node {
pub fn none() -> Self {
Node::default()
}
}
impl Edge {
pub fn none() -> Self {
Edge::default()
}
}
impl Family {
fn name(self) -> &'static str {
match self {
Family::Directed => "digraph",
Family::Undirected => "graph",
}
}
fn edgeop(self) -> &'static str {
match self {
Family::Directed => "->",
Family::Undirected => "--",
}
}
}
impl Id {
const LABEL: Id = Id(IdEnum::Raw(Cow::Borrowed("label")));
const PERIPHERIES: Id = Id(IdEnum::Raw(Cow::Borrowed("peripheries")));
}
impl IdEnum {
fn from_string_like<T>(name: T) -> Self
where T: Into<Cow<'static, str>>
{
let name = name.into();
let raw_identifier = |c: char| c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_';
let raw_identifier_begin = |c: &char| c.is_ascii_alphabetic() || *c == '_';
if name.as_ref().is_empty() {
return IdEnum::Str(name)
}
if name.as_ref().chars().all(|c| c.is_ascii_digit()) {
return IdEnum::Raw(name)
}
if name.as_ref().chars().all(raw_identifier) && name.as_ref().chars().nth(0).filter(raw_identifier_begin).is_some() {
return IdEnum::Raw(name)
}
let quote_count = name.bytes().filter(|c| *c == b'"').count();
let name = if quote_count > 0 {
let mut string = name.into_owned();
unsafe {
let vec = string.as_mut_vec();
let mut num_inserts = quote_count;
assert!(num_inserts > 0, "contains at least one quote");
assert!(!vec.is_empty(), "contains at least one quote");
let mut text_end = vec.len();
let new_len = vec.len().checked_add(num_inserts)
.expect("escaped string would not fit");
vec.resize(new_len, b'\\');
let mut new_end = new_len;
let slice = vec.as_mut_slice();
let base_ptr = slice.as_ptr() as usize;
while num_inserts > 0 {
let tail = slice[..text_end]
.rsplit(|c| *c == b'"').next().unwrap()
.as_ptr() as usize;
assert!(tail > base_ptr, "at least one quote left infront");
let quote_index = tail
.checked_sub(base_ptr).unwrap()
.checked_sub(1).unwrap();
let relative_end = text_end
.checked_sub(quote_index).unwrap();
slice[quote_index..new_end].rotate_right(relative_end);
num_inserts = num_inserts.checked_sub(1).unwrap();
new_end = quote_index + num_inserts;
text_end = quote_index;
}
}
string.into()
} else {
name
};
IdEnum::Str(name)
}
}
impl From<Cow<'static, str>> for Id {
fn from(id: Cow<'static, str>) -> Self {
Id(IdEnum::from_string_like(id))
}
}
impl From<&'static str> for Id {
fn from(id: &'static str) -> Self {
Id(IdEnum::from_string_like(Cow::from(id)))
}
}
impl From<String> for Id {
fn from(id: String) -> Self {
Id(IdEnum::from_string_like(Cow::from(id)))
}
}
impl From<usize> for Id {
fn from(id: usize) -> Self {
Id(IdEnum::Numeral(id))
}
}
impl From<isize> for Id {
fn from(id: isize) -> Self {
Id(IdEnum::INumeral(id))
}
}
impl fmt::Display for IdEnum {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match self {
IdEnum::Raw(id) => write!(f, "{}", id),
IdEnum::Numeral(id) => write!(f, "{}", id),
IdEnum::INumeral(id) => write!(f, "{}", id),
IdEnum::Str(id) => write!(f, "\"{}\"", id),
}
}
}
impl fmt::Display for Id {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
self.0.fmt(f)
}
}
impl fmt::Display for Node {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if let Some(label) = self.label.as_ref() {
write!(f, "{}={},", Id::LABEL, label)?;
}
if let Some(peripheries) = self.peripheries {
write!(f, "{}={},", Id::PERIPHERIES, peripheries)?;
}
Ok(())
}
}
impl fmt::Display for Edge {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
if let Some(label) = self.label.as_ref() {
write!(f, "{}={},", Id::LABEL, label)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identifiers() {
assert_eq!(format!("{}", Id::from("abc")), "abc");
assert_eq!(format!("{}", Id::from(0usize)), "0");
assert_eq!(format!("{}", Id::from(-1isize)), "-1");
assert_eq!(format!("{}", Id::from("123")), "123");
assert_eq!(format!("{}", Id::from("a string with spaces")), r#""a string with spaces""#);
assert_eq!(format!("{}", Id::from("\"")), r#""\"""#);
assert_eq!(format!("{}", Id::from("")), r#""""#);
}
}