Skip to content
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ Cargo.lock

# .dot file for tests
**/*.dot

# Editor related configuratigurations
.vscode
63 changes: 29 additions & 34 deletions src/arrow.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

/// This structure holds all information that can describe an arrow connected to
/// either start or end of an edge.
#[derive(Clone, Hash, PartialEq, Eq)]
Expand All @@ -16,9 +15,7 @@ impl Arrow {

/// Arrow constructor which returns a default arrow
pub fn default() -> Arrow {
Arrow {
arrows: vec![],
}
Arrow { arrows: vec![] }
}

/// Arrow constructor which returns an empty arrow
Expand All @@ -31,7 +28,7 @@ impl Arrow {
/// Arrow constructor which returns a regular triangle arrow, without modifiers
pub fn normal() -> Arrow {
Arrow {
arrows: vec![ArrowShape::normal()]
arrows: vec![ArrowShape::normal()],
}
}

Expand All @@ -47,12 +44,11 @@ impl Arrow {
let mut cow = String::new();
for arrow in &self.arrows {
cow.push_str(&arrow.to_dot_string());
};
}
cow
}
}


impl Into<Arrow> for [ArrowShape; 2] {
fn into(self) -> Arrow {
Arrow {
Expand Down Expand Up @@ -103,14 +99,13 @@ pub enum Side {
impl Side {
pub fn as_slice(self) -> &'static str {
match self {
Side::Left => "l",
Side::Left => "l",
Side::Right => "r",
Side::Both => "",
Side::Both => "",
}
}
}


/// This enumeration represents all possible arrow edge
/// as defined in [grapviz documentation](http://www.graphviz.org/content/arrow-shapes).
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
Expand Down Expand Up @@ -199,37 +194,37 @@ impl ArrowShape {
pub fn to_dot_string(&self) -> String {
let mut res = String::new();
match *self {
Box(fill, side) | ICurve(fill, side)| Diamond(fill, side) |
Inv(fill, side) | Normal(fill, side)=> {
Box(fill, side)
| ICurve(fill, side)
| Diamond(fill, side)
| Inv(fill, side)
| Normal(fill, side) => {
res.push_str(fill.as_slice());
match side {
Side::Left | Side::Right => res.push_str(side.as_slice()),
Side::Both => {},
Side::Both => {}
};
},
Dot(fill) => res.push_str(fill.as_slice()),
Crow(side) | Curve(side) | Tee(side)
| Vee(side) => {
match side {
Side::Left | Side::Right => res.push_str(side.as_slice()),
Side::Both => {},
}
}
NoArrow => {},
Dot(fill) => res.push_str(fill.as_slice()),
Crow(side) | Curve(side) | Tee(side) | Vee(side) => match side {
Side::Left | Side::Right => res.push_str(side.as_slice()),
Side::Both => {}
},
NoArrow => {}
};
match *self {
NoArrow => res.push_str("none"),
Normal(_, _) => res.push_str("normal"),
Box(_, _) => res.push_str("box"),
Crow(_) => res.push_str("crow"),
Curve(_) => res.push_str("curve"),
ICurve(_, _) => res.push_str("icurve"),
Diamond(_, _) => res.push_str("diamond"),
Dot(_) => res.push_str("dot"),
Inv(_, _) => res.push_str("inv"),
Tee(_) => res.push_str("tee"),
Vee(_) => res.push_str("vee"),
NoArrow => res.push_str("none"),
Normal(_, _) => res.push_str("normal"),
Box(_, _) => res.push_str("box"),
Crow(_) => res.push_str("crow"),
Curve(_) => res.push_str("curve"),
ICurve(_, _) => res.push_str("icurve"),
Diamond(_, _) => res.push_str("diamond"),
Dot(_) => res.push_str("dot"),
Inv(_, _) => res.push_str("inv"),
Tee(_) => res.push_str("tee"),
Vee(_) => res.push_str("vee"),
};
res
}
}
}
112 changes: 76 additions & 36 deletions src/edge.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use crate::{
arrow::{Arrow},
style::{Style},
utils::{quote_string},
};
use crate::{arrow::Arrow, style::Style, utils::quote_string};

/// `Graph`'s edge.
#[derive(Clone)]
pub struct Edge {
from: String,
to: String,
label: String,
label_url: String,
url: String,
style: Style,
start_arrow: Arrow,
end_arrow: Arrow,
Expand All @@ -18,66 +16,100 @@ pub struct Edge {

impl Edge {
pub fn new(from: &str, to: &str, label: &str) -> Self {
Edge { from: String::from(from), to: String::from(to), label: String::from(label), style: Style::None, start_arrow: Arrow::default(), end_arrow: Arrow::default(), color: None }
Edge {
from: String::from(from),
to: String::from(to),
label: String::from(label),
label_url: Default::default(),
color: None,
style: Style::None,
start_arrow: Arrow::default(),
end_arrow: Arrow::default(),
url: Default::default(),
}
}

pub fn label(&mut self, label: &str) -> Self {
let mut edge = self.clone();
edge.label = String::from(label);
edge
pub fn label(mut self, label: &str) -> Self {
self.label = String::from(label);
self
}

pub fn style(&mut self, style: Style) -> Self {
let mut edge = self.clone();
edge.style = style;
edge
pub fn style(mut self, style: Style) -> Self {
self.style = style;
self
}

pub fn color(&mut self, color: Option<&str>) -> Self {
let mut edge = self.clone();
edge.color = match color {
pub fn color(mut self, color: Option<&str>) -> Self {
self.color = match color {
Some(c) => Some(String::from(c)),
None => None
None => None,
};
edge
self
}

pub fn start_arrow(mut self, arrow: Arrow) -> Self {
self.start_arrow = arrow;
self
}

pub fn start_arrow(&mut self, arrow: Arrow) -> Self {
let mut edge = self.clone();
edge.start_arrow = arrow;
edge
pub fn end_arrow(mut self, arrow: Arrow) -> Self {
self.end_arrow = arrow;
self
}

pub fn end_arrow(&mut self, arrow: Arrow) -> Self {
let mut edge = self.clone();
edge.end_arrow = arrow;
edge
pub fn label_url(mut self, url: String) -> Self {
self.label_url = url;
self
}

pub fn url(mut self, url: String) -> Self {
self.url = url;
self
}

pub fn to_dot_string(&self, edge_symbol: &str) -> String {
let colorstring: String;
let escaped_label: &String = &quote_string(self.label.clone());
let start_arrow_s: String = self.start_arrow.to_dot_string();
let end_arrow_s: String = self.end_arrow.to_dot_string();
let escaped_label_url: &String = &quote_string(self.label_url.clone());
let escaped_url: &String = &quote_string(self.url.clone());

let mut text = vec!["\"", self.from.as_str(), "\" ",
edge_symbol, " ",
"\"", self.to.as_str(), "\"",];
let mut text = vec![
"\"",
self.from.as_str(),
"\" ",
edge_symbol,
" ",
"\"",
self.to.as_str(),
"\"",
];

text.push("[label=");
text.push(escaped_label.as_str());
text.push("]");

if !self.label_url.is_empty() {
text.push("[labelURL=");
text.push(escaped_label_url.as_str());
text.push("]");
}

if !self.url.is_empty() {
text.push("[URL=");
text.push(escaped_url.as_str());
text.push("]");
}

if self.style != Style::None {
text.push("[style=\"");
text.push(self.style.as_slice());
text.push("\"]");
}

let color: Option<String> = match &self.color {
Some(l) => {
Some((*l).clone())
},
Some(l) => Some((*l).clone()),
None => None,
};
if let Some(c) = color {
Expand All @@ -91,10 +123,18 @@ impl Edge {
let mut arrow_str: String = String::new();
if !self.start_arrow.is_default() || !self.end_arrow.is_default() {
if !self.end_arrow.is_default() {
arrow_text.push(vec!["arrowhead=\"", &end_arrow_s, "\""].into_iter().collect());
arrow_text.push(
vec!["arrowhead=\"", &end_arrow_s, "\""]
.into_iter()
.collect(),
);
}
if !self.start_arrow.is_default() {
arrow_text.push(vec!["arrowtail=\"", &start_arrow_s, "\""].into_iter().collect());
arrow_text.push(
vec!["arrowtail=\"", &start_arrow_s, "\""]
.into_iter()
.collect(),
);
}
if !self.start_arrow.is_default() && !self.end_arrow.is_default() {
arrow_text.push(String::from("dir=\"both\""));
Expand All @@ -110,4 +150,4 @@ impl Edge {
text.push(";");
return text.into_iter().collect();
}
}
}
42 changes: 28 additions & 14 deletions src/graph.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
use crate::{
node::{Node},
edge::{Edge}, subgraph::Subgraph,
};
use std::io::prelude::*;
use crate::{edge::Edge, node::Node, subgraph::Subgraph, utils::quote_string};
use std::io;
use std::io::prelude::*;

/// Entry point of this library, use `to_dot_string` to get the string output.
#[derive(Clone)]
pub struct Graph {
name: String,
kind: Kind,
url: String,
nodes: Vec<Node>,
edges: Vec<Edge>,
subgraph: Vec<Subgraph>
subgraph: Vec<Subgraph>,
}

impl Graph {
pub fn new(name: &str, kind: Kind) -> Graph {
Graph { name: String::from(name), kind: kind, nodes: vec![], edges: vec![], subgraph: vec![] }
Graph {
name: String::from(name),
kind: kind,
nodes: vec![],
edges: vec![],
subgraph: vec![],
url: Default::default(),
}
}

pub fn add_node(&mut self, node: Node) -> () {
Expand All @@ -31,6 +37,12 @@ impl Graph {
self.subgraph.push(subgraph.edgeop(self.kind.edgeop()))
}

pub fn url(&mut self, url: String) -> Self {
let mut graph = self.clone();
graph.url = url;
graph
}

pub fn to_dot_string(&self) -> io::Result<String> {
let mut writer = Vec::new();
self.render_opts(&mut writer).unwrap();
Expand All @@ -41,11 +53,7 @@ impl Graph {

/// Renders graph `g` into the writer `w` in DOT syntax.
/// (Main entry point for the library.)
fn render_opts<'a,
W: Write>
(&self,
w: &mut W)
-> io::Result<()> {
fn render_opts<'a, W: Write>(&self, w: &mut W) -> io::Result<()> {
fn writeln<W: Write>(w: &mut W, arg: &[&str]) -> io::Result<()> {
for &s in arg {
w.write_all(s.as_bytes())?;
Expand All @@ -58,6 +66,12 @@ impl Graph {
}

writeln(w, &[self.kind.keyword(), " ", self.name.as_str(), " {"])?;

if !self.url.is_empty() {
indent(w)?;
writeln(w, &["URL=", quote_string(self.url.clone()).as_str()])?;
}

for n in self.subgraph.iter() {
indent(w)?;
let mut text: Vec<&str> = vec![];
Expand Down Expand Up @@ -101,7 +115,7 @@ impl Kind {
pub fn keyword(&self) -> &'static str {
match *self {
Kind::Digraph => "digraph",
Kind::Graph => "graph"
Kind::Graph => "graph",
}
}

Expand All @@ -112,4 +126,4 @@ impl Kind {
Kind::Graph => "--",
}
}
}
}
Loading