├── .gitignore ├── .idea ├── .gitignore ├── graphviz-rust.iml ├── modules.xml └── vcs.xml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── dot-generator ├── Cargo.toml └── src │ └── lib.rs ├── dot-structures ├── Cargo.toml └── src │ └── lib.rs ├── dprint.json ├── into-attr-derive ├── Cargo.toml └── src │ └── lib.rs ├── into-attr ├── Cargo.toml └── src │ └── lib.rs └── src ├── attributes ├── generate.rs └── mod.rs ├── cmd.rs ├── grammar └── dot.pest ├── lib.rs ├── parser.rs └── printer.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | .idea -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /.idea/graphviz-rust.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - **`0.1.0`** 2 | - Initial implementation 3 | - **`0.2.0`** 4 | - Fix bugs 5 | - Change contract for the context* 6 | - **`0.3.0`** 7 | - Fix bugs with html tags in labels 8 | - **`0.4.0`** 9 | - Add method exec_dot 10 | - **`0.5.0`** 11 | - Reformat documentation 12 | - **`0.5.1`** 13 | - Fix a bug with a return type in exec 14 | - **`0.5.2`** 15 | - Fix a bug with the comment after graph without a new line 16 | - **`0.6.1`** 17 | - Fix a bug with names 18 | - add into params for command lines attrs 19 | - **`0.6.2`** 20 | - up the versions for the underlings 21 | - **`0.6.6`** 22 | - fix some bugs 23 | - **`0.7.0`** 24 | - fix some bugs with port parsing 25 | - **`0.7.2`** 26 | - fix formatting 27 | - **`0.8.0`** 28 | - update macros in the generator 29 | - add macros to construct graph attributes 30 | - **`0.9.1`** 31 | - add configurable comma between attributes on multiple lines to PrinterContext 32 | - **`0.9.2`** 33 | - Fix bugs with executing dot on Windows 34 | - **`0.9.3`** 35 | - make some methods public 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphviz-rust" 3 | version = "0.9.5" 4 | authors = ["BorisZhguchev "] 5 | categories = ["parsing", "visualization", "api-bindings"] 6 | edition = "2021" 7 | homepage = "https://github.com/besok/graphviz-rust" 8 | keywords = ["graph", "graphviz", "dotfile", "dot", "visualize"] 9 | license-file = "LICENSE" 10 | readme = "README.md" 11 | repository = "https://github.com/besok/graphviz-rust" 12 | description = "The library provides the basic access to the graphs in graphviz format with ability to import into or export from it." 13 | 14 | [features] 15 | graphviz-exec = [] 16 | default = ["graphviz-exec"] 17 | 18 | [dependencies] 19 | dot-generator = "0.2.0" 20 | dot-structures = "0.1.2" 21 | into-attr = "0.1.1" 22 | into-attr-derive = "0.2.1" 23 | pest = "2.0" 24 | pest_derive = "2.0" 25 | rand = "0.8.4" 26 | tempfile = "3.13.0" 27 | [dev-dependencies] 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2023] [Boris Zhguchev] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | The library provides the basic access to the graphs in [graphviz](https://graphviz.org/) format with ability to import 4 | into or export from it. 5 | 6 | ### Base examples 7 | 8 | #### Parse dot source 9 | 10 | ```rust 11 | use dot_generator::*; 12 | use dot_structures::*; 13 | 14 | fn parse_test() { 15 | let g: Graph = parse( 16 | r#" 17 | strict digraph t { 18 | aa[color=green] 19 | subgraph v { 20 | aa[shape=square] 21 | subgraph vv{a2 -> b2} 22 | aaa[color=red] 23 | aaa -> bbb 24 | } 25 | aa -> be -> subgraph v { d -> aaa} 26 | aa -> aaa -> v 27 | } 28 | "#, 29 | ) 30 | .unwrap(); 31 | 32 | assert_eq!( 33 | g, 34 | graph!(strict di id!("t"); 35 | node!("aa";attr!("color","green")), 36 | subgraph!("v"; 37 | node!("aa"; attr!("shape","square")), 38 | subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 39 | node!("aaa";attr!("color","red")), 40 | edge!(node_id!("aaa") => node_id!("bbb")) 41 | ), 42 | edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 43 | edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 44 | ) 45 | ) 46 | } 47 | ``` 48 | 49 | #### Print graph into dot source 50 | 51 | ```rust 52 | use dot_generator::*; 53 | use dot_structures::*; 54 | use graphviz_rust::printer::{DotPrinter, PrinterContext}; 55 | 56 | fn print_test() { 57 | let mut g = graph!(strict di id!("id")); 58 | assert_eq!( 59 | "strict digraph id {}".to_string(), 60 | g.print(&mut PrinterContext::default()) 61 | ); 62 | } 63 | ``` 64 | 65 | #### Transform graph into external formats with cmd engine 66 | 67 | ```rust 68 | use dot_generator::*; 69 | use dot_structures::*; 70 | use graphviz_rust::{ 71 | attributes::*, 72 | cmd::{CommandArg, Format}, 73 | exec, parse, 74 | printer::{DotPrinter, PrinterContext}, 75 | }; 76 | 77 | fn output_test() { 78 | let mut g = graph!(id!("id"); 79 | node!("nod"), 80 | subgraph!("sb"; 81 | edge!(node_id!("a") => subgraph!(; 82 | node!("n"; 83 | NodeAttributes::color(color_name::black), NodeAttributes::shape(shape::egg)) 84 | )) 85 | ), 86 | edge!(node_id!("a1") => node_id!(esc "a2")) 87 | ); 88 | let graph_svg = exec( 89 | g, 90 | &mut PrinterContext::default(), 91 | vec![Format::Svg.into()], 92 | ) 93 | .unwrap(); 94 | } 95 | ``` 96 | 97 | ### Structure 98 | 99 | The structure pursues to follow the dot [notation](https://graphviz.org/doc/info/lang.html) closely, therefore it has 100 | straight accordance. The structures can be found in `dot_structures::*` and has the following denotion: 101 | 102 | ```text 103 | strict digraph t { : graph with t as id 104 | aa[color=green] : node aa and attributes in [] 105 | subgraph v { : subgraph v 106 | aa[shape=square] : node aa in subgraph 107 | subgraph vv{a2 -> b2} : another subgraph carrying edge inside( a type of the edge is Pair) 108 | aaa[color=red] 109 | aaa -> subgraph { d -> aaa} : subgraph id is anonymous id 110 | } 111 | aa -> be -> d -> aaa : other edge with a type Chain 112 | } 113 | ``` 114 | 115 | ### Generate a dot structure 116 | 117 | The library provides a set of macros alleviating the process of graph construction. 118 | The details including examples for every macros are given in the documentation for the macros 119 | and can be found in the [ `dot_generator::*`](dot-generator/src/lib.rs) 120 | 121 | #### Example 122 | 123 | ```rust 124 | assert_eq!( 125 | node!("node_id"; attr!("atr1","val1"),attr!("atr2","val2")), 126 | node!( 127 | "node_id", 128 | vec![attr!("atr1", "val1"), attr!("atr2", "val2")] 129 | ) 130 | ); 131 | 132 | fn graph_test() { 133 | use dot_generator::*; 134 | use dot_structures::*; 135 | 136 | let g = r#" 137 | strict digraph t { 138 | aa[color=green] 139 | subgraph v { 140 | aa[shape=square] 141 | subgraph vv{a2 -> b2} 142 | aaa[color=red] 143 | aaa -> bbb 144 | } 145 | aa -> be -> subgraph v { d -> aaa} 146 | aa -> aaa -> v 147 | } 148 | "#; 149 | 150 | graph!(strict di id!("t"); 151 | node!("aa";attr!("color","green")), 152 | subgraph!("v"; 153 | node!("aa"; attr!("shape","square")), 154 | subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 155 | node!("aaa";attr!("color","red")), 156 | edge!(node_id!("aaa") => node_id!("bbb")) 157 | ), 158 | edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 159 | edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 160 | ); 161 | } 162 | ``` 163 | 164 | ### Attributes 165 | 166 | The graphviz provides an enormous amount of possible [attributes](https://graphviz.org/doc/info/attrs.html) and to 167 | support it, the library provides a set of structures alleviating the navigation among them namely: 168 | 169 | - custom attribute can be easily compound with the macros `attr!(id,id)` nevertheless another possible formats: 170 | - using named attributes like `graphviz_rust::attributes::color` for the `color` attribute 171 | - using the particular 172 | structures `graphviz_rust::attributes::{EdgeAttributes,SubgraphAttributes GraphAttributes, NodeAttributes}` 173 | grouping and displaying which attribute belongs to the struct. 174 | 175 | ```rust 176 | use dot_generator::*; 177 | use dot_structures::*; 178 | use graphviz_rust::attributes::{ 179 | color, color_name, GraphAttributes, NodeAttributes, 180 | }; 181 | use into_attr::IntoAttribute; 182 | 183 | fn test() { 184 | assert_eq!(GraphAttributes::center(true), attr!("center", true)); 185 | assert_eq!( 186 | NodeAttributes::color(color_name::antiquewhite1), 187 | attr!("color", "antiquewhite1") 188 | ); 189 | assert_eq!(color::default().into_attr(), attr!("color", "black")); 190 | } 191 | ``` 192 | 193 | ### Transform into string following a dot format 194 | 195 | The trait `DotPrinter` is summoned to transform a graph structure into string. 196 | 197 | ```rust 198 | use dot_generator::*; 199 | use dot_structures::*; 200 | use graphviz_rust::printer::{DotPrinter, PrinterContext}; 201 | 202 | fn subgraph_test() { 203 | let mut ctx = PrinterContext::default(); 204 | let s = 205 | subgraph!("id"; node!("abc"), edge!(node_id!("a") => node_id!("b"))); 206 | 207 | assert_eq!( 208 | s.print(&mut ctx), 209 | "subgraph id {\n abc\n a -- b \n}".to_string() 210 | ); 211 | } 212 | ``` 213 | 214 | The module allows adjusting some parameters such as indent step or line separator using `PrinterContext`: 215 | 216 | ```rust 217 | fn ctx() { 218 | use self::graphviz_rust::printer::PrinterContext; 219 | let mut ctx = PrinterContext::default(); 220 | 221 | ctx.always_inline(); // everything in one line 222 | ctx.with_semi(); // semicolon at the end of every element 223 | ctx.with_indent_step(4); // indent 4 (default 2) 224 | ctx.with_inline_size(60); // size indicating the line needs to break into multilines 225 | } 226 | ``` 227 | 228 | ### External formats and others using cmd engine 229 | 230 | The library provides an ability to use [command commands](https://graphviz.org/doc/info/command.html) from the rust 231 | code. 232 | The details are denoted in `graphviz_rust::{exec}` and `graphviz_rust::{exec_dot}` methods 233 | 234 | ```rust 235 | fn output_test() { 236 | let mut g = graph!(id!("id")); 237 | exec( 238 | g, 239 | PrinterContext::default(), 240 | vec![ 241 | Format::Svg.into(), 242 | CommandArg::Output("path_to_file".to_string()), 243 | ], 244 | ); 245 | } 246 | ``` 247 | 248 | ### Caveats 249 | 250 | #### The [command client](https://graphviz.org/download/) should be installed 251 | 252 | Since, the library operates with a cmd client to execute the commands, the client should be installed beforehand, otherwise, the errors like: `No file or directory found` or `program not found` (depending on the OS) will be popped up. 253 | -------------------------------------------------------------------------------- /dot-generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dot-generator" 3 | version = "0.2.0" 4 | edition = "2021" 5 | license-file = "../LICENSE" 6 | license = "MIT" 7 | description = "the set of macros to generate dot files" 8 | 9 | [dependencies] 10 | dot-structures = { path = "../dot-structures", version = "0.1.0" } 11 | -------------------------------------------------------------------------------- /dot-generator/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # The set of macros helping to generate the elements of graphviz 2 | //! The set helps to generate the major components of the graphviz dot notation 3 | //! endeavouring to follow comparatively close to the language [`notation`] 4 | //! 5 | //! [`notation`]: https://graphviz.org/doc/info/lang.html 6 | //! # Description: 7 | //! In overall, the format of macros is the following one: 8 | //! - name or id or any other markers 9 | //! - list of vec with a prefix , or seq of elems with a prefix ; 10 | //! 11 | //! # Note: 12 | //! - for the list of items the way to pass vec is the following one: element(.. , vec of items) 13 | //! - for the seq of items the way to pass several items is the following one: element(.. ; items+) 14 | //! 15 | //! # Examples: 16 | //! ```rust 17 | //! use dot_generator::*; 18 | //! use dot_structures::*; 19 | //! 20 | //! let g = r#" 21 | //! strict digraph t { 22 | //! aa[color=green] 23 | //! subgraph v { 24 | //! aa[shape=square] 25 | //! subgraph vv{a2 -> b2} 26 | //! aaa[color=red] 27 | //! aaa -> bbb 28 | //! } 29 | //! aa -> be -> subgraph v { d -> aaa} 30 | //! aa -> aaa -> v 31 | //! } 32 | //! "#; 33 | //! 34 | //! graph!(strict di id!("t"); 35 | //! node!("aa";attr!("color","green")), 36 | //! subgraph!("v"; 37 | //! node!("aa"; attr!("shape","square")), 38 | //! subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 39 | //! node!("aaa";attr!("color","red")), 40 | //! edge!(node_id!("aaa") => node_id!("bbb")) 41 | //! ), 42 | //! edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 43 | //! edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 44 | //! ); 45 | //! ``` 46 | use dot_structures::*; 47 | 48 | /// Constructs a port. 49 | /// Port consists of two parts: id and directions 50 | /// The both can be optional. 51 | /// 52 | /// # Arguments: 53 | /// - id: accepts id of the port. Can be constructed with id!. Can be omitted, like `port!(, "x")` 54 | /// - direction: accepts string. Can be omitted 55 | /// 56 | /// # Examples 57 | /// ```rust 58 | /// use dot_generator::*; 59 | /// use dot_structures::*; 60 | /// assert_eq!( 61 | /// port!(), 62 | /// Port(None,None) 63 | /// ); 64 | /// assert_eq!( 65 | /// port!(,""), 66 | /// Port(None,Some("".to_string())) 67 | /// ); 68 | /// assert_eq!( 69 | /// port!(id!(),""), 70 | /// Port(Some( Id::Anonymous("".to_string())),Some("".to_string())) 71 | /// ); 72 | /// assert_eq!( 73 | /// port!(id!()), 74 | /// Port(Some( Id::Anonymous("".to_string())),None) 75 | /// ); 76 | /// ``` 77 | #[macro_export] 78 | macro_rules! port { 79 | () => { 80 | Port(None, None) 81 | }; 82 | (, $str:expr) => { 83 | Port(None, Some($str.to_string())) 84 | }; 85 | ($id:expr, $str:expr) => { 86 | Port(Some($id), Some($str.to_string())) 87 | }; 88 | ($id:expr) => { 89 | Port(Some($id), None) 90 | }; 91 | } 92 | /// Constructs a node id. 93 | /// In short, it is a pair of id and port. 94 | /// 95 | /// # Arguments 96 | /// - id: id of the node, can be omitted. To construct id it accepts 97 | /// - ident (optional): `esc` or `html` prefix 98 | /// - Should be an expression with a result that can be transformed to string (with Display) 99 | /// - port: expects a `Port`. Can be constructed with `port!` 100 | /// 101 | /// # Examples 102 | /// ```rust 103 | /// use dot_generator::*; 104 | /// use dot_structures::*; 105 | /// 106 | /// assert_eq!( 107 | /// node_id!(), 108 | /// NodeId(Id::Anonymous("".to_owned()), None) 109 | /// ); 110 | /// 111 | /// assert_eq!( 112 | /// node_id!("plain"), 113 | /// NodeId(Id::Plain("plain".to_owned()), None) 114 | /// ); 115 | /// 116 | /// assert_eq!( 117 | /// node_id!("plain", port!()), 118 | /// NodeId(Id::Plain("plain".to_owned()), Some(Port(None,None))) 119 | /// ); 120 | /// 121 | /// assert_eq!( 122 | /// node_id!(esc "escaped"), 123 | /// NodeId(Id::Escaped("\"escaped\"".to_owned()),None) 124 | /// ); 125 | /// assert_eq!( 126 | /// node_id!(html "
escaped"), 127 | /// NodeId(Id::Html("
escaped".to_owned()),None) 128 | /// ); 129 | /// ``` 130 | #[macro_export] 131 | macro_rules! node_id { 132 | () => { NodeId(id!(),None) }; 133 | ($e:expr) => { NodeId(id!($e),None) }; 134 | ($e:expr, $p:expr) => { NodeId(id!($e),Some($p)) }; 135 | ($i:ident $e:expr) => { NodeId(id!($i$e),None) }; 136 | ($i:ident $e:expr, $p:expr) => { NodeId(id!($i$e),Some($p)) }; 137 | } 138 | 139 | /// Constructs an id for node or subgraph. 140 | /// # Arguments: 141 | /// - prefix (optional): 142 | /// - html: defines the id has html tags in the name 143 | /// - esc: encompasses the given name with quotes and denotes the string has escaped quotes 144 | /// - can be omitted. In that case, the plain id will be created. 145 | /// - Should be an expression with a result that can be transformed to string (with Display) 146 | /// # Examples: 147 | /// ```rust 148 | /// use dot_generator::*; 149 | /// use dot_structures::*; 150 | /// 151 | /// assert_eq!(id!(), Id::Anonymous("".to_string())); 152 | /// assert_eq!(id!(html "<>"), Id::Html("<>".to_string())); 153 | /// assert_eq!(id!("abc"), Id::Plain("abc".to_string())); 154 | /// assert_eq!(id!(esc "ab\"c"), Id::Escaped("\"ab\"c\"".to_string())); 155 | /// ``` 156 | #[macro_export] 157 | macro_rules! id { 158 | () => { 159 | Id::Anonymous("".to_string()) 160 | }; 161 | (html $e:expr) => { 162 | Id::Html(format!("{}", $e)) 163 | }; 164 | (esc $e:expr) => { 165 | Id::Escaped(format!("\"{}\"", $e)) 166 | }; 167 | ($e:expr) => { 168 | Id::Plain(format!("{}", $e)) 169 | }; 170 | } 171 | 172 | /// Constructs an attribute. 173 | /// Essentially it is a pair of key, value separated with `=` 174 | /// This macro composes internal parts of 2 ids 175 | /// 176 | /// # Arguments 177 | /// - key prefix (optional): esc or html or empty. See `id!` macro 178 | /// - id value for key. Should be an expression with a result that can be transformed to string 179 | /// - value prefix (optional): esc or html or empty. See `id!` macro 180 | /// - id value for value. Should be an expression with a result that can be transformed to string 181 | /// 182 | /// # Examples: 183 | /// ```rust 184 | /// use dot_generator::*; 185 | /// use dot_structures::*; 186 | /// 187 | /// assert_eq!(attr!("a","1"), Attribute(id!("a"), id!("1"))); 188 | /// assert_eq!(attr!(html "a","1"), Attribute(id!(html "a"), id!("1"))); 189 | /// assert_eq!(attr!(esc "a","1"), Attribute(id!(esc "a"), id!("1"))); 190 | /// assert_eq!(attr!(esc "a",esc "1"), Attribute(id!(esc "a"), id!(esc "1"))); 191 | /// assert_eq!(attr!("a",esc "1"), Attribute(id!("a"), id!(esc "1"))) 192 | /// ``` 193 | #[macro_export] 194 | macro_rules! attr { 195 | ($ik:ident $k:expr,$iv:ident $v:expr) => {Attribute(id!($ik $k),id!($iv $v))}; 196 | ($ik:ident $k:expr,$v:expr) => {Attribute(id!($ik $k),id!($v))}; 197 | ($k:expr, $iv:ident $v:expr) => {Attribute(id!($k),id!($iv $v))}; 198 | ($k:expr,$v:expr) => {Attribute(id!($k),id!($v))} 199 | } 200 | 201 | 202 | /// Constructs one of the elements of the graph: 203 | /// - Node : node in the graph 204 | /// - Subgraph: subgraph in the graph 205 | /// - Attribute: attribute of an element 206 | /// - GAttribute: attribute for the given graph or subgraph 207 | /// - Edge: edge in the graph 208 | /// 209 | /// # Argument: 210 | /// - one of the aforementioned items. 211 | /// 212 | /// # Examples: 213 | /// ```rust 214 | /// use dot_generator::*; 215 | /// use dot_structures::*; 216 | /// 217 | /// assert_eq!( 218 | /// stmt!(node!(esc "id")), // if the id has escaped string or needs to be placed in the quotes 219 | /// Stmt::Node(Node::new(NodeId(id!(esc "id"), None), vec![])) 220 | /// ); 221 | /// assert_eq!( 222 | /// stmt!(attr!("a","1")), 223 | /// Stmt::Attribute(Attribute(id!("a"), id!("1"))) 224 | /// ); 225 | /// assert_eq!( 226 | /// stmt!(attr!("a",esc "format")), 227 | /// Stmt::Attribute(Attribute(id!("a"), id!(esc "format"))) 228 | /// ); 229 | /// assert_eq!( 230 | /// stmt!(GraphAttributes::new("graph", vec![attr!("a",esc "format")])), 231 | /// Stmt::GAttribute(GraphAttributes::Graph(vec![attr!("a",esc "format")])) 232 | /// ); 233 | /// assert_eq!( 234 | /// stmt!(edge!(node_id!("a") => node_id!("b"))), 235 | /// Stmt::Edge(edge!(node_id!("a") => node_id!("b"))) 236 | /// ); 237 | /// ``` 238 | #[macro_export] 239 | macro_rules! stmt { 240 | ($k:expr) => { 241 | Stmt::from($k) 242 | }; 243 | } 244 | 245 | /// Constructs a subgraph. 246 | /// 247 | /// # Arguments: 248 | /// - prefix for id (optional): esc or html 249 | /// - id: id of the graph. Can be omitted but the separation needs to be left in like `; ...` 250 | /// - a collection of statements or variadic arguments but the delegates from the statement 251 | /// 252 | /// Therefore, the macros accepts two different forms where the first separator denotes it: 253 | /// - `subgraph!(id, vec![statement])` where statement can be constructed using the `stmt!` macro 254 | /// - `subgraph!(id; statement_delegate, statement_delegate2, ...) 255 | /// where the delegates are undertaking structures. See `stmt! macro 256 | /// 257 | /// # Note: 258 | /// - id can be left empty with varargs like that `subgraph!(; statement1, ...)` 259 | /// - the macros expects the statement delegates, e.g. nodes, edges, attributes. See `stmt!` macro 260 | /// 261 | /// # Examples: 262 | /// ```rust 263 | /// use dot_generator::*; 264 | /// use dot_structures::*; 265 | /// 266 | /// assert_eq!( 267 | /// subgraph!(), 268 | /// Subgraph { id: Id::Anonymous("".to_string()), stmts: vec![] } 269 | /// ); 270 | /// 271 | /// assert_eq!( 272 | /// subgraph!( 273 | /// "abc"; 274 | /// node!("node1"), 275 | /// node!("node2"), 276 | /// edge!(node_id!("node1") => node_id!("node2")) 277 | /// ), 278 | /// Subgraph { 279 | /// id: id!("abc"), 280 | /// stmts: vec![ 281 | /// node!("node1").into(), 282 | /// node!("node2").into(), 283 | /// edge!(node_id!("node1") => node_id!("node2")).into() 284 | /// ], 285 | /// }); 286 | /// assert_eq!( 287 | /// subgraph!( 288 | /// "abc", 289 | /// vec![stmt!(node!("node1")), stmt!(attr!("a","b"))]), 290 | /// Subgraph { 291 | /// id: id!("abc"), 292 | /// stmts: vec![ 293 | /// node!("node1").into(), 294 | /// attr!("a","b").into() 295 | /// ], 296 | /// }); 297 | /// assert_eq!( 298 | /// subgraph!( 299 | /// esc "abc"; 300 | /// edge!(node_id!("left") => node_id!("right"))), 301 | /// Subgraph { 302 | /// id: id!(esc "abc"), 303 | /// stmts: vec![ 304 | /// edge!(node_id!("left") => node_id!("right")).into() 305 | /// ], 306 | /// }); 307 | /// assert_eq!(subgraph!(; edge!(node_id!("left") => node_id!("right"))), 308 | /// Subgraph { 309 | /// id: Id::Anonymous("".to_string()), 310 | /// stmts: vec![ 311 | /// edge!(node_id!("left") => node_id!("right")).into() 312 | /// ], 313 | /// }); 314 | /// ``` 315 | #[macro_export] 316 | macro_rules! subgraph { 317 | () => {Subgraph{id:id!(),stmts:vec![]}}; 318 | ($id:expr) => {Subgraph{id:id!($id),stmts:vec![]}}; 319 | ($i:ident $id:expr) => {Subgraph{id:id!($i$id),stmts:vec![]}}; 320 | ($id:expr, $stmts:expr) => {Subgraph{id:id!($id),stmts:$stmts}}; 321 | ($i:ident $id:expr, $stmts:expr) => {Subgraph{id:id!($i$id),stmts:$stmts}}; 322 | ($i:ident $id:expr; $($stmts:expr),+ ) => {{ 323 | let mut stmts_vec = Vec::new(); 324 | $( stmts_vec.push(stmt!($stmts)) ; )+ 325 | Subgraph{id:id!($i$id),stmts:stmts_vec} 326 | }}; 327 | ($id:expr; $($stmts:expr),+ ) => {{ 328 | let mut stmts_vec = Vec::new(); 329 | $( stmts_vec.push(stmt!($stmts)) ; )+ 330 | Subgraph{id:id!($id),stmts:stmts_vec} 331 | }}; 332 | (; $($stmts:expr),+ ) => {{ 333 | let mut stmts_vec = Vec::new(); 334 | $( stmts_vec.push(stmt!($stmts)) ; )+ 335 | Subgraph{id:id!(),stmts:stmts_vec} 336 | }}; 337 | } 338 | 339 | /// Constructs `GraphAttributes` 340 | /// 341 | /// The GraphAttributes can be either a graph based, edge based or node based. 342 | /// 343 | /// # Arguments: 344 | /// - prefix: graph, edge, node 345 | /// - a collection of attributes or variadic arguments of attributes 346 | /// 347 | /// Therefore, the macros accepts two different forms where the first separator denotes it: 348 | /// - `attrs!(prefix, vec![attrs])` where attr can be constructed using the `attr!` macro 349 | /// - `attrs!(prefix; attr1, attr2, ...)` 350 | /// 351 | /// # Examples: 352 | /// ```rust 353 | /// use dot_generator::*; 354 | /// use dot_structures::*; 355 | /// 356 | /// assert_eq!( 357 | /// attrs!(), 358 | /// GraphAttributes::new("graph", vec![]) 359 | /// ); 360 | /// assert_eq!( 361 | /// attrs!(edge vec![attr!("a","b")]), 362 | /// GraphAttributes::new("edge", vec![attr!("a","b")]) 363 | /// ); 364 | /// assert_eq!( 365 | /// attrs!(graph vec![attr!("a","b")]), 366 | /// GraphAttributes::new("graph", vec![attr!("a","b")]) 367 | /// ); 368 | /// assert_eq!( 369 | /// attrs!(node vec![attr!("a","b")]), 370 | /// GraphAttributes::new("node", vec![attr!("a","b")]) 371 | /// ); 372 | /// assert_eq!( 373 | /// attrs!(node; attr!("a","b"), attr!("c","d")), 374 | /// GraphAttributes::new("node", vec![attr!("a","b"),attr!("c","d")]) 375 | /// ); 376 | /// ``` 377 | #[macro_export] 378 | macro_rules! attrs { 379 | () => {GraphAttributes::Graph(vec![])}; 380 | (graph $attrs:expr) => {GraphAttributes::Graph($attrs)}; 381 | (node $attrs:expr) => {GraphAttributes::Node($attrs)}; 382 | (edge $attrs:expr) => {GraphAttributes::Edge($attrs)}; 383 | (graph; $($attrs:expr),+ ) => {{ 384 | let mut attrs_vec = Vec::new(); 385 | $( attrs_vec.push($attrs) ; )+ 386 | GraphAttributes::Graph(attrs_vec) 387 | }}; 388 | (node; $($attrs:expr),+ ) => {{ 389 | let mut attrs_vec = Vec::new(); 390 | $( attrs_vec.push($attrs) ; )+ 391 | GraphAttributes::Node(attrs_vec) 392 | }}; 393 | (edge; $($attrs:expr),+ ) => {{ 394 | let mut attrs_vec = Vec::new(); 395 | $( attrs_vec.push($attrs) ; )+ 396 | GraphAttributes::Edge(attrs_vec) 397 | }}; 398 | 399 | } 400 | 401 | /// Constructs a node. 402 | /// 403 | /// # Arguments: 404 | /// - prefix for id (optional): esc or html 405 | /// - id(optional): the id for the node 406 | /// - port(optional): the port of the node in a format `id => port` 407 | /// - attributes(optional): either a vec of attributes or attributes in a form of variadic arguments 408 | /// 409 | /// 410 | /// # Examples: 411 | /// ```rust 412 | /// use dot_generator::*; 413 | /// use dot_structures::*; 414 | /// 415 | /// assert_eq!(node!(), Node::new(NodeId(id!(), None), vec![])); 416 | /// 417 | /// assert_eq!(node!(html "abc"; attr!("a","a")), 418 | /// Node::new(NodeId(id!(html "abc"), None), 419 | /// vec![attr!("a","a")])); 420 | /// 421 | /// assert_eq!(node!(esc "abc"; attr!("a","a")), 422 | /// Node::new(NodeId(id!(esc "abc"), None), 423 | /// vec![attr!("a","a")])); 424 | /// 425 | /// assert_eq!(node!("abc" ; attr!("a","a"), attr!("b","b")), 426 | /// Node::new( 427 | /// NodeId(id!("abc"), None), 428 | /// vec![attr!("a","a"),attr!("b","b")] 429 | /// ) 430 | /// ); 431 | /// 432 | /// assert_eq!(node!("abc" , vec![attr!("a","a"),attr!("a","a")]), 433 | /// Node::new(NodeId(id!( "abc"), None), 434 | /// vec![attr!("a","a"), attr!("a","a")]) 435 | /// ); 436 | /// 437 | /// assert_eq!( 438 | /// node!("abc" => port!(,"id") ; attr!("a","a"),attr!("a","a")), 439 | /// Node::new( 440 | /// NodeId(id!( "abc"), Some(Port(None,Some("id".to_string())))), 441 | /// vec![attr!("a","a"), attr!("a","a")]) 442 | /// ); 443 | /// 444 | /// assert_eq!( 445 | /// node!(esc "abc" => port!(id!("port_id")) ; 446 | /// attr!("a","a"),attr!("a","a")), 447 | /// Node::new(NodeId(id!(esc "abc"), Some(port!(id!("port_id")))), 448 | /// vec![attr!("a","a"), attr!("a","a")]) 449 | /// ); 450 | /// ``` 451 | #[macro_export] 452 | macro_rules! node { 453 | () => {Node::new(NodeId(id!(), None), vec![])}; 454 | ($i:ident $id:expr) => {Node::new(NodeId(id!($i$id), None), vec![])}; 455 | ($id:expr) => {Node::new(NodeId(id!($id), None), vec![])}; 456 | ($i:ident $id:expr; $($attr:expr),+ ) => {{ 457 | let mut attrs = Vec::new(); 458 | $( attrs.push($attr) ; )+ 459 | Node::new(NodeId(id!($i$id), None), attrs) 460 | }}; 461 | ($i:ident $id:expr, $attrs:expr ) => { 462 | Node::new(NodeId(id!($i$id), None), $attrs) 463 | }; 464 | ($id:expr, $attrs:expr ) => { 465 | Node::new(NodeId(id!($id), None), $attrs) 466 | }; 467 | ( $id:expr; $($attr:expr),+ ) => {{ 468 | let mut attrs = Vec::new(); 469 | $( attrs.push($attr) ; )+ 470 | Node::new(NodeId(id!( $id), None), attrs) 471 | }}; 472 | ($i:ident $id:expr => $p:expr, $attrs:expr ) => { 473 | Node::new(NodeId(id!($i$id), Some($p)), $attrs) 474 | }; 475 | ($i:ident $id:expr => $p:expr; $($attr:expr),+ ) => {{ 476 | let mut attrs = Vec::new(); 477 | $( attrs.push($attr) ; )+ 478 | Node::new(NodeId(id!($i$id), Some($p)), attrs) 479 | }}; 480 | ( $id:expr => $p:expr, $attrs:expr ) => { 481 | Node::new(NodeId(id!($id), Some($p)), $attrs) 482 | }; 483 | ( $id:expr => $p:expr; $($attr:expr),+ ) => {{ 484 | let mut attrs = Vec::new(); 485 | $( attrs.push($attr) ; )+ 486 | Node::new(NodeId(id!($id), Some($p)), attrs) 487 | }}; 488 | } 489 | 490 | /// Constructs an edge. 491 | /// 492 | /// # Arguments: 493 | /// - chain of edges separated by `=>`: the edge expects either `node_id!` or `subgraph!` 494 | /// - attributes: either a vec of attributes or attributes in a form of variadic arguments 495 | /// 496 | /// # Examples: 497 | /// 498 | /// ```rust 499 | /// use dot_generator::*; 500 | /// use dot_structures::*; 501 | /// 502 | /// assert_eq!( 503 | /// edge!(node_id!("1") => node_id!("2")), 504 | /// Edge { 505 | /// ty: EdgeTy::Pair( 506 | /// Vertex::N(node_id!("1")), 507 | /// Vertex::N(node_id!("2")) 508 | /// ), 509 | /// attributes: vec![] 510 | /// } 511 | /// ); 512 | /// 513 | /// assert_eq!( 514 | /// edge!( 515 | /// node_id!("1") => node_id!("2") => subgraph!("a") 516 | /// ), 517 | /// Edge { 518 | /// ty: EdgeTy::Chain(vec![ 519 | /// Vertex::N(node_id!("1")), 520 | /// Vertex::N(node_id!("2")), 521 | /// Vertex::S(subgraph!("a"))]), 522 | /// attributes: vec![] } 523 | /// ); 524 | /// 525 | /// assert_eq!( 526 | /// edge!( 527 | /// node_id!("1") => node_id!("2"), vec![attr!("a","b")] 528 | /// ), 529 | /// Edge { 530 | /// ty: EdgeTy::Pair( 531 | /// Vertex::N(node_id!("1")), 532 | /// Vertex::N(node_id!("2")) 533 | /// ), 534 | /// attributes: vec![attr!("a","b")] } 535 | /// ); 536 | /// 537 | /// assert_eq!( 538 | /// edge!(node_id!("1") => node_id!("2"); 539 | /// attr!("a","b"), attr!("a1","b1")), 540 | /// Edge { 541 | /// ty: EdgeTy::Pair( 542 | /// Vertex::N(node_id!("1")), 543 | /// Vertex::N(node_id!("2")) 544 | /// ), 545 | /// attributes: vec![attr!("a","b"), attr!("a1","b1")] } 546 | /// ); 547 | /// ``` 548 | #[macro_export] 549 | macro_rules! edge { 550 | ($l:expr => $r:expr) => { 551 | Edge{ ty: EdgeTy::Pair(Vertex::from($l),Vertex::from($r)), attributes: vec![] } 552 | }; 553 | ($l:expr => $r:expr $(=> $nexts:expr)+) => {{ 554 | let mut edges_vec = vec![Vertex::from($l),Vertex::from($r)]; 555 | $( edges_vec.push(Vertex::from($nexts)) ; )+ 556 | 557 | Edge{ ty: EdgeTy::Chain(edges_vec), attributes: vec![] } 558 | }}; 559 | 560 | ($l:expr => $r:expr, $attrs:expr) => { 561 | Edge{ ty: EdgeTy::Pair(Vertex::from($l),Vertex::from($r)), attributes: $attrs }; 562 | }; 563 | ($l:expr => $r:expr; $($attrs:expr),+) => {{ 564 | let mut attrs_vec = Vec::new(); 565 | $( attrs_vec.push($attrs) ; )+ 566 | Edge{ ty: EdgeTy::Pair(Vertex::from($l),Vertex::from($r)), attributes: attrs_vec } 567 | }}; 568 | ($l:expr => $r:expr $(=> $nexts:expr)+; $($attrs:expr),+) => {{ 569 | let mut attrs_vec = Vec::new(); 570 | $( attrs_vec.push($attrs) ; )+ 571 | 572 | let mut edges_vec = vec![Vertex::from($l),Vertex::from($r)]; 573 | $( edges_vec.push(Vertex::from($nexts)) ; )+ 574 | 575 | Edge{ ty: EdgeTy::Chain(edges_vec), attributes: attrs_vec } 576 | }}; 577 | ($l:expr => $r:expr $(=> $nexts:expr)+ , $attrs:expr) => {{ 578 | 579 | let mut edges_vec = vec![Vertex::from($l),Vertex::from($r)] 580 | $( edges_vec.push(Vertex::from($nexts)) ; )+ 581 | 582 | Edge{ ty: EdgeTy::Chain(edges_vec), attributes: $attrs } 583 | }}; 584 | } 585 | 586 | /// Constructs a graph. 587 | /// 588 | /// # Arguments 589 | /// - prefix strict(optional): the prefix defines the strictness of the graph 590 | /// - prefix di(optional): the prefix defines the graph is a digraph 591 | /// - id: id of the graph. Can be constructed using `id!` macros 592 | /// - a collection of statements or variadic arguments but the delegates from the statement 593 | /// 594 | /// Therefore, the macros accepts two different forms where the first separator denotes it: 595 | /// - `graph!(id, vec![statement])` where statement can be constructed using the `stmt!` macro 596 | /// - `graph!(id; statement_delegate, statement_delegate2, ...) 597 | /// where the delegates are undertaking structures. See `stmt! macro 598 | /// 599 | /// # Note: 600 | /// - the macros expects the statement delegates, e.g. nodes, edges, attributes. See `stmt!` macro 601 | /// 602 | /// # Examples: 603 | /// ``` 604 | /// use dot_generator::*; 605 | /// use dot_structures::*; 606 | /// 607 | /// assert_eq!( 608 | /// graph!(strict di id!("abc")), 609 | /// Graph::DiGraph { id: id!("abc"), strict: true, stmts: vec![] } 610 | /// ); 611 | /// 612 | /// assert_eq!( 613 | /// graph!(strict di id!("abc"); 614 | /// node!("abc"), 615 | /// node!("cde") 616 | /// ), 617 | /// Graph::DiGraph { 618 | /// id: id!("abc"), 619 | /// strict: true, 620 | /// stmts: vec![stmt!(node!("abc")),stmt!(node!("cde"))] } 621 | /// ); 622 | /// 623 | /// assert_eq!( 624 | /// graph!(di id!("abc"); 625 | /// edge!(node_id!("a") => node_id!("b"); attr!("a","b")), 626 | /// subgraph!( 627 | /// "abc", 628 | /// vec![stmt!(node!("node1")), stmt!(attr!("a","b"))] 629 | /// ) 630 | /// ), 631 | /// Graph::DiGraph { 632 | /// id: id!("abc"), 633 | /// strict: false, 634 | /// stmts: vec![ 635 | /// edge!(node_id!("a") => node_id!("b"); attr!("a","b")).into(), 636 | /// subgraph!( 637 | /// "abc", 638 | /// vec![stmt!(node!("node1")), stmt!(attr!("a","b"))] 639 | /// ).into() 640 | /// ] 641 | /// }); 642 | /// 643 | /// assert_eq!( 644 | /// graph!(di id!("abc"), 645 | /// vec![ 646 | /// stmt!(edge!(node_id!("a") => node_id!("b"); attr!("a","b"))), 647 | /// stmt!(node!("e")), 648 | /// stmt!(attr!("attr","val")), 649 | /// ] 650 | /// ), 651 | /// Graph::DiGraph { 652 | /// id: id!("abc"), 653 | /// strict: false, 654 | /// stmts: vec![ 655 | /// edge!(node_id!("a") => node_id!("b"); attr!("a","b")).into(), 656 | /// node!("e").into(), 657 | /// attr!("attr","val").into() 658 | /// ] } 659 | /// ); 660 | /// 661 | /// assert_eq!( 662 | /// graph!(di id!("abc"), 663 | /// vec![ 664 | /// stmt!(edge!(node_id!("a") => node_id!("b"); attr!("a","b"))), 665 | /// stmt!(node!("e")), 666 | /// stmt!(attr!("attr","val")), 667 | /// stmt!(attrs!(graph; attr!("attr","val"))), 668 | /// ] 669 | /// ), 670 | /// Graph::DiGraph { 671 | /// id: id!("abc"), 672 | /// strict: false, 673 | /// stmts: vec![ 674 | /// edge!(node_id!("a") => node_id!("b"); attr!("a","b")).into(), 675 | /// node!("e").into(), 676 | /// attr!("attr","val").into(), 677 | /// attrs!(graph; attr!("attr","val")).into() 678 | /// ] } 679 | /// ); 680 | /// ``` 681 | #[macro_export] 682 | macro_rules! graph { 683 | (strict $id:expr) => { 684 | Graph::Graph { id: $id, strict: true, stmts: vec![] } 685 | }; 686 | ($id:expr) => { 687 | Graph::Graph { id: $id, strict: false, stmts: vec![] } 688 | }; 689 | (strict di $id:expr) => { 690 | Graph::DiGraph { id: id!($id), strict: true, stmts: vec![] } 691 | }; 692 | (di $id:expr) => { 693 | Graph::DiGraph { id: id!($id), strict: false, stmts: vec![] } 694 | }; 695 | (strict $id:expr, $stmts:expr) => { 696 | Graph::Graph { id: $id, strict: true, stmts: $stmts } 697 | }; 698 | ($id:expr, $stmts:expr) => { 699 | Graph::Graph { id: $id, strict: false, stmts: $stmts } 700 | }; 701 | (strict di $id:expr, $stmts:expr) => { 702 | Graph::DiGraph { id: $id, strict: true, stmts: $stmts } 703 | }; 704 | (di $id:expr, $stmts:expr) => { 705 | Graph::DiGraph { id: $id, strict: false, stmts: $stmts } 706 | }; 707 | 708 | (strict $id:expr; $($stmts:expr),+) => {{ 709 | let mut stmts = vec![]; 710 | $( stmts.push(stmt!($stmts)) ; )+ 711 | Graph::Graph { id: $id, strict: true, stmts: stmts } 712 | }}; 713 | ($id:expr; $($stmts:expr),+) => {{ 714 | let mut stmts = vec![]; 715 | $( stmts.push(stmt!($stmts)) ; )+ 716 | Graph::Graph { id: $id, strict: false, stmts: stmts } 717 | }}; 718 | (strict di $id:expr; $($stmts:expr),+) => {{ 719 | let mut stmts = vec![]; 720 | $( stmts.push(stmt!($stmts)) ; )+ 721 | Graph::DiGraph { id: $id, strict: true, stmts: stmts } 722 | }}; 723 | (di $id:expr; $($stmts:expr),+) => {{ 724 | let mut stmts = vec![]; 725 | $( stmts.push(stmt!($stmts)) ; )+ 726 | Graph::DiGraph { id: $id, strict: false, stmts: stmts } 727 | }}; 728 | 729 | } 730 | 731 | #[cfg(test)] 732 | mod tests { 733 | use dot_structures::*; 734 | 735 | #[test] 736 | fn port_test() { 737 | assert_eq!( 738 | port!(), 739 | Port(None, None) 740 | ); 741 | assert_eq!( 742 | port!(,""), 743 | Port(None, Some("".to_string())) 744 | ); 745 | assert_eq!( 746 | port!(id!(),""), 747 | Port(Some(Id::Anonymous("".to_string())), Some("".to_string())) 748 | ); 749 | assert_eq!( 750 | port!(id!()), 751 | Port(Some(Id::Anonymous("".to_string())), None) 752 | ); 753 | } 754 | 755 | #[test] 756 | fn node_id_test() { 757 | assert_eq!( 758 | node_id!(), 759 | NodeId(Id::Anonymous("".to_owned()), None) 760 | ); 761 | 762 | assert_eq!( 763 | node_id!("plain"), 764 | NodeId(Id::Plain("plain".to_owned()), None) 765 | ); 766 | 767 | assert_eq!( 768 | node_id!("plain", port!()), 769 | NodeId(Id::Plain("plain".to_owned()), Some(Port(None, None))) 770 | ); 771 | 772 | assert_eq!( 773 | node_id!(esc "escaped"), 774 | NodeId(Id::Escaped("\"escaped\"".to_owned()), None) 775 | ); 776 | assert_eq!( 777 | node_id!(html "
escaped"), 778 | NodeId(Id::Html("
escaped".to_owned()), None) 779 | ); 780 | } 781 | 782 | #[test] 783 | fn graph_test() { 784 | assert_eq!( 785 | graph!(strict di id!("abc")), 786 | Graph::DiGraph { 787 | id: id!("abc"), 788 | strict: true, 789 | stmts: vec![], 790 | } 791 | ); 792 | assert_eq!( 793 | graph!(strict di id!("abc");stmt!(node!("abc"))), 794 | Graph::DiGraph { 795 | id: id!("abc"), 796 | strict: true, 797 | stmts: vec![stmt!(node!("abc"))], 798 | } 799 | ); 800 | } 801 | 802 | #[test] 803 | fn edge_test() { 804 | assert_eq!( 805 | edge!(node_id!("1") => node_id!("2")), 806 | Edge { 807 | ty: EdgeTy::Pair(Vertex::N(node_id!("1")), Vertex::N(node_id!("2"))), 808 | attributes: vec![], 809 | } 810 | ); 811 | assert_eq!( 812 | edge!(node_id!("1") => node_id!("2") => subgraph!("a")), 813 | Edge { 814 | ty: EdgeTy::Chain(vec![ 815 | Vertex::N(node_id!("1")), 816 | Vertex::N(node_id!("2")), 817 | Vertex::S(subgraph!("a")), 818 | ]), 819 | attributes: vec![], 820 | } 821 | ); 822 | assert_eq!( 823 | edge!(node_id!("1") => node_id!("2"), vec![attr!("a","b")]), 824 | Edge { 825 | ty: EdgeTy::Pair(Vertex::N(node_id!("1")), Vertex::N(node_id!("2"))), 826 | attributes: vec![attr!("a", "b")], 827 | } 828 | ); 829 | assert_eq!( 830 | edge!(node_id!("1") => node_id!("2"); attr!("a","b")), 831 | Edge { 832 | ty: EdgeTy::Pair(Vertex::N(node_id!("1")), Vertex::N(node_id!("2"))), 833 | attributes: vec![attr!("a", "b")], 834 | } 835 | ); 836 | } 837 | 838 | #[test] 839 | fn stmt_test() { 840 | assert_eq!( 841 | stmt!(node!()), 842 | Stmt::Node(Node::new(NodeId(id!(), None), vec![])) 843 | ); 844 | } 845 | 846 | #[test] 847 | fn subgraph_test() { 848 | assert_eq!( 849 | subgraph!(), 850 | Subgraph { 851 | id: Id::Anonymous("".to_string()), 852 | stmts: vec![], 853 | } 854 | ); 855 | assert_eq!( 856 | subgraph!("abc";node!()), 857 | Subgraph { 858 | id: Id::Plain("abc".to_string()), 859 | stmts: vec![stmt!(node!())], 860 | } 861 | ); 862 | } 863 | 864 | #[test] 865 | fn node_test() { 866 | assert_eq!(node!(), Node::new(NodeId(id!(), None), vec![])); 867 | assert_eq!( 868 | node!(html "abc"; attr!("a","a")), 869 | Node::new(NodeId(id!(html "abc"), None), vec![attr!("a", "a")]) 870 | ); 871 | assert_eq!( 872 | node!(html "abc" ; attr!("a","a")), 873 | Node::new(NodeId(id!(html "abc"), None), vec![attr!("a", "a")]) 874 | ); 875 | assert_eq!( 876 | node!("abc" ; attr!("a","a"),attr!("a","a")), 877 | Node::new( 878 | NodeId(id!("abc"), None), 879 | vec![attr!("a", "a"), attr!("a", "a")], 880 | ) 881 | ) 882 | } 883 | 884 | #[test] 885 | fn attr_test() { 886 | assert_eq!(attr!("a", "1"), Attribute(id!("a"), id!("1"))); 887 | assert_eq!(attr!(html "a","1"), Attribute(id!(html "a"), id!("1"))) 888 | } 889 | 890 | #[test] 891 | fn id_test() { 892 | assert_eq!(id!(), Id::Anonymous("".to_string())); 893 | assert_eq!(id!(html "<>"), Id::Html("<>".to_string())); 894 | assert_eq!(id!("abc"), Id::Plain("abc".to_string())); 895 | assert_eq!(id!(esc "ab\\\"c"), Id::Escaped("\"ab\\\"c\"".to_string())); 896 | } 897 | } 898 | -------------------------------------------------------------------------------- /dot-structures/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dot-structures" 3 | version = "0.1.2" 4 | edition = "2021" 5 | license-file = "../LICENSE" 6 | license = "MIT" 7 | description = "The structrures to support graphviz-rust library" 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | -------------------------------------------------------------------------------- /dot-structures/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # The structures of dot language 2 | //! The set of components of the graphviz dot notation 3 | //! endeavouring to follow comparatively close to the language [`notation`] 4 | //! 5 | //! [`notation`]: https://graphviz.org/doc/info/lang.html 6 | //! 7 | //! # Description: 8 | //! ```txt 9 | //! strict digraph t { <= graph 10 | //! aa[color=green] <= node aa and attributes in [..] 11 | //! subgraph v { <= subgraph v 12 | //! aa[shape=square] 13 | //! subgraph vv{a2 -> b2} 14 | //! aaa[color=red] 15 | //! aaa -> subgraph { d -> aaa} <= subgraph id is anon 16 | //! } 17 | //! aa -> be -> d -> aaa <= type of the edge is chain 18 | //! } 19 | //! ``` 20 | use std::fmt::{Display, Formatter}; 21 | 22 | /// the component represents a port in the language. 23 | /// It contains from id and direction. All can be optional separately but not at the same time. 24 | #[derive(Debug, PartialEq, Clone, Eq, Hash)] 25 | pub struct Port(pub Option, pub Option); 26 | 27 | /// the component represents a id in the language. 28 | /// The Anonymous is a virtual component to keep the other components consistent in case 29 | /// when a node or subgraph is anonymous 30 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 31 | pub enum Id { 32 | Html(String), 33 | Escaped(String), 34 | Plain(String), 35 | Anonymous(String), 36 | } 37 | 38 | impl Display for Id { 39 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 40 | match self { 41 | Id::Html(v) => f.write_str(format!("html {}", v).as_str()), 42 | Id::Escaped(v) => f.write_str(format!("esc {}", v).as_str()), 43 | Id::Plain(v) => f.write_str(format!("{}", v).as_str()), 44 | Id::Anonymous(v) => f.write_str(format!("anon {}", v).as_str()), 45 | } 46 | } 47 | } 48 | 49 | /// the component represents a node_id in the language. 50 | /// The component turns up in the edges predominantly or as an id for a node. 51 | #[derive(Debug, PartialEq, Eq, Clone, Hash)] 52 | pub struct NodeId(pub Id, pub Option); 53 | 54 | /// the component represents a attribute in the language. 55 | #[derive(PartialEq, Debug, Clone)] 56 | pub struct Attribute(pub Id, pub Id); 57 | 58 | /// the component represents a set of attributes with prefix denoting a type in the language. 59 | #[derive(PartialEq, Debug, Clone)] 60 | pub enum GraphAttributes { 61 | Graph(Vec), 62 | Node(Vec), 63 | Edge(Vec), 64 | } 65 | 66 | impl GraphAttributes { 67 | pub fn new(ty: &str, attrs: Vec) -> Self { 68 | match ty.to_lowercase().as_str() { 69 | "graph" => GraphAttributes::Graph(attrs), 70 | "node" => GraphAttributes::Node(attrs), 71 | "edge" => GraphAttributes::Edge(attrs), 72 | _ => panic!("only graph, node, edge is applied here. "), 73 | } 74 | } 75 | } 76 | 77 | /// the component represents a edge in the language. 78 | #[derive(Debug, PartialEq, Clone)] 79 | pub struct Edge { 80 | pub ty: EdgeTy, 81 | pub attributes: Vec, 82 | } 83 | 84 | impl Edge { 85 | fn add_attr(&mut self, attr: Attribute) { 86 | self.attributes.push(attr) 87 | } 88 | } 89 | 90 | /// the component depicts a type of the edge, namely it is a pair of chain. 91 | /// From the graph point of view, it impacts a compact display only. 92 | #[derive(Debug, PartialEq, Clone)] 93 | pub enum EdgeTy { 94 | Pair(Vertex, Vertex), 95 | Chain(Vec), 96 | } 97 | 98 | /// the component represents the vital component, namely node in the lang. 99 | #[derive(Debug, PartialEq, Clone)] 100 | pub struct Node { 101 | pub id: NodeId, 102 | pub attributes: Vec, 103 | } 104 | 105 | impl Node { 106 | pub fn new(id: NodeId, attributes: Vec) -> Self { 107 | Node { id, attributes } 108 | } 109 | } 110 | 111 | /// the component represents a wrapper to keep sustainability in subgraph and graph bodies. 112 | #[derive(PartialEq, Debug, Clone)] 113 | pub enum Stmt { 114 | Node(Node), 115 | Subgraph(Subgraph), 116 | Attribute(Attribute), 117 | GAttribute(GraphAttributes), 118 | Edge(Edge), 119 | } 120 | 121 | impl From for Stmt { 122 | fn from(v: Node) -> Self { 123 | Stmt::Node(v) 124 | } 125 | } 126 | 127 | impl From for Stmt { 128 | fn from(v: Edge) -> Self { 129 | Stmt::Edge(v) 130 | } 131 | } 132 | 133 | impl From for Stmt { 134 | fn from(v: GraphAttributes) -> Self { 135 | Stmt::GAttribute(v) 136 | } 137 | } 138 | 139 | impl From for Stmt { 140 | fn from(v: Attribute) -> Self { 141 | Stmt::Attribute(v) 142 | } 143 | } 144 | 145 | impl From for Stmt { 146 | fn from(v: Subgraph) -> Self { 147 | Stmt::Subgraph(v) 148 | } 149 | } 150 | 151 | /// the component represents a subgraph in the lang. 152 | #[derive(PartialEq, Debug, Clone)] 153 | pub struct Subgraph { 154 | pub id: Id, 155 | pub stmts: Vec, 156 | } 157 | 158 | impl Subgraph { 159 | pub fn add_stmt(&mut self, stmt: Stmt) { 160 | self.stmts.push(stmt) 161 | } 162 | } 163 | 164 | /// the component represents an edge component. 165 | #[derive(PartialEq, Debug, Clone)] 166 | pub enum Vertex { 167 | N(NodeId), 168 | S(Subgraph), 169 | } 170 | 171 | impl From for Vertex { 172 | fn from(v: NodeId) -> Self { 173 | Vertex::N(v) 174 | } 175 | } 176 | 177 | impl From for Vertex { 178 | fn from(v: Subgraph) -> Self { 179 | Vertex::S(v) 180 | } 181 | } 182 | 183 | /// the component represents a graph in the lang. 184 | #[derive(Debug, PartialEq, Clone)] 185 | pub enum Graph { 186 | Graph { 187 | id: Id, 188 | strict: bool, 189 | stmts: Vec, 190 | }, 191 | DiGraph { 192 | id: Id, 193 | strict: bool, 194 | stmts: Vec, 195 | }, 196 | } 197 | 198 | impl Graph { 199 | pub fn add_stmt(&mut self, stmt: Stmt) { 200 | match self { 201 | Graph::Graph { stmts, .. } => stmts.push(stmt), 202 | Graph::DiGraph { stmts, .. } => stmts.push(stmt), 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "json": {}, 3 | "markdown": {}, 4 | "toml": {}, 5 | "rustfmt": { 6 | "tab_spaces": "4", 7 | "imports_granularity": "Crate", 8 | "reorder_imports": true, 9 | "group_imports": "StdExternalCrate", 10 | "use_field_init_shorthand": true, 11 | "normalize_doc_attributes": true, 12 | "format_code_in_doc_comments": true, 13 | "format_macro_matchers": true, 14 | "use_try_shorthand": true 15 | }, 16 | "includes": [ 17 | "**/*.{json,md,toml,rs}" 18 | ], 19 | "excludes": [ 20 | "*lock*", 21 | "**/target/" 22 | ], 23 | "plugins": [ 24 | "https://plugins.dprint.dev/json-0.16.0.wasm", 25 | "https://plugins.dprint.dev/markdown-0.14.3.wasm", 26 | "https://plugins.dprint.dev/toml-0.5.4.wasm", 27 | "https://plugins.dprint.dev/rustfmt-0.6.2.json@886c6f3161cf020c2d75160262b0f56d74a521e05cfb91ec4f956650c8ca76ca" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /into-attr-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "into-attr-derive" 3 | version = "0.2.1" 4 | edition = "2021" 5 | license-file = "../LICENSE" 6 | license = "MIT" 7 | description = "derive for attributes for graphviz-rust lib" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [dependencies] 15 | dot-generator = "0.2.0" 16 | dot-structures = "0.1.1" 17 | into-attr = "0.1.1" 18 | quote = "1.0" 19 | syn = "1.0" 20 | -------------------------------------------------------------------------------- /into-attr-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate dot_structures; 2 | extern crate proc_macro; 3 | 4 | use dot_generator::attr; 5 | use into_attr::IntoAttribute; 6 | use proc_macro::TokenStream; 7 | use quote::quote; 8 | use syn::{self, Data}; 9 | 10 | #[proc_macro_derive(IntoAttribute)] 11 | pub fn into_attr_derive(input: TokenStream) -> TokenStream { 12 | let ast = syn::parse(input).unwrap(); 13 | impl_into_attr_macro(&ast) 14 | } 15 | 16 | fn impl_into_attr_macro(ast: &syn::DeriveInput) -> TokenStream { 17 | let name = &ast.ident; 18 | let name_str = name.to_string(); 19 | let gen = match &ast.data { 20 | Data::Enum(de) => { 21 | quote! { 22 | impl IntoAttribute for #name { 23 | fn into_attr(self) -> Attribute { 24 | let v = format!("{:?}",self); 25 | let v = v.as_str().strip_suffix("_").unwrap_or(v.as_str()); 26 | attr!(#name_str,v) 27 | } 28 | } 29 | } 30 | } 31 | Data::Struct(ds) => { 32 | quote! { 33 | impl IntoAttribute for #name { 34 | fn into_attr(self) -> Attribute { 35 | attr!(#name_str,self.0) 36 | } 37 | } 38 | } 39 | } 40 | _ => panic!("the unions are unexpected"), 41 | }; 42 | 43 | gen.into() 44 | } 45 | -------------------------------------------------------------------------------- /into-attr/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "into-attr" 3 | version = "0.1.1" 4 | edition = "2021" 5 | license-file = "../LICENSE" 6 | license = "MIT" 7 | description = "the macros helping to transform attributes in graphviz-rust library" 8 | 9 | [dependencies] 10 | dot-structures = "0.1.1" 11 | -------------------------------------------------------------------------------- /into-attr/src/lib.rs: -------------------------------------------------------------------------------- 1 | use dot_structures::Attribute; 2 | pub trait IntoAttribute { 3 | fn into_attr(self) -> Attribute; 4 | } 5 | -------------------------------------------------------------------------------- /src/attributes/generate.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! as_item { 3 | ($i:item) => { 4 | $i 5 | }; 6 | } 7 | 8 | #[macro_export] 9 | macro_rules! generate_attr { 10 | (enum $name:tt ; $($values:tt),+) =>{ 11 | as_item! { 12 | #[derive(Debug,PartialEq, IntoAttribute)] 13 | pub enum $name { $($values),+ } 14 | } 15 | impl Display for $name { 16 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 17 | f.write_str(format!("{:?}",self).as_str()) 18 | } 19 | } 20 | }; 21 | (enum $name:tt for $($owners:tt),+; $($values:tt),+) =>{ 22 | as_item! { 23 | #[derive(Debug,PartialEq, IntoAttribute)] 24 | pub enum $name { $($values),+ } 25 | } 26 | 27 | $(impl $owners { 28 | pub fn $name(elem:$name) -> Attribute { 29 | elem.into_attr() 30 | } 31 | } 32 | )+ 33 | 34 | }; 35 | (enum $name:tt for $($owners:tt),+; $($values:tt),+;$default:tt ) =>{ 36 | as_item! { 37 | #[derive(Debug,PartialEq, IntoAttribute)] 38 | pub enum $name { $($values),+ } 39 | } 40 | 41 | impl Default for $name{ 42 | fn default() -> Self { $name::$default } 43 | } 44 | 45 | $(impl $owners{ 46 | pub fn $name(elem:$name) -> Attribute { 47 | elem.into_attr() 48 | } 49 | } 50 | )+ 51 | 52 | 53 | }; 54 | (struct $name:tt for $($owners:tt),+; $ty:tt) =>{ 55 | as_item! { 56 | #[derive(Debug,PartialEq, IntoAttribute)] 57 | pub struct $name ($ty); 58 | } 59 | $(impl $owners { 60 | pub fn $name(elem:$ty) -> Attribute { 61 | $name(elem).into_attr() 62 | } 63 | } 64 | )+ 65 | 66 | }; 67 | (struct $name:tt for $($owners:tt),+; $ty:tt; $default:expr) =>{ 68 | as_item! { 69 | #[derive(Debug,PartialEq, IntoAttribute)] 70 | pub struct $name ($ty); 71 | } 72 | impl Default for $name{ 73 | fn default() -> Self { $name($default) } 74 | } 75 | $( 76 | impl $owners{ 77 | pub fn $name(elem:$ty) -> Attribute { 78 | $name(elem).into_attr() 79 | } 80 | } 81 | )+ 82 | 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /src/attributes/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | //! graphviz [`attributes`] 3 | //! 4 | //! [`attributes`]: https://graphviz.org/doc/info/attrs.html 5 | //! # Examples: 6 | //! ```rust 7 | //! use dot_generator::*; 8 | //! use dot_structures::*; 9 | //! use graphviz_rust::attributes::{color, color_name, GraphAttributes, NodeAttributes}; 10 | //! use into_attr::IntoAttribute; 11 | //! fn test() { 12 | //! assert_eq!(GraphAttributes::center(true), attr!("center", true)); 13 | //! assert_eq!( 14 | //! NodeAttributes::color(color_name::antiquewhite1), 15 | //! attr!("color", "antiquewhite1") 16 | //! ); 17 | //! assert_eq!(color::default().into_attr(), attr!("color", "black")); 18 | //! } 19 | //! ``` 20 | mod generate; 21 | use std::fmt::{Display, Formatter}; 22 | 23 | use dot_generator::{attr, id}; 24 | use dot_structures::*; 25 | use into_attr::IntoAttribute; 26 | use into_attr_derive::IntoAttribute; 27 | 28 | use crate::{as_item, generate_attr}; 29 | 30 | /// The attributes appearing on the node 31 | pub enum NodeAttributes {} 32 | /// The attributes appearing on the edge 33 | pub enum EdgeAttributes {} 34 | /// The attributes appearing on the root graph 35 | pub enum GraphAttributes {} 36 | /// The attributes appearing on the subgraph 37 | pub enum SubgraphAttributes {} 38 | 39 | generate_attr!(struct _background for GraphAttributes; String; "".to_string() ); 40 | generate_attr!(struct area for NodeAttributes, SubgraphAttributes; f32; 1.0); 41 | generate_attr!(enum arrowhead for EdgeAttributes; 42 | //region values 43 | normal,dot,odot,none,empty,diamond,ediamond,odiamond,box_,open,vee,halfopen,obox, 44 | crow,invempty,tee,invodot,invdot,inv; 45 | normal 46 | //endregion 47 | ); 48 | generate_attr!(enum arrowtail for EdgeAttributes; 49 | //region values 50 | normal,dot,odot,none,empty,diamond,ediamond,odiamond,box_,open,vee,halfopen,obox, 51 | crow,invempty,tee,invodot,invdot,inv; 52 | normal 53 | //endregion 54 | ); 55 | generate_attr!(struct arrowsize for EdgeAttributes; f32; 0.0); 56 | generate_attr!(struct bb for GraphAttributes; String); 57 | generate_attr!(struct center for GraphAttributes; bool;false); 58 | generate_attr!(struct charset for GraphAttributes; String;"UTF-8".to_string()); 59 | generate_attr!(struct class for GraphAttributes,NodeAttributes,EdgeAttributes,SubgraphAttributes;String;"".to_string()); 60 | generate_attr!(struct colorscheme for GraphAttributes,NodeAttributes,EdgeAttributes,SubgraphAttributes;String;"".to_string()); 61 | generate_attr!(struct comment for GraphAttributes,NodeAttributes,EdgeAttributes;String;"".to_string()); 62 | generate_attr!(struct compound for GraphAttributes;bool;false); 63 | generate_attr!(struct concentrate for GraphAttributes;bool;false); 64 | generate_attr!(struct Damping for GraphAttributes;f32;0.99); 65 | generate_attr!(struct decorate for EdgeAttributes;bool;false); 66 | generate_attr!(struct defaultdist for GraphAttributes;f32); 67 | generate_attr!(struct constraint for EdgeAttributes;bool;true); 68 | generate_attr!(struct dim for GraphAttributes;i32;2); 69 | generate_attr!(struct dimen for GraphAttributes;i32;2); 70 | generate_attr!(enum clusterrank for GraphAttributes; local,global,none; local); 71 | generate_attr!(enum dir for EdgeAttributes; forward,back,both,none; forward); 72 | generate_attr!(struct diredgeconstraints for GraphAttributes; bool; false); 73 | generate_attr!(struct distortion for NodeAttributes; f32; 0.0); 74 | generate_attr!(struct dpi for GraphAttributes; f32; 96.0); 75 | generate_attr!(struct dedgehrefpi for EdgeAttributes; String); 76 | generate_attr!(struct edgetarget for EdgeAttributes; String ); 77 | generate_attr!(struct edgetooltip for EdgeAttributes; String ); 78 | generate_attr!(struct edgeURL for EdgeAttributes; String ); 79 | generate_attr!(struct epsilon for GraphAttributes; f32); 80 | generate_attr!(struct esep for GraphAttributes; f32;3.0); 81 | generate_attr!(struct fixedsize for NodeAttributes; bool;false); 82 | generate_attr!(struct fontname for NodeAttributes,GraphAttributes,EdgeAttributes,SubgraphAttributes; String;"Times-Roman".to_string()); 83 | generate_attr!(struct href for NodeAttributes,GraphAttributes,EdgeAttributes,SubgraphAttributes; String;"".to_string()); 84 | generate_attr!(struct id for NodeAttributes,GraphAttributes,EdgeAttributes,SubgraphAttributes; String;"".to_string()); 85 | generate_attr!(struct fontsize for NodeAttributes,GraphAttributes,EdgeAttributes,SubgraphAttributes; f32;14.0); 86 | generate_attr!(struct fontnames for GraphAttributes; String;"".to_string()); 87 | generate_attr!(struct fontpath for GraphAttributes; String;"".to_string()); 88 | generate_attr!(struct forcelabels for GraphAttributes; bool;true); 89 | generate_attr!(struct gradientangle for NodeAttributes,GraphAttributes,SubgraphAttributes; i32); 90 | generate_attr!(struct group for NodeAttributes; String); 91 | generate_attr!(struct head_lp for EdgeAttributes; String); 92 | generate_attr!(struct headclip for EdgeAttributes; bool;true); 93 | generate_attr!(struct headhref for EdgeAttributes; String;"".to_string()); 94 | generate_attr!(struct headlabel for EdgeAttributes; String;"".to_string()); 95 | generate_attr!(struct headtarget for EdgeAttributes; String;"".to_string()); 96 | generate_attr!(struct headtooltip for EdgeAttributes; String;"".to_string()); 97 | generate_attr!(struct headURL for EdgeAttributes; String;"".to_string()); 98 | generate_attr!(struct height for NodeAttributes; f32;0.5); 99 | generate_attr!(struct image for NodeAttributes; String;"".to_string()); 100 | generate_attr!(struct imagepos for NodeAttributes; String;"mc".to_string()); 101 | generate_attr!(struct imagescale for NodeAttributes; bool;false); 102 | generate_attr!(struct imagepath for GraphAttributes; String;"".to_string()); 103 | generate_attr!(struct inputscale for GraphAttributes; f32); 104 | generate_attr!(struct K for GraphAttributes,SubgraphAttributes; f32;0.3); 105 | generate_attr!(struct label for NodeAttributes,GraphAttributes,EdgeAttributes,SubgraphAttributes; String;"".to_string()); 106 | generate_attr!(struct label_scheme for GraphAttributes; i32;0); 107 | generate_attr!(struct labelangle for EdgeAttributes; f32;-25.0); 108 | generate_attr!(struct labeldistance for EdgeAttributes; f32;1.0); 109 | generate_attr!(struct labelfloat for EdgeAttributes; bool;false); 110 | generate_attr!(struct labelfontname for EdgeAttributes; String;"Times-Roman".to_string()); 111 | generate_attr!(struct labelfontsize for EdgeAttributes; f32;14.0); 112 | generate_attr!(struct labelhref for EdgeAttributes; String;"".to_string()); 113 | generate_attr!(struct labeljust for GraphAttributes,SubgraphAttributes; String;"c".to_string()); 114 | generate_attr!(struct labelloc for GraphAttributes,SubgraphAttributes,NodeAttributes; String); 115 | generate_attr!(struct labeltarget for EdgeAttributes; String); 116 | generate_attr!(struct labeltooltip for EdgeAttributes; String); 117 | generate_attr!(struct labelURL for EdgeAttributes; String); 118 | generate_attr!(struct landscape for GraphAttributes; bool;false); 119 | generate_attr!(struct layer for EdgeAttributes,NodeAttributes,SubgraphAttributes; String); 120 | generate_attr!(struct layerlistsep for GraphAttributes; String;",".to_string()); 121 | generate_attr!(struct layers for GraphAttributes; String); 122 | generate_attr!(struct layerselect for GraphAttributes; String); 123 | generate_attr!(struct layersep for GraphAttributes; String;":\t ".to_string()); 124 | generate_attr!(struct layout for GraphAttributes; String); 125 | generate_attr!(struct len for EdgeAttributes; f32); 126 | generate_attr!(struct levels for GraphAttributes; i32); 127 | generate_attr!(struct levelsgap for GraphAttributes; f32;0.0); 128 | generate_attr!(struct lhead for EdgeAttributes; String); 129 | generate_attr!(struct lheight for GraphAttributes,SubgraphAttributes; f32); 130 | generate_attr!(struct lwidth for GraphAttributes,SubgraphAttributes; f32); 131 | generate_attr!(struct lp for GraphAttributes,SubgraphAttributes,EdgeAttributes; String); 132 | generate_attr!(struct margin for GraphAttributes,SubgraphAttributes,NodeAttributes; f32); 133 | generate_attr!(struct ltail for EdgeAttributes; String); 134 | generate_attr!(struct maxiter for GraphAttributes; i32); 135 | generate_attr!(struct mclimit for GraphAttributes; f32;1.); 136 | generate_attr!(struct mindist for GraphAttributes; f32;1.); 137 | generate_attr!(struct minlen for EdgeAttributes; i32;1); 138 | generate_attr!(struct mode for GraphAttributes; String;"major".to_string()); 139 | generate_attr!(struct model for GraphAttributes; String;"shortpath".to_string()); 140 | generate_attr!(struct mosek for GraphAttributes; bool;false); 141 | generate_attr!(struct newrank for GraphAttributes; bool;false); 142 | generate_attr!(struct nodesep for GraphAttributes; f32;0.25); 143 | generate_attr!(struct normalize for GraphAttributes; bool;false); 144 | generate_attr!(struct notranslate for GraphAttributes; bool;false); 145 | generate_attr!(struct nslimit for GraphAttributes; f32); 146 | generate_attr!(struct nslimit1 for GraphAttributes; f32); 147 | generate_attr!(struct ordering for GraphAttributes,NodeAttributes; String); 148 | generate_attr!(struct orientation for GraphAttributes,NodeAttributes; f32;0.); 149 | generate_attr!(enum outputorder for GraphAttributes; breadthfirst,nodesfirst,edgesfirst;breadthfirst); 150 | generate_attr!(struct nojustify for GraphAttributes,NodeAttributes,SubgraphAttributes,EdgeAttributes; bool;false); 151 | generate_attr!(struct overlap for GraphAttributes; bool;true); 152 | generate_attr!(struct overlap_shrink for GraphAttributes; bool;true); 153 | generate_attr!(struct pack for GraphAttributes; bool;false); 154 | generate_attr!(struct overlap_scaling for GraphAttributes; f32;-4.); 155 | generate_attr!(struct pad for GraphAttributes; f32;0.0555); 156 | generate_attr!(struct page for GraphAttributes; f32); 157 | generate_attr!(enum packmode for GraphAttributes; node,clust,graph,array;node); 158 | generate_attr!(enum pagedir for GraphAttributes; BL,BR,TL,TR,RB,RT,LB,LT;BL); 159 | generate_attr!(struct penwidth for SubgraphAttributes,NodeAttributes,EdgeAttributes; f32;1.); 160 | generate_attr!(struct peripheries for SubgraphAttributes,NodeAttributes; i32); 161 | generate_attr!(struct pin for NodeAttributes; bool;false); 162 | generate_attr!(struct pos for NodeAttributes,EdgeAttributes; String); 163 | generate_attr!(enum quadtree for GraphAttributes; normal,fast,none;normal); 164 | generate_attr!(struct quantum for GraphAttributes; f32;0.); 165 | generate_attr!(enum rank for SubgraphAttributes; same,min,source,max,sink); 166 | generate_attr!(enum rankdir for GraphAttributes; TB,BT,LR,RL;TB); 167 | generate_attr!(struct ranksep for GraphAttributes; f32;0.5); 168 | generate_attr!(struct ratio for GraphAttributes; f32); 169 | generate_attr!(struct rects for NodeAttributes; String); 170 | generate_attr!(struct regular for NodeAttributes; bool;false); 171 | generate_attr!(struct remincross for GraphAttributes; bool;true); 172 | generate_attr!(struct repulsiveforce for GraphAttributes; f32;1.); 173 | generate_attr!(struct resolution for GraphAttributes; f32;96.); 174 | generate_attr!(struct root for GraphAttributes,NodeAttributes; String); 175 | generate_attr!(struct rotate for GraphAttributes; i32;0); 176 | generate_attr!(struct rotation for GraphAttributes; f32;0.); 177 | generate_attr!(struct samehead for EdgeAttributes; String); 178 | generate_attr!(struct sametail for EdgeAttributes; String); 179 | generate_attr!(struct samplepoints for NodeAttributes; i32;8); 180 | generate_attr!(struct searchsize for GraphAttributes; i32;30); 181 | generate_attr!(struct scale for GraphAttributes; f32); 182 | generate_attr!(struct sep for GraphAttributes; f32;4.); 183 | generate_attr!(struct shapefile for NodeAttributes; String); 184 | generate_attr!(enum shape for NodeAttributes; 185 | //region values 186 | box_,polygon,ellipse,oval,circle,point,egg,triangle,plaintext,plain,diamond,trapezium, 187 | parallelogram,house,pentagon,hexagon,septagon,octagon,doublecircle,doubleoctagon,tripleoctagon, 188 | invtriangle,invtrapezium,invhouse,Mdiamond,Msquare,Mcircle,rect,rectangle,square,star,none, 189 | underline,cylinder,note,tab,folder,box3d,component,promoter,cds,terminator,utr,primersite, 190 | restrictionsite,fivepoverhang,threepoverhang,noverhang,assembly,signature,insulator,ribosite, 191 | rnastab,proteasesite,proteinstab,rpromoter,rarrow,larrow,lpromoter; 192 | ellipse 193 | //endregion 194 | ); 195 | generate_attr!(struct showboxes for NodeAttributes,GraphAttributes,EdgeAttributes; i32;0); 196 | generate_attr!(struct sides for NodeAttributes; i32;4); 197 | generate_attr!(struct skew for NodeAttributes; f32;0.); 198 | generate_attr!(enum smoothing for GraphAttributes; none,avg_dist,graph_dist,power_dist,rng,spring,triangle;none); 199 | generate_attr!(struct size for GraphAttributes; f32); 200 | generate_attr!(struct sortv for GraphAttributes,SubgraphAttributes,NodeAttributes; i32;0); 201 | generate_attr!(struct splines for GraphAttributes; bool); 202 | generate_attr!(struct start for GraphAttributes; String); 203 | generate_attr!(struct style for GraphAttributes,EdgeAttributes,NodeAttributes,SubgraphAttributes; String); 204 | generate_attr!(struct stylesheet for GraphAttributes; String); 205 | generate_attr!(struct tail_lp for EdgeAttributes; String); 206 | generate_attr!(struct tailhref for EdgeAttributes; String); 207 | generate_attr!(struct taillabel for EdgeAttributes; String); 208 | generate_attr!(struct tailtarget for EdgeAttributes; String); 209 | generate_attr!(struct tailtooltip for EdgeAttributes; String); 210 | generate_attr!(struct tailURL for EdgeAttributes; String); 211 | generate_attr!(struct target for EdgeAttributes,GraphAttributes,NodeAttributes,SubgraphAttributes; String); 212 | generate_attr!(struct tooltip for EdgeAttributes,GraphAttributes,NodeAttributes,SubgraphAttributes; String); 213 | generate_attr!(struct URL for EdgeAttributes,GraphAttributes,NodeAttributes,SubgraphAttributes; String); 214 | generate_attr!(struct tailclip for EdgeAttributes; bool;true); 215 | generate_attr!(struct truecolor for GraphAttributes; bool); 216 | generate_attr!(struct vertices for NodeAttributes; String); 217 | generate_attr!(struct viewport for GraphAttributes; String); 218 | generate_attr!(struct voro_margin for GraphAttributes; f32;0.05); 219 | generate_attr!(struct weight for EdgeAttributes; i32;1); 220 | generate_attr!(struct width for NodeAttributes; f32;0.75); 221 | generate_attr!(struct xdotversion for GraphAttributes; String); 222 | generate_attr!(struct xlabel for EdgeAttributes,NodeAttributes; String); 223 | generate_attr!(struct xlp for EdgeAttributes,NodeAttributes; String); 224 | generate_attr!(struct z for NodeAttributes; f32;0.); 225 | generate_attr!(struct bgcolor for GraphAttributes,SubgraphAttributes; color_name); 226 | generate_attr!(struct color for EdgeAttributes,SubgraphAttributes,NodeAttributes; color_name;color_name::black); 227 | generate_attr!(struct fillcolor for EdgeAttributes,SubgraphAttributes,NodeAttributes; color_name); 228 | generate_attr!(struct fontcolor for GraphAttributes,EdgeAttributes,SubgraphAttributes,NodeAttributes; color_name); 229 | generate_attr!(struct labelfontcolor for EdgeAttributes; color_name;color_name::black); 230 | generate_attr!(struct pencolor for SubgraphAttributes; color_name;color_name::black); 231 | 232 | // support of the x11 color scheme 233 | generate_attr!(enum color_name; 234 | //region values 235 | aliceblue,antiquewhite,antiquewhite1,antiquewhite2,antiquewhite3,antiquewhite4,aqua,aquamarine, 236 | aquamarine1,aquamarine2,aquamarine3,aquamarine4,azure,azure1,azure2,azure3,azure4,beige,bisque, 237 | bisque1,bisque2,bisque3,bisque4,black,blanchedalmond,blue,blue1,blue2,blue3,blue4,blueviolet, 238 | brown,brown1,brown2,brown3,brown4,burlywood,burlywood1,burlywood2,burlywood3,burlywood4, 239 | cadetblue,cadetblue1,cadetblue2,cadetblue3,cadetblue4,chartreuse,chartreuse1,chartreuse2, 240 | chartreuse3,chartreuse4,chocolate,chocolate1,chocolate2,chocolate3,chocolate4,coral,coral1, 241 | coral2,coral3,coral4,cornflowerblue,cornsilk,cornsilk1,cornsilk2,cornsilk3,cornsilk4,crimson, 242 | cyan,cyan1,cyan2,cyan3,cyan4,darkblue,darkcyan,darkgoldenrod,darkgoldenrod1,darkgoldenrod2, 243 | darkgoldenrod3,darkgoldenrod4,darkgray,darkgreen,darkgrey,darkkhaki,darkmagenta,darkolivegreen, 244 | darkolivegreen1,darkolivegreen2,darkolivegreen3,darkolivegreen4,darkorange,darkorange1, 245 | darkorange2,darkorange3,darkorange4,darkorchid,darkorchid1,darkorchid2,darkorchid3,darkorchid4, 246 | darkred,darksalmon,darkseagreen,darkseagreen1,darkseagreen2,darkseagreen3,darkseagreen4, 247 | darkslateblue,darkslategray,darkslategray1,darkslategray2,darkslategray3,darkslategray4, 248 | darkslategrey,darkturquoise,darkviolet,deeppink,deeppink1,deeppink2,deeppink3,deeppink4, 249 | deepskyblue,deepskyblue1,deepskyblue2,deepskyblue3,deepskyblue4,dimgray,dimgrey,dodgerblue, 250 | dodgerblue1,dodgerblue2,dodgerblue3,dodgerblue4,firebrick,firebrick1,firebrick2,firebrick3, 251 | firebrick4,floralwhite,forestgreen,fuchsia,gainsboro,ghostwhite,gold,gold1,gold2,gold3, 252 | gold4,goldenrod,goldenrod1,goldenrod2,goldenrod3,goldenrod4,gray,gray0,gray1,gray10, 253 | gray100,gray11,gray12,gray13,gray14,gray15,gray16,gray17,gray18,gray19,gray2,gray20,gray21, 254 | gray22,gray23,gray24,gray25,gray26,gray27,gray28,gray29,gray3,gray30,gray31,gray32,gray33, 255 | gray34,gray35,gray36,gray37,gray38,gray39,gray4,gray40,gray41,gray42,gray43,gray44,gray45, 256 | gray46,gray47,gray48,gray49,gray5,gray50,gray51,gray52,gray53,gray54,gray55,gray56,gray57, 257 | gray58,gray59,gray6,gray60,gray61,gray62,gray63,gray64,gray65,gray66,gray67,gray68,gray69, 258 | gray7,gray70,gray71,gray72,gray73,gray74,gray75,gray76,gray77,gray78,gray79,gray8,gray80, 259 | gray81,gray82,gray83,gray84,gray85,gray86,gray87,gray88,gray89,gray9,gray90,gray91,gray92, 260 | gray93,gray94,gray95,gray96,gray97,gray98,gray99,green,green1,green2,green3,green4,greenyellow, 261 | grey,grey0,grey1,grey10,grey100,grey11,grey12,grey13,grey14,grey15,grey16,grey17,grey18, 262 | grey19,grey2,grey20,grey21,grey22,grey23,grey24,grey25,grey26,grey27,grey28,grey29,grey3, 263 | grey30,grey31,grey32,grey33,grey34,grey35,grey36,grey37,grey38,grey39,grey4,grey40,grey41, 264 | grey42,grey43,grey44,grey45,grey46,grey47,grey48,grey49,grey5,grey50,grey51,grey52,grey53, 265 | grey54,grey55,grey56,grey57,grey58,grey59,grey6,grey60,grey61,grey62,grey63,grey64,grey65, 266 | grey66,grey67,grey68,grey69,grey7,grey70,grey71,grey72,grey73,grey74,grey75,grey76,grey77, 267 | grey78,grey79,grey8,grey80,grey81,grey82,grey83,grey84,grey85,grey86,grey87,grey88,grey89, 268 | grey9,grey90,grey91,grey92,grey93,grey94,grey95,grey96,grey97,grey98,grey99,honeydew,honeydew1, 269 | honeydew2,honeydew3,honeydew4,hotpink,hotpink1,hotpink2,hotpink3,hotpink4,indianred,indianred1, 270 | indianred2,indianred3,indianred4,indigo,invis,ivory,ivory1,ivory2,ivory3,ivory4,khaki,khaki1, 271 | khaki2,khaki3,khaki4,lavender,lavenderblush,lavenderblush1,lavenderblush2,lavenderblush3, 272 | lavenderblush4,lawngreen,lemonchiffon,lemonchiffon1,lemonchiffon2,lemonchiffon3,lemonchiffon4, 273 | lightblue,lightblue1,lightblue2,lightblue3,lightblue4,lightcoral,lightcyan,lightcyan1, 274 | lightcyan2,lightcyan3,lightcyan4,lightgoldenrod,lightgoldenrod1,lightgoldenrod2,lightgoldenrod3, 275 | lightgoldenrod4,lightgoldenrodyellow,lightgray,lightgreen,lightgrey,lightpink,lightpink1, 276 | lightpink2,lightpink3,lightpink4,lightsalmon,lightsalmon1,lightsalmon2,lightsalmon3, 277 | lightsalmon4,lightseagreen,lightskyblue,lightskyblue1,lightskyblue2,lightskyblue3, 278 | lightskyblue4,lightslateblue,lightslategray,lightslategrey,lightsteelblue,lightsteelblue1, 279 | lightsteelblue2,lightsteelblue3,lightsteelblue4,lightyellow,lightyellow1,lightyellow2, 280 | lightyellow3,lightyellow4,lime,limegreen,linen,magenta,magenta1,magenta2,magenta3, 281 | magenta4,maroon,maroon1,maroon2,maroon3,maroon4,mediumaquamarine,mediumblue,mediumorchid, 282 | mediumorchid1,mediumorchid2,mediumorchid3,mediumorchid4,mediumpurple,mediumpurple1, 283 | mediumpurple2,mediumpurple3,mediumpurple4,mediumseagreen,mediumslateblue,mediumspringgreen, 284 | mediumturquoise,mediumvioletred,midnightblue,mintcream,mistyrose,mistyrose1,mistyrose2, 285 | mistyrose3,mistyrose4,moccasin,navajowhite,navajowhite1,navajowhite2,navajowhite3,navajowhite4, 286 | navy,navyblue,none,oldlace,olive,olivedrab,olivedrab1,olivedrab2,olivedrab3,olivedrab4, 287 | orange,orange1,orange2,orange3,orange4,orangered,orangered1,orangered2,orangered3,orangered4, 288 | orchid,orchid1,orchid2,orchid3,orchid4,palegoldenrod,palegreen,palegreen1,palegreen2, 289 | palegreen3,palegreen4,paleturquoise,paleturquoise1,paleturquoise2,paleturquoise3,paleturquoise4, 290 | palevioletred,palevioletred1,palevioletred2,palevioletred3,palevioletred4,papayawhip, 291 | peachpuff,peachpuff1,peachpuff2,peachpuff3,peachpuff4,peru,pink,pink1,pink2,pink3,pink4, 292 | plum,plum1,plum2,plum3,plum4,powderblue,purple,purple1,purple2,purple3,purple4,rebeccapurple, 293 | red,red1,red2,red3,red4,rosybrown,rosybrown1,rosybrown2,rosybrown3,rosybrown4,royalblue, 294 | royalblue1,royalblue2,royalblue3,royalblue4,saddlebrown,salmon,salmon1,salmon2,salmon3,salmon4, 295 | sandybrown,seagreen,seagreen1,seagreen2,seagreen3,seagreen4,seashell,seashell1,seashell2, 296 | seashell3,seashell4,sienna,sienna1,sienna2,sienna3,sienna4,silver,skyblue,skyblue1,skyblue2, 297 | skyblue3,skyblue4,slateblue,slateblue1,slateblue2,slateblue3,slateblue4,slategray,slategray1, 298 | slategray2,slategray3,slategray4,slategrey,snow,snow1,snow2,snow3,snow4,springgreen, 299 | springgreen1,springgreen2,springgreen3,springgreen4,steelblue,steelblue1,steelblue2,steelblue3, 300 | steelblue4,tan,tan1,tan2,tan3,tan4,teal,thistle,thistle1,thistle2,thistle3,thistle4,tomato, 301 | tomato1,tomato2,tomato3,tomato4,transparent,turquoise,turquoise1,turquoise2,turquoise3, 302 | turquoise4,violet,violetred,violetred1,violetred2,violetred3,violetred4,webgray,webgreen, 303 | webgrey,webmaroon,webpurple,wheat,wheat1,wheat2,wheat3,wheat4,white,whitesmoke,x11gray, 304 | x11green,x11grey,x11maroon,x11purple,yellow,yellow1,yellow2,yellow3,yellow4,yellowgreen 305 | //endregion 306 | ); 307 | 308 | impl NodeAttributes { 309 | pub fn margin_separate(horizontal: f32, vertical: f32) -> Attribute { 310 | Attribute( 311 | id!("margin"), 312 | id!(format!("{},{}", horizontal, vertical)), 313 | ) 314 | } 315 | } 316 | 317 | #[cfg(test)] 318 | pub mod tests { 319 | use dot_generator::attr; 320 | use into_attr::IntoAttribute; 321 | 322 | use crate::attributes::*; 323 | 324 | #[test] 325 | fn test() { 326 | assert_eq!(GraphAttributes::center(true), attr!("center", true)); 327 | assert_eq!( 328 | GraphAttributes::bgcolor(color_name::antiquewhite1), 329 | attr!("bgcolor", "antiquewhite1") 330 | ); 331 | assert_eq!(color::default().into_attr(), attr!("color", "black")); 332 | 333 | assert_eq!(NodeAttributes::margin(1.0), attr!("margin", "1")); 334 | assert_eq!(NodeAttributes::margin_separate(1.0, 2.0), attr!("margin", "1,2")); 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /src/cmd.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for executing the [`dot` command line executable]. 2 | //! 3 | //! *Important*: users should have the `dot` command line executable installed. 4 | //! A download can be found here: . 5 | //! 6 | //! Additional information on controlling the output can be found in the `graphviz` 7 | //! docs on [layouts] and [output formats]. 8 | //! 9 | //! [layouts]: https://graphviz.org/docs/layouts/ 10 | //! [output formats]:https://graphviz.org/docs/outputs/ 11 | //! # Example: 12 | //! ```no_run 13 | //! use dot_structures::*; 14 | //! use dot_generator::*; 15 | //! use graphviz_rust::attributes::*; 16 | //! use graphviz_rust::cmd::{CommandArg, Format}; 17 | //! use graphviz_rust::exec; 18 | //! use graphviz_rust::printer::{PrinterContext,DotPrinter}; 19 | //! 20 | //! fn graph_to_output(){ 21 | //! let mut g = graph!(id!("id"); 22 | //! node!("nod"), 23 | //! subgraph!("sb"; 24 | //! edge!(node_id!("a") => subgraph!(; 25 | //! node!("n"; 26 | //! NodeAttributes::color(color_name::black), NodeAttributes::shape(shape::egg)) 27 | //! )) 28 | //! ), 29 | //! edge!(node_id!("a1") => node_id!(esc "a2")) 30 | //! ); 31 | //! let graph_svg = exec(g, &mut PrinterContext::default(), vec![ 32 | //! CommandArg::Format(Format::Svg), 33 | //! ]).unwrap(); 34 | //! } 35 | //! 36 | //! fn graph_to_file(){ 37 | //! let mut g = graph!(id!("id")); 38 | //! let mut ctx = PrinterContext::default(); 39 | //! ctx.always_inline(); 40 | //! let empty = exec(g, &mut ctx, vec![ 41 | //! CommandArg::Format(Format::Svg), 42 | //! CommandArg::Output("1.svg".to_string()) 43 | //! ]); 44 | //! } 45 | //! ``` 46 | //! 47 | //! [`dot` command line executable]: https://graphviz.org/doc/info/command.html 48 | use std::{ 49 | io::{self, ErrorKind, Write}, 50 | process::{Command, Output}, 51 | }; 52 | 53 | use tempfile::NamedTempFile; 54 | 55 | pub(crate) fn exec(graph: String, args: Vec) -> io::Result> { 56 | let args = args.into_iter().map(|a| a.prepare()).collect(); 57 | temp_file(graph).and_then(|f| { 58 | let path = f.into_temp_path(); 59 | do_exec(path.to_string_lossy().to_string(), args).and_then(|o| { 60 | if o.status.code().map(|c| c != 0).unwrap_or(true) { 61 | let mes = String::from_utf8_lossy(&o.stderr).to_string(); 62 | path.close()?; 63 | Err(std::io::Error::new(ErrorKind::Other, mes)) 64 | } else { 65 | path.close()?; 66 | Ok(o.stdout) 67 | } 68 | }) 69 | }) 70 | } 71 | 72 | fn do_exec(input: String, args: Vec) -> io::Result { 73 | let mut command = Command::new("dot"); 74 | 75 | for arg in args { 76 | command.arg(arg); 77 | } 78 | command.arg(input).output() 79 | } 80 | 81 | fn temp_file(ctx: String) -> io::Result { 82 | let mut file = NamedTempFile::new()?; 83 | file.write_all(ctx.as_bytes()).map(|_x| file) 84 | } 85 | 86 | /// Commandline arguments that can be passed to executable. 87 | /// 88 | /// The list of possible commands can be found here: 89 | /// . 90 | pub enum CommandArg { 91 | /// any custom argument. 92 | /// 93 | /// _Note_: it does not manage any prefixes and thus '-' or the prefix must 94 | /// be passed as well. 95 | Custom(String), 96 | /// Regulates the output file with -o prefix 97 | Output(String), 98 | /// [`Layouts`] in cmd 99 | /// 100 | /// [`Layouts`]: https://graphviz.org/docs/layouts/ 101 | Layout(Layout), 102 | /// [`Output`] formats in cmd 103 | /// 104 | /// [`Output`]:https://graphviz.org/docs/outputs/ 105 | Format(Format), 106 | } 107 | 108 | impl From for CommandArg { 109 | fn from(value: Layout) -> Self { 110 | CommandArg::Layout(value) 111 | } 112 | } 113 | 114 | impl From for CommandArg { 115 | fn from(value: Format) -> Self { 116 | CommandArg::Format(value) 117 | } 118 | } 119 | 120 | impl CommandArg { 121 | fn prepare(&self) -> String { 122 | match self { 123 | CommandArg::Custom(s) => s.clone(), 124 | CommandArg::Output(p) => format!("-o{}", p), 125 | CommandArg::Layout(l) => format!("-K{}", format!("{:?}", l).to_lowercase()), 126 | CommandArg::Format(f) => { 127 | let str = match f { 128 | Format::Xdot12 => "xdot1.2".to_string(), 129 | Format::Xdot14 => "xdot1.4".to_string(), 130 | Format::ImapNp => "imap_np".to_string(), 131 | Format::CmapxNp => "cmapx_np".to_string(), 132 | Format::DotJson => "dot_json".to_string(), 133 | Format::XdotJson => "xdot_json".to_string(), 134 | Format::PlainExt => "plain-ext".to_string(), 135 | _ => format!("{:?}", f).to_lowercase(), 136 | }; 137 | format!("-T{}", str) 138 | } 139 | } 140 | } 141 | } 142 | 143 | /// Various algorithms for projecting abstract graphs into a space for 144 | /// visualization 145 | /// 146 | /// 147 | #[derive(Debug, Copy, Clone)] 148 | pub enum Layout { 149 | Dot, 150 | Neato, 151 | Twopi, 152 | Circo, 153 | Fdp, 154 | Asage, 155 | Patchwork, 156 | Sfdp, 157 | } 158 | 159 | /// Various graphic and data formats for end user, web, documents and other 160 | /// applications. 161 | /// 162 | /// 163 | #[derive(Debug, Copy, Clone)] 164 | pub enum Format { 165 | Bmp, 166 | Cgimage, 167 | Canon, 168 | Dot, 169 | Gv, 170 | Xdot, 171 | Xdot12, 172 | Xdot14, 173 | Eps, 174 | Exr, 175 | Fig, 176 | Gd, 177 | Gd2, 178 | Gif, 179 | Gtk, 180 | Ico, 181 | Cmap, 182 | Ismap, 183 | Imap, 184 | Cmapx, 185 | ImapNp, 186 | CmapxNp, 187 | Jpg, 188 | Jpeg, 189 | Jpe, 190 | Jp2, 191 | Json, 192 | Json0, 193 | DotJson, 194 | XdotJson, 195 | Pdf, 196 | Pic, 197 | Pct, 198 | Pict, 199 | Plain, 200 | PlainExt, 201 | Png, 202 | Pov, 203 | Ps, 204 | Ps2, 205 | Psd, 206 | Sgi, 207 | Svg, 208 | Svgz, 209 | Tga, 210 | Tif, 211 | Tiff, 212 | Tk, 213 | Vml, 214 | Vmlz, 215 | Vrml, 216 | Vbmp, 217 | Webp, 218 | Xlib, 219 | X11, 220 | } 221 | 222 | #[cfg(test)] 223 | mod tests { 224 | use dot_generator::*; 225 | use dot_structures::*; 226 | 227 | use crate::printer::{DotPrinter, PrinterContext}; 228 | 229 | use super::{exec, CommandArg, Format}; 230 | 231 | #[test] 232 | fn error_test() { 233 | let g = graph!(id!("id")); 234 | let mut ctx = PrinterContext::default(); 235 | ctx.always_inline(); 236 | let empty = exec( 237 | g.print(&mut ctx), 238 | vec![ 239 | Format::Svg.into(), 240 | CommandArg::Output("missing/1.svg".to_string()), 241 | ], 242 | ); 243 | assert!(empty.is_err()) 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/grammar/dot.pest: -------------------------------------------------------------------------------- 1 | WHITESPACE = _{ " " | "\t" | "\r\n" | "\n" } 2 | COMMENT = _{("/*" ~ (!"*/" ~ ANY)* ~ "*/") | (("#" | "//") ~ (!NEWLINE ~ ANY)* ~ NEWLINE?) } 3 | word = _{ ('a'..'z' | 'A'..'Z' | "_")+ } 4 | arr = _{"->" | "--"} 5 | char = { 6 | !("\"" | "\\") ~ ANY 7 | | "\\" ~ (ANY) 8 | } 9 | 10 | inner = ${ char* } 11 | number = ${"-"? ~ (("0" | ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) ~ ("." ~ ASCII_DIGIT+)? | ("." ~ ASCII_DIGIT+))} 12 | string_qt = ${ "\"" ~ inner ~ "\"" } 13 | html = ${"<" ~ (!(">" ~ WHITESPACE* ~">") ~ ANY)+ ~ ">" ~ WHITESPACE* ~ ">"} 14 | plain = ${(word ~ (word | ASCII_DIGIT)*) | number} 15 | 16 | 17 | compass = ${"n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw" | "c" | "_"} 18 | 19 | port = {":" ~ id ~ (":" ~ compass)? } 20 | id = {plain | html | string_qt} 21 | node_id = {id ~ port?} 22 | attr = {bare_attr ~ (";" |",")?} 23 | attr_list = {("[" ~ attr* ~ "]")+} 24 | node = {node_id ~ attr_list*} 25 | 26 | attr_mark = {"graph" | "node" | "edge"} 27 | attr_stmt = {attr_mark ~ attr_list} 28 | bare_attr = {id ~ "=" ~ id} 29 | edge_tail = {arr ~ vertex} 30 | edge = {vertex ~ edge_tail+} 31 | edge_stmt = {edge ~ attr_list?} 32 | vertex = { subgraph | node_id } 33 | subgraph = {"subgraph" ~ id? ~ body} 34 | stmt = {attr_stmt | subgraph | bare_attr | edge_stmt | node } 35 | body = {"{" ~ (stmt ~ ";"?)* ~"}"} 36 | strict = {"strict"} 37 | graph_ty = {"graph" | "digraph"} 38 | graph = {strict? ~ graph_ty ~ id? ~ body} 39 | file = {SOI ~ graph ~ EOI } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The library provides functionality for interacting with the [`graphviz` DOT language]. 2 | //! 3 | //! # Description: 4 | //! This library contains 4 primary functions: 5 | //! - [parse]: parses a string in the dot [`notation`] into a [Graph]. 6 | //! - [print](crate::print): serializes a [Graph] into a string given a [DotPrinter]. 7 | //! - [exec]: executes the [`dot` command line executable] given a [Graph]. 8 | //! - [exec_dot]: executes the [`dot` command line executable] given a string in 9 | //! the dot [`notation`]. 10 | //! 11 | //! # Examples: 12 | //! ```rust 13 | //! use dot_generator::*; 14 | //! use dot_structures::*; 15 | //! use graphviz_rust::{ 16 | //! attributes::*, 17 | //! cmd::{CommandArg, Format}, 18 | //! exec, exec_dot, parse, 19 | //! printer::{DotPrinter, PrinterContext}, 20 | //! }; 21 | //! 22 | //! let g: Graph = parse( 23 | //! r#" 24 | //! strict digraph t { 25 | //! aa[color=green] 26 | //! subgraph v { 27 | //! aa[shape=square] 28 | //! subgraph vv{a2 -> b2} 29 | //! aaa[color=red] 30 | //! aaa -> bbb 31 | //! } 32 | //! aa -> be -> subgraph v { d -> aaa} 33 | //! aa -> aaa -> v 34 | //! } 35 | //! "#, 36 | //! ) 37 | //! .unwrap(); 38 | //! 39 | //! assert_eq!( 40 | //! g, 41 | //! graph!(strict di id!("t"); 42 | //! node!("aa";attr!("color","green")), 43 | //! subgraph!("v"; 44 | //! node!("aa"; attr!("shape","square")), 45 | //! subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 46 | //! node!("aaa";attr!("color","red")), 47 | //! edge!(node_id!("aaa") => node_id!("bbb")) 48 | //! ), 49 | //! edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 50 | //! edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 51 | //! ) 52 | //! ); 53 | //! 54 | //! let mut g = graph!(strict di id!("id")); 55 | //! assert_eq!( 56 | //! "strict digraph id {\n\n}".to_string(), 57 | //! g.print(&mut PrinterContext::default()) 58 | //! ); 59 | //! 60 | //! fn output_test() { 61 | //! let mut g = graph!(id!("id"); 62 | //! node!("nod"), 63 | //! subgraph!("sb"; 64 | //! edge!(node_id!("a") => subgraph!(; 65 | //! node!("n"; 66 | //! NodeAttributes::color(color_name::black), NodeAttributes::shape(shape::egg)) 67 | //! )) 68 | //! ), 69 | //! edge!(node_id!("a1") => node_id!(esc "a2")) 70 | //! ); 71 | //! let graph_svg = exec( 72 | //! g, 73 | //! &mut PrinterContext::default(), 74 | //! vec![Format::Svg.into()], 75 | //! ) 76 | //! .unwrap(); 77 | //! } 78 | //! fn output_exec_from_test() { 79 | //! let mut g = graph!(id!("id"); 80 | //! node!("nod"), 81 | //! subgraph!("sb"; 82 | //! edge!(node_id!("a") => subgraph!(; 83 | //! node!("n"; 84 | //! NodeAttributes::color(color_name::black), NodeAttributes::shape(shape::egg)) 85 | //! )) 86 | //! ), 87 | //! edge!(node_id!("a1") => node_id!(esc "a2")) 88 | //! ); 89 | //! let dot = g.print(&mut PrinterContext::default()); 90 | //! println!("{}", dot); 91 | //! let format = Format::Svg; 92 | //! 93 | //! let graph_svg = exec_dot(dot.clone(), vec![format.into()]).unwrap(); 94 | //! 95 | //! let graph_svg = exec_dot(dot, vec![format.clone().into()]).unwrap(); 96 | //! } 97 | //! ``` 98 | //! 99 | //! [`graphviz`]: https://graphviz.org/ 100 | //! [`graphviz` DOT language]: https://graphviz.org/doc/info/lang.html 101 | //! [`notation`]: https://graphviz.org/doc/info/lang.html 102 | //! [`dot` command line executable]: https://graphviz.org/doc/info/command.html 103 | #![allow(non_camel_case_types)] 104 | #![allow(dead_code)] 105 | pub extern crate dot_generator; 106 | pub extern crate dot_structures; 107 | pub extern crate into_attr; 108 | pub extern crate into_attr_derive; 109 | 110 | use dot_structures::*; 111 | 112 | use crate::printer::{DotPrinter, PrinterContext}; 113 | 114 | pub mod attributes; 115 | #[cfg(feature = "graphviz-exec")] 116 | pub mod cmd; 117 | mod parser; 118 | pub mod printer; 119 | 120 | #[macro_use] 121 | extern crate pest_derive; 122 | extern crate pest; 123 | 124 | /// Parses a string into a [Graph]. 125 | pub fn parse(dot: &str) -> Result { 126 | parser::parse(dot) 127 | } 128 | 129 | /// Serializes a [Graph] into a string given a [DotPrinter]. 130 | pub fn print(graph: Graph, ctx: &mut PrinterContext) -> String { 131 | graph.print(ctx) 132 | } 133 | 134 | #[cfg(feature = "graphviz-exec")] 135 | use cmd::CommandArg; 136 | #[cfg(feature = "graphviz-exec")] 137 | use std::io; 138 | 139 | /// Executes the [`dot` command line executable](https://graphviz.org/doc/info/command.html) 140 | /// using the given [Graph], [PrinterContext] and command line arguments. 141 | #[cfg(feature = "graphviz-exec")] 142 | pub fn exec(graph: Graph, ctx: &mut PrinterContext, args: Vec) -> io::Result> { 143 | cmd::exec(print(graph, ctx), args) 144 | } 145 | 146 | /// Executes the [`dot` command line executable](https://graphviz.org/doc/info/command.html) 147 | /// using the given string dot notation, [PrinterContext] and command line arguments. 148 | #[cfg(feature = "graphviz-exec")] 149 | pub fn exec_dot(dot_graph: String, args: Vec) -> io::Result> { 150 | cmd::exec(dot_graph, args) 151 | } 152 | 153 | #[cfg(test)] 154 | mod tests { 155 | 156 | use dot_generator::*; 157 | use dot_structures::*; 158 | 159 | use crate::{ 160 | parse, 161 | printer::{DotPrinter, PrinterContext}, 162 | }; 163 | 164 | #[test] 165 | fn parse_test() { 166 | let g: Graph = parse( 167 | r#" 168 | strict digraph t { 169 | aa[color=green] 170 | subgraph v { 171 | aa[shape=square] 172 | subgraph vv{a2 -> b2} 173 | aaa[color=red] 174 | aaa -> bbb 175 | } 176 | aa -> be -> subgraph v { d -> aaa} 177 | aa -> aaa -> v 178 | } 179 | "#, 180 | ) 181 | .unwrap(); 182 | 183 | assert_eq!( 184 | g, 185 | graph!(strict di id!("t"); 186 | node!("aa";attr!("color","green")), 187 | subgraph!("v"; 188 | node!("aa"; attr!("shape","square")), 189 | subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 190 | node!("aaa";attr!("color","red")), 191 | edge!(node_id!("aaa") => node_id!("bbb")) 192 | ), 193 | edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 194 | edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 195 | ) 196 | ) 197 | } 198 | 199 | #[test] 200 | fn print_test() { 201 | let mut g = graph!(id!("id")); 202 | 203 | for el in (1..10000).into_iter() { 204 | if el % 2 == 0 { 205 | g.add_stmt(stmt!(node!(el))) 206 | } else { 207 | g.add_stmt(stmt!(subgraph!(el))) 208 | } 209 | } 210 | 211 | assert_eq!(148898, g.print(&mut PrinterContext::default()).len()) 212 | } 213 | 214 | #[cfg(windows)] 215 | const LS: &'static str = "\r\n"; 216 | #[cfg(not(windows))] 217 | const LS: &'static str = "\n"; 218 | 219 | #[test] 220 | #[cfg(feature = "graphviz-exec")] 221 | fn exec_test() { 222 | use std::{fs, process::Command}; 223 | 224 | use crate::{ 225 | attributes::{color_name, shape, NodeAttributes}, 226 | cmd::{CommandArg, Format}, 227 | exec, 228 | }; 229 | 230 | let g = graph!(id!("id"); 231 | node!("nod"), 232 | subgraph!("sb"; 233 | edge!(node_id!("a") => subgraph!(; 234 | node!("n"; 235 | NodeAttributes::color(color_name::black), NodeAttributes::shape(shape::egg)) 236 | )) 237 | ), 238 | edge!(node_id!("a1") => node_id!(esc "a2")) 239 | ); 240 | let graph_str = "graph id {\n nod\n subgraph sb {\n a -- subgraph {n[color=black,shape=egg]}\n }\n a1 -- \"a2\"\n}"; 241 | 242 | let mut ctx = PrinterContext::default(); 243 | assert_eq!(graph_str, g.print(&mut ctx)); 244 | 245 | let child = Command::new("dot") 246 | .arg("-V") 247 | .output() 248 | .expect("dot command failed to start"); 249 | 250 | let output = String::from_utf8_lossy(&child.stderr); 251 | let version = output 252 | .strip_prefix("dot - ") 253 | .and_then(|v| v.strip_suffix(LS)) 254 | .expect("the version of client is unrecognizable "); 255 | println!("{}", version); 256 | 257 | let out_svg = exec(g.clone(), &mut ctx, vec![CommandArg::Format(Format::Svg)]).unwrap(); 258 | 259 | let p = "1.svg"; 260 | let out = exec( 261 | g.clone(), 262 | &mut PrinterContext::default(), 263 | vec![ 264 | CommandArg::Format(Format::Svg), 265 | CommandArg::Output(p.to_string()), 266 | ], 267 | ) 268 | .unwrap(); 269 | 270 | let file = fs::read_to_string(p).unwrap(); 271 | 272 | fs::remove_file(p).unwrap(); 273 | assert_eq!(Vec::::new(), out); 274 | assert_eq!(out_svg, file.as_bytes()); 275 | } 276 | 277 | #[test] 278 | #[cfg(feature = "graphviz-exec")] 279 | fn output_exec_from_test() { 280 | use crate::{ 281 | attributes::{color_name, shape, NodeAttributes}, 282 | cmd::Format, 283 | exec_dot, 284 | }; 285 | 286 | let g = graph!(id!("id"); 287 | node!("nod"), 288 | subgraph!("sb"; 289 | edge!(node_id!("a") => subgraph!(; 290 | node!("n"; 291 | NodeAttributes::color(color_name::black), NodeAttributes::shape(shape::egg)) 292 | )) 293 | ), 294 | edge!(node_id!("a1") => node_id!(esc "a2")) 295 | ); 296 | let dot = g.print(&mut PrinterContext::default()); 297 | let format = Format::Svg; 298 | 299 | let res1 = exec_dot(dot.clone(), vec![format.into()]).unwrap(); 300 | let res2 = exec_dot(dot.clone(), vec![format.clone().into()]).unwrap(); 301 | 302 | assert_eq!(res1, res2) 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | //! Parser for the ['notation']. 2 | //! 3 | //! The grammar can be viewed in `/grammar/dot.pest` 4 | //! 5 | //! ['notation']: https://graphviz.org/doc/info/lang.html 6 | use dot_structures::*; 7 | use pest::{ 8 | error::Error, 9 | iterators::{Pair, Pairs}, 10 | }; 11 | 12 | use crate::pest::Parser; 13 | 14 | #[derive(Parser)] 15 | #[grammar = "grammar/dot.pest"] 16 | struct DotParser; 17 | 18 | pub(crate) fn parse(dot: &str) -> Result { 19 | do_parse(dot, Rule::file) 20 | .map(|r| r.into_iter().next().unwrap()) 21 | .map(|r| process_graph(down(r))) 22 | .map_err(|v| v.to_string()) 23 | } 24 | 25 | fn down(rule: Pair) -> Pair { 26 | rule.into_inner().next().unwrap() 27 | } 28 | 29 | fn do_parse(input: &str, ty: Rule) -> Result, Error> { 30 | DotParser::parse(ty, input) 31 | } 32 | 33 | fn process_attr_list(rule: Pair) -> Vec { 34 | let mut attrs = vec![]; 35 | let mut attr_list = rule.into_inner(); 36 | while attr_list.peek().is_some() { 37 | attrs.push(process_attr(attr_list.next().unwrap())) 38 | } 39 | attrs 40 | } 41 | 42 | fn process_bare_attr(rule: Pair) -> Attribute { 43 | let mut attr = rule.into_inner(); 44 | let key = attr.next().map(process_id).unwrap(); 45 | let val = attr.next().map(process_id).unwrap(); 46 | Attribute(key, val) 47 | } 48 | 49 | fn process_attr(rule: Pair) -> Attribute { 50 | process_bare_attr(down(rule)) 51 | } 52 | 53 | fn process_id(rule: Pair) -> Id { 54 | let val = rule.as_str().to_string(); 55 | match down(rule).as_rule() { 56 | Rule::plain => Id::Plain(val), 57 | Rule::html => Id::Html(val), 58 | Rule::string_qt => Id::Escaped(val), 59 | p => panic!("unreachable, got {:?}", p), 60 | } 61 | } 62 | 63 | fn parse_compass_manually(id: Id) -> Option { 64 | match id { 65 | Id::Plain(ref s) => match s.as_str() { 66 | "n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw" | "c" | "_" => Some(id.to_string()), 67 | _ => None 68 | } 69 | _ => None 70 | } 71 | } 72 | 73 | fn process_port(port: Pair) -> Port { 74 | let mut port_r = port.into_inner(); 75 | if let Some(r) = port_r.next() { 76 | match r.as_rule() { 77 | Rule::compass => Port(None, Some(r.as_str().to_string())), 78 | Rule::id => { 79 | let mb_id_mb_compass = process_id(r); 80 | if let Some(r) = port_r.next() { 81 | Port(Some(mb_id_mb_compass), Some(r.as_str().to_string())) 82 | } else { 83 | parse_compass_manually(mb_id_mb_compass.clone()) 84 | .map(|s| Port(None, Some(s))) 85 | .unwrap_or_else(|| Port(Some(mb_id_mb_compass), None)) 86 | } 87 | } 88 | _ => panic!("unreachable!"), 89 | } 90 | } else { 91 | panic!("port can not be empty") 92 | } 93 | } 94 | 95 | fn process_node_id(rule: Pair) -> NodeId { 96 | let mut node_id = rule.into_inner(); 97 | let id = node_id.next().map(process_id).unwrap(); 98 | if let Some(r) = node_id.next() { 99 | NodeId(id, Some(process_port(r))) 100 | } else { 101 | NodeId(id, None) 102 | } 103 | } 104 | 105 | fn process_subgraph(rule: Pair) -> Subgraph { 106 | let mut sub_r = rule.into_inner(); 107 | let id = match sub_r.peek().map(|r| r.as_rule()) { 108 | Some(Rule::id) => process_id(sub_r.next().unwrap()), 109 | _ => Id::Anonymous(rand::random::().to_string()), 110 | }; 111 | let stmts = process_body(sub_r.next().unwrap()); 112 | Subgraph { id, stmts } 113 | } 114 | 115 | fn process_body(rule: Pair) -> Vec { 116 | let mut stmts = vec![]; 117 | let mut body_r = rule.into_inner(); 118 | while body_r.peek().is_some() { 119 | stmts.push(process_stmt(body_r.next().unwrap())); 120 | } 121 | stmts 122 | } 123 | 124 | fn process_node(rule: Pair) -> Node { 125 | let mut node_r = rule.into_inner(); 126 | let id = process_node_id(node_r.next().unwrap()); 127 | if let Some(r) = node_r.next() { 128 | Node { 129 | id, 130 | attributes: process_attr_list(r), 131 | } 132 | } else { 133 | Node { 134 | id, 135 | attributes: vec![], 136 | } 137 | } 138 | } 139 | 140 | fn process_vertex(rule: Pair) -> Vertex { 141 | let vertex_r = down(rule); 142 | match vertex_r.as_rule() { 143 | Rule::node_id => Vertex::N(process_node_id(vertex_r)), 144 | Rule::subgraph => Vertex::S(process_subgraph(vertex_r)), 145 | _ => unreachable!(""), 146 | } 147 | } 148 | 149 | fn process_edge(rule: Pair) -> Vec { 150 | let mut edge_r = rule.into_inner(); 151 | let h = process_vertex(edge_r.next().unwrap()); 152 | let mut chain = vec![h]; 153 | 154 | while edge_r.peek().is_some() { 155 | chain.push(process_vertex(down(edge_r.next().unwrap()))) 156 | } 157 | chain 158 | } 159 | 160 | fn process_edge_stmt(rule: Pair) -> Edge { 161 | let mut edge_r = rule.into_inner(); 162 | let edges = process_edge(edge_r.next().unwrap()); 163 | 164 | let ty = if edges.len() > 2 { 165 | EdgeTy::Chain(edges) 166 | } else { 167 | let mut edge_iter = edges.into_iter(); 168 | EdgeTy::Pair(edge_iter.next().unwrap(), edge_iter.next().unwrap()) 169 | }; 170 | 171 | if let Some(attr_r) = edge_r.next() { 172 | Edge { 173 | ty, 174 | attributes: process_attr_list(attr_r), 175 | } 176 | } else { 177 | Edge { 178 | ty, 179 | attributes: vec![], 180 | } 181 | } 182 | } 183 | 184 | fn process_attr_stmt(rule: Pair) -> GraphAttributes { 185 | let mut stmts_r = rule.into_inner(); 186 | let mark = stmts_r.next().unwrap().as_str(); 187 | let attrs = process_attr_list(stmts_r.next().unwrap()); 188 | GraphAttributes::new(mark, attrs) 189 | } 190 | 191 | fn process_stmt(rule: Pair) -> Stmt { 192 | let stmt_r = down(rule); 193 | match stmt_r.as_rule() { 194 | Rule::attr_stmt => Stmt::GAttribute(process_attr_stmt(stmt_r)), 195 | Rule::subgraph => Stmt::Subgraph(process_subgraph(stmt_r)), 196 | Rule::node => Stmt::Node(process_node(stmt_r)), 197 | Rule::bare_attr => Stmt::Attribute(process_bare_attr(stmt_r)), 198 | Rule::edge_stmt => Stmt::Edge(process_edge_stmt(stmt_r)), 199 | _ => unreachable!(), 200 | } 201 | } 202 | 203 | fn process_graph(rule: Pair) -> Graph { 204 | let mut graph_r = rule.into_inner(); 205 | let strict = match graph_r.peek().map(|r| r.as_rule()) { 206 | Some(Rule::strict) => { 207 | graph_r.next(); 208 | true 209 | } 210 | _ => false, 211 | }; 212 | 213 | let is_di = matches!(graph_r.next().map(|r| r.as_str()), Some("digraph")); 214 | 215 | let id = match graph_r.peek().map(|r| r.as_rule()) { 216 | Some(Rule::id) => process_id(graph_r.next().unwrap()), 217 | _ => Id::Anonymous(rand::random::().to_string()), 218 | }; 219 | 220 | let stmts = process_body(graph_r.next().unwrap()); 221 | if is_di { 222 | Graph::DiGraph { id, strict, stmts } 223 | } else { 224 | Graph::Graph { id, strict, stmts } 225 | } 226 | } 227 | 228 | #[cfg(test)] 229 | mod test { 230 | use dot_generator::{attr, edge, graph, id, node, node_id, port, stmt, subgraph}; 231 | use dot_structures::*; 232 | use pest::iterators::Pair; 233 | 234 | use crate::parser::{ 235 | do_parse, parse, process_attr, process_attr_list, process_attr_stmt, process_edge, 236 | process_edge_stmt, process_id, process_node, process_node_id, process_stmt, process_vertex, 237 | Rule, Stmt, Vertex, 238 | }; 239 | 240 | fn _parse(input: &str, ty: Rule) -> Pair { 241 | match do_parse(input, ty) { 242 | Ok(mut r) => r.next().unwrap(), 243 | Err(e) => panic!("parsing err: {}", e), 244 | } 245 | } 246 | 247 | #[test] 248 | fn id_test() { 249 | let result = process_id(_parse("abc_a", Rule::id)); 250 | assert_eq!(result, id!("abc_a")); 251 | 252 | // valid ID 253 | let result = process_id(_parse(r#""a\b\c.'\"""#, Rule::id)); 254 | assert_eq!(result, id!(esc r#"a\b\c.'\""#)); 255 | 256 | // invalid ID unescaped quote 257 | let result = process_id(_parse(r#""a\b"\c.'\"""#, Rule::id)); 258 | assert_eq!(result, id!(esc r#"a\b"#)); 259 | 260 | let result = process_id(_parse("\"ab\\\"c\"", Rule::id)); 261 | assert_eq!(result, id!(esc "ab\\\"c")); 262 | 263 | let result = process_id(_parse( 264 | r#"<abc >"#, 265 | Rule::id, 266 | )); 267 | assert_eq!( 268 | result, 269 | id!(html r#"<abc >"#) 270 | ); 271 | 272 | let result = process_id(_parse( 273 | r#"< 274 | 275 |
leftmid dleright
>"#, 276 | Rule::id, 277 | )); 278 | assert_eq!( 279 | result, 280 | id!(html r#"< 281 | 282 |
leftmid dleright
>"#) 283 | ); 284 | 285 | let result = process_id(_parse( 286 | r#"< 287 | 288 | 289 |
leftmid dleright
290 | >"#, 291 | Rule::id, 292 | )); 293 | assert_eq!( 294 | result, 295 | id!(html r#"< 296 | 297 | 298 |
leftmid dleright
299 | >"#) 300 | ); 301 | let result = process_id(_parse( 302 | r#"<address_id:!@#$%^&*()_+/.,"\| int>"#, 303 | Rule::id, 304 | )); 305 | assert_eq!( 306 | result, 307 | id!(html r#"<address_id:!@#$%^&*()_+/.,"\| int>"#) 308 | ); 309 | } 310 | 311 | #[test] 312 | fn attr_test() { 313 | let result = process_attr(_parse("a=1", Rule::attr)); 314 | assert_eq!(result, attr!("a", "1")); 315 | let result = process_attr(_parse("a = 1 , ;", Rule::attr)); 316 | assert_eq!(result, attr!("a", "1")); 317 | } 318 | 319 | #[test] 320 | fn attr_list_test() { 321 | let result = process_attr_list(_parse("[a=1 , b=c ; d=<> e=e]", Rule::attr_list)); 322 | let expect = vec![ 323 | attr!("a", "1"), 324 | attr!("b", "c"), 325 | attr!("d", html "<>"), 326 | attr!("e", "e"), 327 | ]; 328 | assert_eq!(result, expect); 329 | let result = process_attr_list(_parse("[a=1 , b=c] [ d=<> e=e]", Rule::attr_list)); 330 | assert_eq!(result, expect); 331 | } 332 | 333 | #[test] 334 | fn node_id_test() { 335 | let result = process_node_id(_parse("abc:n", Rule::node_id)); 336 | let expect = node_id!(id!("abc"), port!(, "n")); 337 | assert_eq!(result, expect); 338 | 339 | let result = process_node_id(_parse("abc:abc", Rule::node_id)); 340 | let expect = node_id!(id!("abc"), port!(id!("abc"))); 341 | assert_eq!(result, expect); 342 | 343 | let result = process_node_id(_parse("abc:abc:n", Rule::node_id)); 344 | let expect = node_id!(id!("abc"), port!(id!("abc"), "n")); 345 | assert_eq!(result, expect); 346 | } 347 | 348 | #[test] 349 | fn node_test() { 350 | let result = process_node(_parse("abc:n[a=1 , b=c ; d=<> e=e]", Rule::node)); 351 | let p = port!(, "n" ); 352 | let attributes = vec![ 353 | attr!("a", "1"), 354 | attr!("b", "c"), 355 | attr!("d", html "<>"), 356 | attr!("e", "e"), 357 | ]; 358 | assert_eq!(result, node!("abc" => p, attributes)); 359 | } 360 | 361 | #[test] 362 | fn attr_stmts_test() { 363 | let result = process_attr_stmt(_parse("node [a=1 , b=c ; d=<> e=e]", Rule::attr_stmt)); 364 | let attributes = vec![ 365 | attr!("a", "1"), 366 | attr!("b", "c"), 367 | attr!("d", html "<>"), 368 | attr!("e", "e"), 369 | ]; 370 | assert_eq!(result, GraphAttributes::Node(attributes)); 371 | 372 | let result = 373 | process_attr_stmt(_parse("graph [a=1 , b=c ; d=<> e=e]", Rule::attr_stmt)); 374 | let attributes = vec![ 375 | attr!("a", "1"), 376 | attr!("b", "c"), 377 | attr!("d", html "<>"), 378 | attr!("e", "e"), 379 | ]; 380 | assert_eq!(result, GraphAttributes::Graph(attributes)); 381 | } 382 | 383 | #[test] 384 | fn vertex_test() { 385 | let result = process_vertex(_parse("node", Rule::vertex)); 386 | assert_eq!(result, Vertex::N(node_id!("node"))); 387 | } 388 | 389 | #[test] 390 | fn edge_test() { 391 | let result = process_edge(_parse("node -> node1 -> node2", Rule::edge)); 392 | let expected = vec![ 393 | Vertex::N(node_id!("node")), 394 | Vertex::N(node_id!("node1")), 395 | Vertex::N(node_id!("node2")), 396 | ]; 397 | assert_eq!(result, expected); 398 | } 399 | 400 | #[test] 401 | fn edge_stmt_test() { 402 | let result = process_edge_stmt(_parse("node -> node1 -> node2[a=2]", Rule::edge_stmt)); 403 | assert_eq!( 404 | result, 405 | edge!(node_id!("node")=> node_id!("node1")=>node_id!("node2"); attr!("a","2")) 406 | ); 407 | 408 | let result = process_edge_stmt(_parse("node -> subgraph sg{a -> b}[a=2]", Rule::edge_stmt)); 409 | 410 | assert_eq!( 411 | result, 412 | edge!( 413 | node_id!("node") => subgraph!("sg";stmt!(edge!(node_id!("a") => node_id!("b")))); 414 | attr!("a","2") 415 | ) 416 | ); 417 | } 418 | 419 | #[test] 420 | fn stmt_test() { 421 | let result = process_stmt(_parse("a=b", Rule::stmt)); 422 | assert_eq!(result, stmt!(attr!("a", "b"))); 423 | 424 | let result = process_stmt(_parse("node [a=1 , b=c ; d=<> e=e]", Rule::stmt)); 425 | let attributes = vec![ 426 | attr!("a", "1"), 427 | attr!("b", "c"), 428 | attr!("d", html "<>"), 429 | attr!("e", "e"), 430 | ]; 431 | assert_eq!(result, stmt!(GraphAttributes::Node(attributes))); 432 | 433 | let result = process_stmt(_parse("node -> node1 -> node2[a=2]", Rule::stmt)); 434 | 435 | assert_eq!( 436 | result, 437 | stmt!(edge!(node_id!("node")=> node_id!("node1")=>node_id!("node2"); attr!("a","2"))) 438 | ); 439 | } 440 | 441 | #[test] 442 | fn graph_html_test() { 443 | let g: Graph = parse( 444 | r#" 445 | digraph G { 446 | a [ label=< 447 | 448 | 449 |
class
qualifier
> 450 | ] 451 | b [shape=ellipse style=filled 452 | label=< 453 | 454 | 455 | 456 | 458 | 459 | 460 | 467 | 468 | 469 | 470 | 471 | 472 |
elephanttwo
461 | 462 | 463 | 464 | 465 |
corn
c
f
466 |
penguin
4
> 473 | ] 474 | c [ label=line 2
line 3
> ] 475 | d [ label=<address_id: int> ] 476 | } 477 | "#, 478 | ) 479 | .unwrap(); 480 | 481 | assert_eq!( 482 | g, 483 | graph!(di id!("G"); 484 | node!("a"; attr!("label",html r#"< 485 | 486 | 487 |
class
qualifier
>"#)), 488 | node!("b"; 489 | attr!("shape","ellipse"), 490 | attr!("style","filled"), 491 | attr!("label",html r#"< 492 | 493 | 494 | 495 | 497 | 498 | 499 | 506 | 507 | 508 | 509 | 510 | 511 |
elephanttwo
500 | 501 | 502 | 503 | 504 |
corn
c
f
505 |
penguin
4
>"#) 512 | ), 513 | node!("c"; attr!("label", html r#"line 2
line 3
>"#)), 514 | node!("d"; attr!("label", html r#"<address_id: int>"#)) 515 | 516 | ) 517 | ) 518 | } 519 | 520 | #[test] 521 | fn graph_test() { 522 | let g: Graph = parse( 523 | r#" 524 | strict digraph t { 525 | aa[color=green,label="shouln't er\ror"] 526 | subgraph v { 527 | aa[shape=square] 528 | subgraph vv{a2 -> b2} 529 | aaa[color=red] 530 | aaa -> bbb 531 | } 532 | aa -> be -> subgraph v { d -> aaa} 533 | aa -> aaa -> v 534 | } 535 | "#, 536 | ) 537 | .unwrap(); 538 | 539 | assert_eq!( 540 | g, 541 | graph!(strict di id!("t"); 542 | node!("aa";attr!("color","green"),attr!("label", esc "shouln't er\\ror")), 543 | subgraph!("v"; 544 | node!("aa"; attr!("shape","square")), 545 | subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 546 | node!("aaa";attr!("color","red")), 547 | edge!(node_id!("aaa") => node_id!("bbb")) 548 | ), 549 | edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 550 | edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 551 | ) 552 | ) 553 | } 554 | 555 | #[test] 556 | fn global_attr_test() { 557 | let g: Graph = parse( 558 | r#" 559 | graph t { 560 | graph [_draw_="c 9 "]; 561 | node [label="\N"]; 562 | a -- b; 563 | } 564 | "#, 565 | ) 566 | .unwrap(); 567 | 568 | assert_eq!( 569 | g, 570 | graph!(id!("t"); 571 | stmt!(GraphAttributes::Graph(vec![attr!("_draw_", esc "c 9 ")])), 572 | stmt!(GraphAttributes::Node(vec![attr!("label", esc "\\N")])), 573 | edge!(node_id!("a") => node_id!("b")) 574 | ) 575 | ) 576 | } 577 | 578 | #[test] 579 | fn comments_test() { 580 | let g: Graph = parse("// abc \n # abc \n strict digraph t { \n /* \n abc */ \n}").unwrap(); 581 | 582 | assert_eq!(g, graph!(strict di id!("t"))) 583 | } 584 | 585 | #[test] 586 | fn comments_after_graph_test() { 587 | let g: Graph = parse("// b \n strict digraph t { \n /* \n abc */ \n} \n // a ").unwrap(); 588 | 589 | assert_eq!(g, graph!(strict di id!("t"))) 590 | } 591 | 592 | #[test] 593 | fn port_test() { 594 | let g = parse(r#" 595 | digraph test { A:s0 -> B;}"#).unwrap(); 596 | assert_eq!(g, graph!(di id!("test"); edge!(node_id!("A", port!(id!("s0"))) => node_id!("B")))) 597 | } 598 | 599 | #[test] 600 | fn port_w_test() { 601 | let g = parse(r#" 602 | digraph test { A:s0:s -> B;}"#).unwrap(); 603 | assert_eq!(g, graph!(di id!("test"); edge!(node_id!("A", port!(id!("s0"),"s")) => node_id!("B")))) 604 | } 605 | 606 | #[test] 607 | fn port_compass_test() { 608 | let g = parse(r#" 609 | digraph test { A:s -> B;}"#).unwrap(); 610 | assert_eq!(g, graph!(di id!("test"); edge!(node_id!("A", port!(,"s")) => node_id!("B")))) 611 | } 612 | } 613 | -------------------------------------------------------------------------------- /src/printer.rs: -------------------------------------------------------------------------------- 1 | //! Serialize a [Graph] into a string according to the [`graphviz` DOT language]. 2 | //! 3 | //! # Example: 4 | //! ```rust 5 | //! use dot_generator::*; 6 | //! use dot_structures::*; 7 | //! use graphviz_rust::printer::{PrinterContext,DotPrinter}; 8 | //! let mut ctx = PrinterContext::default(); 9 | //! let s = subgraph!("id"; node!("abc"), edge!(node_id!("a") => node_id!("b"))); 10 | //! assert_eq!(s.print(&mut ctx), "subgraph id {\n abc\n a -- b\n}".to_string()); 11 | //! ``` 12 | //! 13 | //! [`graphviz` DOT language]: https://graphviz.org/doc/info/lang.html 14 | use std::collections::HashMap; 15 | 16 | use dot_structures::{ 17 | Attribute, Edge, EdgeTy, Graph, GraphAttributes, Id, Node, NodeId, Port, Stmt, Subgraph, Vertex, 18 | }; 19 | 20 | /// A function which can be passed to the [PrinterContext] to provide custom printing for attribute values. 21 | /// 22 | /// # Example: 23 | /// ```rust 24 | /// use dot_generator::*; 25 | /// use dot_structures::*; 26 | /// use graphviz_rust::printer::{AttributeValuePrinter, DotPrinter, PrinterContext}; 27 | /// fn attr_formatter_test() { 28 | /// let mut ctx = PrinterContext::default(); 29 | /// let formatter: Box = 30 | /// Box::new(|value, _line_sep, _indent, _indent_step| format!(r#""**{}**""#, value.trim_matches('"'))); 31 | /// let g = graph!(di id!("attr_formatting"); 32 | /// node!("abc";attr!("custom",esc "Custom Text")), 33 | /// edge!(node_id!("a") => node_id!("b")) 34 | /// ); 35 | /// assert_eq!( 36 | /// "digraph attr_formatting {\n abc[custom=\"**Custom Text**\"]\n a -> b\n}", 37 | /// g.print( 38 | /// ctx.with_node_mult_attr_s_l() 39 | /// .with_attr_value_printer(id!("custom"), formatter) 40 | /// ) 41 | /// ); 42 | /// } 43 | /// ``` 44 | pub type AttributeValuePrinter = dyn Fn(&str, &str, &str, usize) -> String; 45 | 46 | /// Context allows to customize the output of the file. 47 | /// 48 | /// # Example: 49 | /// ```rust 50 | /// use self::graphviz_rust::printer::PrinterContext; 51 | /// 52 | /// let mut ctx = PrinterContext::default(); 53 | /// ctx.always_inline(); 54 | /// ctx.with_indent_step(4); 55 | /// ``` 56 | pub struct PrinterContext { 57 | /// internal flag which is decoupled from the graph 58 | is_digraph: bool, 59 | /// a flag adds a semicolon at the end of the line 60 | semi: bool, 61 | /// a flag, print multiple node attributes on seperate lines 62 | mult_node_attr_on_s_l: bool, 63 | /// include a comma after node attributes on seperate lines 64 | mult_node_attr_on_s_l_comma: bool, 65 | /// an initial indent. 0 by default 66 | indent: usize, 67 | /// a step of the indent. 2 by default 68 | indent_step: usize, 69 | /// a line separator. can be empty 70 | l_s: String, 71 | /// a len of the text to keep on one line 72 | inline_size: usize, 73 | l_s_i: String, 74 | l_s_m: String, 75 | /// a map of attribute id to AttributeValuePrinters 76 | attr_value_printers: HashMap>, 77 | } 78 | 79 | impl PrinterContext { 80 | /// Print everything on one line. 81 | pub fn always_inline(&mut self) -> &mut PrinterContext { 82 | self.l_s_m = self.l_s_i.clone(); 83 | self.l_s = self.l_s_i.clone(); 84 | self 85 | } 86 | /// Add a semicolon at the end of every line. 87 | pub fn with_semi(&mut self) -> &mut PrinterContext { 88 | self.semi = true; 89 | self 90 | } 91 | /// Print multiple attributes on seperate lines 92 | pub fn with_node_mult_attr_s_l(&mut self) -> &mut PrinterContext { 93 | self.mult_node_attr_on_s_l = true; 94 | self 95 | } 96 | /// Don't include a comma when printing attributes on seperate lines 97 | pub fn with_no_node_mult_attr_s_l_comma(&mut self) -> &mut PrinterContext { 98 | self.mult_node_attr_on_s_l_comma = false; 99 | self 100 | } 101 | /// Set a step of the indent. 102 | pub fn with_indent_step(&mut self, step: usize) -> &mut PrinterContext { 103 | self.indent_step = step; 104 | self 105 | } 106 | /// Set a specific line separator. 107 | pub fn with_line_sep(&mut self, sep: String) -> &mut PrinterContext { 108 | self.l_s = sep.clone(); 109 | self.l_s_m = sep; 110 | self 111 | } 112 | /// Set the max line length. 113 | /// 114 | /// The default value is 90. 115 | pub fn with_inline_size(&mut self, inline_s: usize) -> &mut PrinterContext { 116 | self.inline_size = inline_s; 117 | self 118 | } 119 | /// Add an attribute printer for a specific attribute id. 120 | pub fn with_attr_value_printer( 121 | &mut self, 122 | attr_id: Id, 123 | fmt: Box, 124 | ) -> &mut PrinterContext { 125 | self.attr_value_printers.insert(attr_id, fmt); 126 | self 127 | } 128 | 129 | pub fn new(semi: bool, indent_step: usize, line_s: String, inline_size: usize) -> Self { 130 | PrinterContext { 131 | is_digraph: false, 132 | semi, 133 | mult_node_attr_on_s_l: false, 134 | mult_node_attr_on_s_l_comma: true, 135 | indent: 0, 136 | indent_step, 137 | inline_size, 138 | l_s: line_s.clone(), 139 | l_s_i: line_s, 140 | l_s_m: "".to_string(), 141 | attr_value_printers: HashMap::new(), 142 | } 143 | } 144 | } 145 | 146 | impl PrinterContext { 147 | fn indent(&self) -> String { 148 | if self.is_inline_on() { 149 | "".to_string() 150 | } else { 151 | " ".repeat(self.indent) 152 | } 153 | } 154 | fn indent_grow(&mut self) { 155 | if !self.is_inline_on() { 156 | self.indent += self.indent_step 157 | } 158 | } 159 | fn indent_shrink(&mut self) { 160 | if !self.is_inline_on() { 161 | self.indent -= self.indent_step 162 | } 163 | } 164 | 165 | fn is_inline_on(&self) -> bool { 166 | self.l_s == self.l_s_i 167 | } 168 | fn inline_mode(&mut self) { 169 | self.l_s = self.l_s_i.clone() 170 | } 171 | fn multiline_mode(&mut self) { 172 | self.l_s = self.l_s_m.clone() 173 | } 174 | } 175 | 176 | impl Default for PrinterContext { 177 | fn default() -> Self { 178 | PrinterContext { 179 | is_digraph: false, 180 | mult_node_attr_on_s_l: false, 181 | mult_node_attr_on_s_l_comma: true, 182 | semi: false, 183 | indent: 0, 184 | indent_step: 2, 185 | l_s: "\n".to_string(), 186 | inline_size: 90, 187 | l_s_i: "".to_string(), 188 | l_s_m: "\n".to_string(), 189 | attr_value_printers: HashMap::new(), 190 | } 191 | } 192 | } 193 | 194 | /// The trait for serailizing a [Graph] into the `graphviz` DOT language: 195 | /// 196 | /// # Example: 197 | /// ```rust 198 | /// use dot_generator::*; 199 | /// use dot_structures::*; 200 | /// use self::graphviz_rust::printer::PrinterContext; 201 | /// use self::graphviz_rust::printer::DotPrinter; 202 | /// 203 | /// let mut ctx =PrinterContext::default(); 204 | /// ctx.always_inline(); 205 | /// ctx.with_indent_step(4); 206 | /// let graph = graph!(strict di id!("t")); 207 | /// 208 | /// let string = graph.print(&mut ctx); 209 | /// ``` 210 | pub trait DotPrinter { 211 | fn print(&self, ctx: &mut PrinterContext) -> String; 212 | } 213 | 214 | impl DotPrinter for Id { 215 | fn print(&self, _ctx: &mut PrinterContext) -> String { 216 | match self { 217 | Id::Html(v) | Id::Escaped(v) | Id::Plain(v) => v.clone(), 218 | Id::Anonymous(_) => "".to_string(), 219 | } 220 | } 221 | } 222 | 223 | impl DotPrinter for Port { 224 | fn print(&self, ctx: &mut PrinterContext) -> String { 225 | match self { 226 | Port(Some(id), Some(d)) => format!(":{}:{}", id.print(ctx), d), 227 | Port(None, Some(d)) => format!(":{}", d), 228 | Port(Some(id), None) => format!(":{}", id.print(ctx)), 229 | _ => unreachable!(""), 230 | } 231 | } 232 | } 233 | 234 | impl DotPrinter for NodeId { 235 | fn print(&self, ctx: &mut PrinterContext) -> String { 236 | match self { 237 | NodeId(id, None) => id.print(ctx), 238 | NodeId(id, Some(port)) => [id.print(ctx), port.print(ctx)].join(""), 239 | } 240 | } 241 | } 242 | 243 | impl DotPrinter for Attribute { 244 | fn print(&self, ctx: &mut PrinterContext) -> String { 245 | match self { 246 | Attribute(l, r) => { 247 | let l_val = l.print(ctx); 248 | let r_val = r.print(ctx); 249 | if let Some(formatter) = ctx.attr_value_printers.get(l) { 250 | format!( 251 | "{}={}", 252 | l_val, 253 | formatter(&r_val, &ctx.l_s, &ctx.indent(), ctx.indent_step) 254 | ) 255 | } else { 256 | format!("{}={}", l_val, r_val) 257 | } 258 | } 259 | } 260 | } 261 | } 262 | 263 | impl DotPrinter for Vec { 264 | fn print(&self, ctx: &mut PrinterContext) -> String { 265 | let attrs: Vec = self.iter().map(|e| e.print(ctx)).collect(); 266 | if attrs.is_empty() { 267 | "".to_string() 268 | } else if attrs.len() > 1 && ctx.mult_node_attr_on_s_l { 269 | let indent = ctx.indent(); 270 | ctx.indent_grow(); 271 | let r = format!( 272 | "[{}{}{}{}{}]", 273 | ctx.l_s, 274 | ctx.indent(), 275 | attrs.join(&format!( 276 | "{}{}{}", 277 | { 278 | if ctx.is_inline_on() || ctx.mult_node_attr_on_s_l_comma { 279 | "," 280 | } else { 281 | "" 282 | } 283 | }, 284 | ctx.l_s, 285 | ctx.indent() 286 | )), 287 | ctx.l_s, 288 | indent, 289 | ); 290 | ctx.indent_shrink(); 291 | r 292 | } else { 293 | format!("[{}]", attrs.join(",")) 294 | } 295 | } 296 | } 297 | 298 | impl DotPrinter for GraphAttributes { 299 | fn print(&self, ctx: &mut PrinterContext) -> String { 300 | match self { 301 | GraphAttributes::Graph(attrs) => format!("graph{}", attrs.print(ctx)), 302 | GraphAttributes::Node(attrs) => format!("node{}", attrs.print(ctx)), 303 | GraphAttributes::Edge(attrs) => format!("edge{}", attrs.print(ctx)), 304 | } 305 | } 306 | } 307 | 308 | impl DotPrinter for Node { 309 | fn print(&self, ctx: &mut PrinterContext) -> String { 310 | format!("{}{}", self.id.print(ctx), self.attributes.print(ctx)) 311 | } 312 | } 313 | 314 | impl DotPrinter for Vertex { 315 | fn print(&self, ctx: &mut PrinterContext) -> String { 316 | match self { 317 | Vertex::N(el) => el.print(ctx), 318 | Vertex::S(el) => el.print(ctx), 319 | } 320 | } 321 | } 322 | 323 | impl DotPrinter for Subgraph { 324 | fn print(&self, ctx: &mut PrinterContext) -> String { 325 | let indent = ctx.indent(); 326 | ctx.indent_grow(); 327 | let header = format!("subgraph {} {{{}", self.id.print(ctx), ctx.l_s); 328 | let r = format!("{}{}{}{}}}", header, self.stmts.print(ctx), ctx.l_s, indent); 329 | ctx.indent_shrink(); 330 | r 331 | } 332 | } 333 | 334 | impl DotPrinter for Graph { 335 | fn print(&self, ctx: &mut PrinterContext) -> String { 336 | ctx.indent_grow(); 337 | 338 | match self { 339 | Graph::Graph { id, strict, stmts } if *strict => { 340 | ctx.is_digraph = false; 341 | let body = stmts.print(ctx); 342 | format!( 343 | "strict graph {} {{{}{}{}}}", 344 | id.print(ctx), 345 | ctx.l_s, 346 | body, 347 | ctx.l_s 348 | ) 349 | } 350 | Graph::Graph { 351 | id, 352 | strict: _, 353 | stmts, 354 | } => { 355 | ctx.is_digraph = false; 356 | let body = stmts.print(ctx); 357 | format!("graph {} {{{}{}{}}}", id.print(ctx), ctx.l_s, body, ctx.l_s) 358 | } 359 | Graph::DiGraph { id, strict, stmts } if *strict => { 360 | ctx.is_digraph = true; 361 | let body = stmts.print(ctx); 362 | format!( 363 | "strict digraph {} {{{}{}{}}}", 364 | id.print(ctx), 365 | ctx.l_s, 366 | body, 367 | ctx.l_s 368 | ) 369 | } 370 | Graph::DiGraph { 371 | id, 372 | strict: _, 373 | stmts, 374 | } => { 375 | ctx.is_digraph = true; 376 | let body = stmts.print(ctx); 377 | format!( 378 | "digraph {} {{{}{}{}}}", 379 | id.print(ctx), 380 | ctx.l_s, 381 | body, 382 | ctx.l_s 383 | ) 384 | } 385 | } 386 | } 387 | } 388 | 389 | impl DotPrinter for Vec { 390 | fn print(&self, ctx: &mut PrinterContext) -> String { 391 | let attrs: Vec = self.iter().map(|e| e.print(ctx)).collect(); 392 | attrs.join(ctx.l_s.as_str()) 393 | } 394 | } 395 | 396 | impl DotPrinter for Stmt { 397 | fn print(&self, ctx: &mut PrinterContext) -> String { 398 | let end = if ctx.semi { ";" } else { "" }; 399 | let indent = ctx.indent(); 400 | match self { 401 | Stmt::Node(e) => format!("{}{}{}", indent, e.print(ctx), end), 402 | Stmt::Subgraph(e) => format!("{}{}{}", indent, e.print(ctx), end), 403 | Stmt::Attribute(e) => format!("{}{}{}", indent, e.print(ctx), end), 404 | Stmt::GAttribute(e) => format!("{}{}{}", indent, e.print(ctx), end), 405 | Stmt::Edge(e) => format!("{}{}{}", indent, e.print(ctx), end), 406 | } 407 | } 408 | } 409 | 410 | fn print_edge(edge: &Edge, ctx: &mut PrinterContext) -> String { 411 | let bond = if ctx.is_digraph { "->" } else { "--" }; 412 | match edge { 413 | Edge { 414 | ty: EdgeTy::Pair(l, r), 415 | attributes, 416 | } => { 417 | if attributes.is_empty() { 418 | format!("{} {} {}", l.print(ctx), bond, r.print(ctx)) 419 | } else { 420 | format!( 421 | "{} {} {} {}", 422 | l.print(ctx), 423 | bond, 424 | r.print(ctx), 425 | attributes.print(ctx) 426 | ) 427 | } 428 | } 429 | Edge { 430 | ty: EdgeTy::Chain(vs), 431 | attributes, 432 | } => { 433 | let mut iter = vs.iter(); 434 | let h = iter.next().unwrap().print(ctx); 435 | let mut chain = h; 436 | for el in iter { 437 | chain = format!("{} {} {}", chain, bond, el.print(ctx)) 438 | } 439 | format!("{}{}", chain, attributes.print(ctx)) 440 | } 441 | } 442 | } 443 | 444 | impl DotPrinter for Edge { 445 | fn print(&self, ctx: &mut PrinterContext) -> String { 446 | let mut edge_str = print_edge(self, ctx); 447 | if edge_str.len() <= ctx.inline_size && !ctx.is_inline_on() { 448 | ctx.inline_mode(); 449 | edge_str = print_edge(self, ctx); 450 | ctx.multiline_mode(); 451 | } 452 | 453 | edge_str 454 | } 455 | } 456 | 457 | #[cfg(test)] 458 | mod tests { 459 | use dot_generator::{attr, edge, graph, id, node, node_id, port, stmt, subgraph}; 460 | use dot_structures::*; 461 | 462 | use crate::printer::{DotPrinter, PrinterContext}; 463 | 464 | #[test] 465 | fn edge_test() { 466 | let mut ctx = PrinterContext::default(); 467 | let edge = edge!(node_id!("abc") => node_id!("bce") => node_id!("cde"); attr!("a",2)); 468 | assert_eq!(edge.print(&mut ctx), "abc -- bce -- cde[a=2]"); 469 | ctx.is_digraph = true; 470 | assert_eq!(edge.print(&mut ctx), "abc -> bce -> cde[a=2]"); 471 | } 472 | 473 | #[test] 474 | fn node_id_test() { 475 | let node_id = NodeId(id!("abc"), Some(port!(id!("abc"), "n"))); 476 | let mut ctx = PrinterContext::default(); 477 | assert_eq!(node_id.print(&mut ctx), "abc:abc:n".to_string()); 478 | } 479 | 480 | #[test] 481 | fn node_test() { 482 | let mut ctx = PrinterContext::default(); 483 | assert_eq!( 484 | node!("abc";attr!("a",2)).print(&mut ctx), 485 | "abc[a=2]".to_string() 486 | ); 487 | } 488 | 489 | #[test] 490 | fn attr_test() { 491 | let mut ctx = PrinterContext::default(); 492 | let attr = attr!("a", 2); 493 | assert_eq!(attr.print(&mut ctx), "a=2".to_string()); 494 | } 495 | 496 | #[test] 497 | fn graph_attr_test() { 498 | let mut ctx = PrinterContext::default(); 499 | let n_attr = GraphAttributes::Node(vec![attr!("a", 2), attr!("b", 3)]); 500 | assert_eq!(n_attr.print(&mut ctx), "node[a=2,b=3]".to_string()); 501 | } 502 | 503 | #[test] 504 | fn subgraph_test() { 505 | let mut ctx = PrinterContext::default(); 506 | let s = subgraph!("id"; node!("abc"), edge!(node_id!("a") => node_id!("b"))); 507 | println!("{}", s.print(&mut ctx)); 508 | assert_eq!( 509 | s.print(&mut ctx), 510 | "subgraph id {\n abc\n a -- b\n}".to_string() 511 | ); 512 | } 513 | 514 | #[test] 515 | fn graph_test() { 516 | let mut ctx = PrinterContext::default(); 517 | ctx.always_inline(); 518 | let g = graph!(strict di id!("t"); 519 | node!("aa";attr!("color","green")), 520 | subgraph!("v"; 521 | node!("aa"; attr!("shape","square")), 522 | subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 523 | node!("aaa";attr!("color","red")), 524 | edge!(node_id!("aaa") => node_id!("bbb")) 525 | ), 526 | edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 527 | edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 528 | ); 529 | assert_eq!( 530 | r#"strict digraph t {aa[color=green]subgraph v {aa[shape=square]subgraph vv {a2 -> b2}aaa[color=red]aaa -> bbb}aa -> be -> subgraph v {d -> aaa}aa -> aaa -> v}"#, 531 | g.print(&mut ctx) 532 | ); 533 | } 534 | 535 | #[test] 536 | fn semi_graph_test() { 537 | let mut ctx = PrinterContext::default(); 538 | let g = graph!(strict di id!("t"); 539 | node!("aa";attr!("color","green")), 540 | subgraph!("v"; 541 | node!("aa"; attr!("shape","square")), 542 | subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 543 | node!("aaa";attr!("color","red")), 544 | edge!(node_id!("aaa") => node_id!("bbb")) 545 | ), 546 | edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 547 | edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 548 | ); 549 | assert_eq!( 550 | "strict digraph t {\n aa[color=green];\n subgraph v {\n aa[shape=square];\n subgraph vv {\n a2 -> b2;\n };\n aaa[color=red];\n aaa -> bbb;\n };\n aa -> be -> subgraph v {d -> aaa;};\n aa -> aaa -> v;\n}", 551 | g.print(ctx.with_semi()) 552 | ); 553 | } 554 | 555 | #[test] 556 | fn indent_step_graph_test() { 557 | let mut ctx = PrinterContext::default(); 558 | let g = graph!(strict di id!("t"); 559 | node!("aa";attr!("color","green")), 560 | subgraph!("v"; 561 | node!("aa"; attr!("shape","square")), 562 | subgraph!("vv"; edge!(node_id!("a2") => node_id!("b2"))), 563 | node!("aaa";attr!("color","red")), 564 | edge!(node_id!("aaa") => node_id!("bbb")) 565 | ), 566 | edge!(node_id!("aa") => node_id!("be") => subgraph!("v"; edge!(node_id!("d") => node_id!("aaa")))), 567 | edge!(node_id!("aa") => node_id!("aaa") => node_id!("v")) 568 | ); 569 | assert_eq!( 570 | "strict digraph t {\n aa[color=green]\n subgraph v {\n aa[shape=square]\n subgraph vv {\n a2 -> b2\n }\n aaa[color=red]\n aaa -> bbb\n }\n aa -> be -> subgraph v {d -> aaa}\n aa -> aaa -> v\n}", 571 | g.print(ctx.with_indent_step(4)) 572 | ); 573 | } 574 | 575 | #[test] 576 | fn mult_attr_l_s_graph_test() { 577 | let mut ctx = PrinterContext::default(); 578 | let g = graph!(di id!("multi"); 579 | node!("a";attr!("shape","square")), 580 | node!("aa";attr!("color","blue"),attr!("shape","Mrecord")), 581 | subgraph!("v"; 582 | node!("aaa"; attr!("shape","square")), 583 | node!("aaaa";attr!("color","red"),attr!("shape","Mrecord")), 584 | edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE")) 585 | ), 586 | edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green")) 587 | ); 588 | assert_eq!( 589 | "digraph multi {\n a[shape=square]\n aa[\n color=blue,\n shape=Mrecord\n ]\n subgraph v {\n aaa[shape=square]\n aaaa[\n color=red,\n shape=Mrecord\n ]\n aaa -> aaaa [label=FALSE]\n }\n a -> aa [label=TRUE,color=green]\n}", 590 | g.print(ctx.with_node_mult_attr_s_l()) 591 | ); 592 | } 593 | 594 | #[test] 595 | fn mult_attr_l_s_graph_test_no_comma() { 596 | let mut ctx = PrinterContext::default(); 597 | let g = graph!(di id!("multi"); 598 | node!("a";attr!("shape","square")), 599 | node!("aa";attr!("color","blue"),attr!("shape","Mrecord")), 600 | subgraph!("v"; 601 | node!("aaa"; attr!("shape","square")), 602 | node!("aaaa";attr!("color","red"),attr!("shape","Mrecord")), 603 | edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE")) 604 | ), 605 | edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green")) 606 | ); 607 | assert_eq!( 608 | "digraph multi {\n a[shape=square]\n aa[\n color=blue\n shape=Mrecord\n ]\n subgraph v {\n aaa[shape=square]\n aaaa[\n color=red\n shape=Mrecord\n ]\n aaa -> aaaa [label=FALSE]\n }\n a -> aa [label=TRUE,color=green]\n}", 609 | g.print(ctx.with_node_mult_attr_s_l().with_no_node_mult_attr_s_l_comma()) 610 | ); 611 | } 612 | 613 | #[test] 614 | fn mult_attr_l_s_graph_test_no_comma_no_inline() { 615 | let mut ctx = PrinterContext::default(); 616 | let g = graph!(di id!("multi"); 617 | node!("a";attr!("shape","square")), 618 | node!("aa";attr!("color","blue"),attr!("shape","Mrecord")), 619 | subgraph!("v"; 620 | node!("aaa"; attr!("shape","square")), 621 | node!("aaaa";attr!("color","red"),attr!("shape","Mrecord")), 622 | edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE")) 623 | ), 624 | edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green")) 625 | ); 626 | assert_eq!( 627 | "digraph multi {\n a[shape=square]\n aa[\n color=blue\n shape=Mrecord\n ]\n subgraph v {\n aaa[shape=square]\n aaaa[\n color=red\n shape=Mrecord\n ]\n aaa -> aaaa [label=FALSE]\n }\n a -> aa [\n label=TRUE\n color=green\n ]\n}", 628 | g.print(ctx.with_node_mult_attr_s_l().with_no_node_mult_attr_s_l_comma().with_inline_size(0)) 629 | ); 630 | } 631 | 632 | #[test] 633 | fn attr_formatter_graph_test() { 634 | let mut ctx = PrinterContext::default(); 635 | let g = graph!(di id!("multi"); 636 | node!("a";attr!("shape","square")), 637 | node!("aa";attr!("color","blue"),attr!("custom", esc "Custom Text"),attr!("shape","Mrecord")), 638 | subgraph!("v"; 639 | node!("aaa"; attr!("shape","square")), 640 | node!("aaaa";attr!("color","red"),attr!("custom", esc "Custom Text2")), 641 | edge!(node_id!("aaa") => node_id!("aaaa");attr!("label","FALSE")) 642 | ), 643 | edge!(node_id!("a") => node_id!("aa");attr!("label","TRUE"), attr!("color","green")) 644 | ); 645 | assert_eq!( 646 | "digraph multi {\n a[shape=square]\n aa[\n color=blue,\n custom=\"**Custom Text**\",\n shape=Mrecord\n ]\n subgraph v {\n aaa[shape=square]\n aaaa[\n color=red,\n custom=\"**Custom Text2**\"\n ]\n aaa -> aaaa [label=FALSE]\n }\n a -> aa [label=TRUE,color=green]\n}", 647 | g.print(ctx.with_node_mult_attr_s_l().with_attr_value_printer(id!("custom"), Box::new(|value, _l_s, _indent, _i_s| { 648 | format!(r#""**{}**""#, value.trim_matches('"')) 649 | }))) 650 | ); 651 | } 652 | } 653 | --------------------------------------------------------------------------------