From 9a82e0bdb7fcebe2661b6cea4cdc68ba4d930071 Mon Sep 17 00:00:00 2001 From: Lishen Date: Wed, 25 Oct 2023 13:04:43 +0300 Subject: [PATCH 1/2] add custom attribute adding function --- src/graph.rs | 39 ++++++++++++++++++++++++---------- src/node.rs | 60 ++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/src/graph.rs b/src/graph.rs index a7a1853..e8cb42d 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -1,9 +1,6 @@ -use crate::{ - node::{Node}, - edge::{Edge}, subgraph::Subgraph, utils::quote_string, -}; -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)] @@ -13,12 +10,21 @@ pub struct Graph { url: String, nodes: Vec, edges: Vec, - subgraph: Vec + subgraph: Vec, + attribs: Vec, } impl Graph { pub fn new(name: &str, kind: Kind) -> Graph { - Graph { name: String::from(name), kind: kind, nodes: vec![], edges: vec![], subgraph: vec![], url: Default::default() } + Graph { + name: String::from(name), + kind: kind, + nodes: vec![], + edges: vec![], + subgraph: vec![], + url: Default::default(), + attribs: vec![], + } } pub fn add_node(&mut self, node: Node) -> () { @@ -47,9 +53,15 @@ impl Graph { Ok(s) } + pub fn attrib(&self, name: &str, value: &str) -> Self { + let mut graph = self.clone(); + graph.attribs.push(format!("{}={}", name, value)); + 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: &mut W, arg: &[&str]) -> io::Result<()> { for &s in arg { w.write_all(s.as_bytes())?; @@ -63,7 +75,12 @@ impl Graph { writeln(w, &[self.kind.keyword(), " ", self.name.as_str(), " {"])?; - if !self.url.is_empty(){ + for attrib in &self.attribs { + indent(w)?; + writeln(w, &[&attrib])?; + } + + if !self.url.is_empty() { indent(w)?; writeln(w, &["URL=", quote_string(self.url.clone()).as_str()])?; } @@ -111,7 +128,7 @@ impl Kind { pub fn keyword(&self) -> &'static str { match *self { Kind::Digraph => "digraph", - Kind::Graph => "graph" + Kind::Graph => "graph", } } @@ -122,4 +139,4 @@ impl Kind { Kind::Graph => "--", } } -} \ No newline at end of file +} diff --git a/src/node.rs b/src/node.rs index b9a3b36..f07d955 100644 --- a/src/node.rs +++ b/src/node.rs @@ -1,30 +1,35 @@ /// each node is an index in a vector in the graph. // pub type Node = usize; - -use crate::{ - style::Style, - utils::{quote_string}, -}; +use crate::{style::Style, utils::quote_string}; /// `Graph`'s node #[derive(Clone)] pub struct Node { pub name: String, - label: String, + label: Option, style: Style, color: Option, shape: Option, - url: String + url: String, + attribs: Vec, } impl Node { pub fn new(name: &str) -> Self { - Node { name: new_name(name), label: String::from(name), style: Style::None, color: None, shape: None, url: Default::default() } + Node { + name: new_name(name), + label: None, + style: Style::None, + color: None, + shape: None, + url: Default::default(), + attribs: vec![], + } } pub fn label(&self, label: &str) -> Self { let mut node = self.clone(); - node.label = String::from(label); + node.label = Some(String::from(label)); node } @@ -38,7 +43,7 @@ impl Node { let mut node = self.clone(); match shape { Some(s) => node.shape = Some(String::from(s)), - None => node.shape = None + None => node.shape = None, } node } @@ -47,7 +52,7 @@ impl Node { let mut node = self.clone(); node.color = match color { Some(c) => Some(String::from(c)), - None => None + None => None, }; node } @@ -58,18 +63,38 @@ impl Node { node } + pub fn attrib(&self, name: &str, value: &str) -> Self { + let mut node = self.clone(); + node.attribs.push(format!("{}={}", name, value)); + node + } + pub fn to_dot_string(&self) -> String { let colorstring: String; - let escaped_label: String = quote_string(self.label.clone()); let escaped_url: String = quote_string(self.url.clone()); let shape: String; let mut text = vec!["\"", self.name.as_str(), "\""]; - text.push("[label="); - text.push(escaped_label.as_str()); - text.push("]"); + let escaped_label: String = if self.label.is_some() { + quote_string(self.label.clone().unwrap()) + } else { + quote_string("".to_owned()) + }; + + if self.label.is_some() { + text.push("[label="); + text.push(escaped_label.as_str()); + text.push("]"); + } + + let binding = self.attribs.join(","); + if !self.attribs.is_empty() { + text.push("["); + text.push(binding.as_str()); + text.push("]"); + } if !self.url.is_empty() { text.push("[URL="); @@ -100,7 +125,6 @@ impl Node { text.push(";"); return text.into_iter().collect(); } - } /// Check if the node's name is illegal. @@ -126,7 +150,7 @@ fn new_name(name: &str) -> String { if !chars.all(is_constituent) { panic!("The name of the node should only contain letter/number/underscore/dot") } - return String::from(name); + return String::from(name); fn is_letter_or_underscore_or_dot(c: char) -> bool { in_range('a', c, 'z') || in_range('A', c, 'Z') || c == '_' || c == '.' @@ -137,4 +161,4 @@ fn new_name(name: &str) -> String { fn in_range(low: char, c: char, high: char) -> bool { low as usize <= c as usize && c as usize <= high as usize } -} \ No newline at end of file +} From 17029d6abf7696ee4ee5d8319c2b7f985f71df86 Mon Sep 17 00:00:00 2001 From: Lishen Date: Thu, 26 Oct 2023 16:37:56 +0300 Subject: [PATCH 2/2] from custom fields move to common attributes --- src/edge.rs | 125 +++++++++++++++++++-------------------------------- src/graph.rs | 2 +- src/node.rs | 84 +++------------------------------- 3 files changed, 54 insertions(+), 157 deletions(-) diff --git a/src/edge.rs b/src/edge.rs index 7d602d3..036ae90 100644 --- a/src/edge.rs +++ b/src/edge.rs @@ -1,52 +1,41 @@ -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, - color: Option, + attribs: Vec, } 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), label_url: Default::default(), - color: None, style: Style::None, - start_arrow: Arrow::default(), end_arrow: Arrow::default(), - url: Default::default() + pub fn new(from: &str, to: &str) -> Self { + Edge { + from: String::from(from), + to: String::from(to), + start_arrow: Arrow::default(), + end_arrow: Arrow::default(), + attribs: vec![], } } pub fn label(&mut self, label: &str) -> Self { - let mut edge = self.clone(); - edge.label = String::from(label); - edge + self.attrib("label", label) } pub fn style(&mut self, style: Style) -> Self { - let mut edge = self.clone(); - edge.style = style; - edge + self.attrib("style", style.as_slice()) } - pub fn color(&mut self, color: Option<&str>) -> Self { + pub fn color(&mut self, color: &str) -> Self { + self.attrib("color", "e_string(color.to_owned())) + } + + pub fn attrib(&self, name: &str, value: &str) -> Self { let mut edge = self.clone(); - edge.color = match color { - Some(c) => Some(String::from(c)), - None => None - }; + edge.attribs.push(format!("{}={}", name, value)); edge } @@ -62,62 +51,33 @@ impl Edge { edge } - pub fn label_url(&mut self, url: String) -> Self { - let mut edge = self.clone(); - edge.label_url = url; - edge + pub fn label_url(&mut self, url: &str) -> Self { + self.attrib("labelURL", "e_string(url.to_owned())) } - pub fn url(&mut self, url: String) -> Self { - let mut edge = self.clone(); - edge.url = url; - edge + pub fn url(&mut self, url: &str) -> Self { + self.attrib("URL", "e_string(url.to_owned())) } pub fn to_dot_string(&self, edge_symbol: &str) -> String { - let colorstring: String; - let escaped_label: &String = "e_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 = "e_string(self.label_url.clone()); - let escaped_url: &String = "e_string(self.url.clone()); - - 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 mut text = vec![ + "\"", + self.from.as_str(), + "\" ", + edge_symbol, + " ", + "\"", + self.to.as_str(), + "\"", + ]; - let color: Option = match &self.color { - Some(l) => { - Some((*l).clone()) - }, - None => None, - }; - if let Some(c) = color { - colorstring = quote_string(c); - text.push("[color="); - text.push(&colorstring); + let binding = self.attribs.join(","); + if !self.attribs.is_empty() { + text.push("["); + text.push(binding.as_str()); text.push("]"); } @@ -125,10 +85,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\"")); @@ -140,8 +108,7 @@ impl Edge { arrow_str.push_str("]"); text.push(arrow_str.as_str()); } - text.push(";"); return text.into_iter().collect(); } -} \ No newline at end of file +} diff --git a/src/graph.rs b/src/graph.rs index e8cb42d..1595a1a 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -77,7 +77,7 @@ impl Graph { for attrib in &self.attribs { indent(w)?; - writeln(w, &[&attrib])?; + writeln(w, &[&attrib, ";"])?; } if !self.url.is_empty() { diff --git a/src/node.rs b/src/node.rs index f07d955..962c763 100644 --- a/src/node.rs +++ b/src/node.rs @@ -6,11 +6,6 @@ use crate::{style::Style, utils::quote_string}; #[derive(Clone)] pub struct Node { pub name: String, - label: Option, - style: Style, - color: Option, - shape: Option, - url: String, attribs: Vec, } @@ -18,49 +13,28 @@ impl Node { pub fn new(name: &str) -> Self { Node { name: new_name(name), - label: None, - style: Style::None, - color: None, - shape: None, - url: Default::default(), attribs: vec![], } } pub fn label(&self, label: &str) -> Self { - let mut node = self.clone(); - node.label = Some(String::from(label)); - node + self.attrib("label", label) } pub fn style(&self, style: Style) -> Self { - let mut node = self.clone(); - node.style = style; - node + self.attrib("style", style.as_slice()) } - pub fn shape(&self, shape: Option<&str>) -> Self { - let mut node = self.clone(); - match shape { - Some(s) => node.shape = Some(String::from(s)), - None => node.shape = None, - } - node + pub fn shape(&self, shape: &str) -> Self { + self.attrib("shape", shape) } - pub fn color(&self, color: Option<&str>) -> Self { - let mut node = self.clone(); - node.color = match color { - Some(c) => Some(String::from(c)), - None => None, - }; - node + pub fn color(&self, color: &str) -> Self { + self.attrib("color", "e_string(color.to_owned())) } pub fn url(&mut self, url: String) -> Self { - let mut node = self.clone(); - node.url = url; - node + self.attrib("URL", "e_string(url.to_owned())) } pub fn attrib(&self, name: &str, value: &str) -> Self { @@ -70,58 +44,14 @@ impl Node { } pub fn to_dot_string(&self) -> String { - let colorstring: String; - - let escaped_url: String = quote_string(self.url.clone()); - let shape: String; - let mut text = vec!["\"", self.name.as_str(), "\""]; - let escaped_label: String = if self.label.is_some() { - quote_string(self.label.clone().unwrap()) - } else { - quote_string("".to_owned()) - }; - - if self.label.is_some() { - text.push("[label="); - text.push(escaped_label.as_str()); - text.push("]"); - } - let binding = self.attribs.join(","); if !self.attribs.is_empty() { text.push("["); text.push(binding.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("\"]"); - } - - if let Some(c) = &self.color { - colorstring = quote_string(c.to_string()); - text.push("[color="); - text.push(&colorstring); - text.push("]"); - } - - if let Some(s) = self.shape.clone() { - shape = s; - text.push("[shape=\""); - text.push(&shape); - text.push("\"]"); - } - text.push(";"); return text.into_iter().collect(); }