dot-writer-0.1.3/.cargo_vcs_info.json0000644000000001360000000000100131350ustar { "git": { "sha1": "5684dcc37caa73598b79b8a39f056f74e43c7959" }, "path_in_vcs": "" }dot-writer-0.1.3/.gitignore000064400000000000000000000000230072674642500137400ustar 00000000000000/target Cargo.lock dot-writer-0.1.3/Cargo.toml0000644000000014460000000000100111400ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "dot-writer" version = "0.1.3" authors = ["DrGabble"] description = "A library for writing the Graphviz DOT graph language" readme = "README.md" keywords = ["graphviz", "graph", "dot"] categories = ["visualization"] license = "MIT" repository = "https://bitbucket.org/DrGabble/dot-writer" [dependencies] dot-writer-0.1.3/Cargo.toml.orig000064400000000000000000000005450072674642500146500ustar 00000000000000[package] authors = ["DrGabble"] branch = "master" categories = ["visualization"] description = "A library for writing the Graphviz DOT graph language" edition = "2018" keywords = ["graphviz", "graph", "dot"] license = "MIT" name = "dot-writer" readme = "README.md" repository = "https://bitbucket.org/DrGabble/dot-writer" version = "0.1.3" [dependencies] dot-writer-0.1.3/README.md000064400000000000000000000024320072674642500132350ustar 00000000000000# Graphviz DOT Writer This library is a (hopefully) ergonomic library for plotting graphs. It outputs the [Graphviz](https://www.graphviz.org/) language [DOT](https://www.graphviz.org/doc/info/lang.html). Graphs written in DOT can then be easily converted to SVG or other image formats using the Graphviz [dot executable](https://graphviz.org/doc/info/command.html). The structs in this library leverage the Rust type system and lifetimes to ensure that it's harder to use them to construct an invalid DOT graph. It's important to note that this means you need to make sure that child structs go out of scope before using their parents again. This is to make sure that the [`Drop`] writes the closing brackets correctly. ## Getting started Have a look at the API on [docs.rs](https://docs.rs/dot-writer) for examples and how to get coding. ## Non Goals This library only writes DOT in a strongly typed way. It doesn't read DOT or render DOT into image files. ## Contributing Contributions welcome, please feel free to open issues and pull requests. I've only just realised (7/11/22) that I hadn't enabled issues on the github repository, so my apologies to anyone who tried to post issues in the past! Drop me (DrGabble) a message if you have any questions on anything at all, always happy to help.dot-writer-0.1.3/bitbucket-pipelines.yml000064400000000000000000000004640072674642500164460ustar 00000000000000image: cimg/rust:1.64 pipelines: default: - parallel: - step: name: 'Build and Test' script: - cargo build -v - cargo test -v - step: name: 'Check Formatting' script: - cargo fmt --check - cargo clippydot-writer-0.1.3/src/attribute.rs000064400000000000000000000240500072674642500151160ustar 00000000000000use super::writer::Statement; /// Structs that implement [`Attributes`] are used for writing the attributes /// of a diagraph, graph, subgraph, subgraph cluster, node or edge. /// /// The raw [`Attributes::set`] function is provided for setting arbitary atrributes, /// but it is recommended to use the more typesafe functions where available. /// If a typesafe function is missing and you have to use the `set` function, please do file an issue in the github and we can add it. pub trait Attributes: Sized { /// Set the name of the font family for label text fn set_font(&mut self, label: &str) -> &mut Self { self.set("fontname", label, true) } /// Set arbitary html, useful for constructing more complex nodes fn set_html(&mut self, label: &str) -> &mut Self { self.set("label", label, false) } /// Set the display label for a graph, node or edge fn set_label(&mut self, label: &str) -> &mut Self { self.set("label", label, true) } /// Set the label to appear at the head of an edge fn set_head_label(&mut self, label: &str) -> &mut Self { self.set("headlabel", label, true) } /// Set the label to appear at the tail of an edge fn set_tail_label(&mut self, label: &str) -> &mut Self { self.set("taillabel", label, true) } /// Set the edge or line color fn set_color(&mut self, color: Color) -> &mut Self { self.set("color", color.as_str(), false) } /// Set the color to fill the area with fn set_fill_color(&mut self, color: Color) -> &mut Self { self.set("fillcolor", color.as_str(), false) } /// Set the color of the font used for text fn set_font_color(&mut self, color: Color) -> &mut Self { self.set("fontcolor", color.as_str(), false) } /// Set the background color fn set_background_color(&mut self, color: Color) -> &mut Self { self.set("bgcolor", color.as_str(), false) } /// Set the shape of a graph, subgraph, cluster or node fn set_shape(&mut self, shape: Shape) -> &mut Self { self.set("shape", shape.as_str(), false) } /// Set the style fn set_style(&mut self, style: Style) -> &mut Self { self.set("style", style.as_str(), true) } /// Set type of arrow head for edge lines (the arrow at the destination) fn set_arrow_head(&mut self, arrow_type: ArrowType) -> &mut Self { self.set("arrowhead", arrow_type.as_str(), false) } /// Set type of arrow tail for edge lines (the arrow at the source) fn set_arrow_tail(&mut self, arrow_type: ArrowType) -> &mut Self { self.set("arrowtail", arrow_type.as_str(), false) } /// Set the relative rank, which affects layout fn set_rank(&mut self, rank: Rank) -> &mut Self { self.set("rank", rank.as_str(), false) } /// Set the pen width for drawing lines fn set_pen_width(&mut self, width: f32) -> &mut Self { self.set("penwidth", &width.to_string(), false) } /// Set the arrow size fn set_arrow_size(&mut self, size: f32) -> &mut Self { self.set("arrowsize", &size.to_string(), false) } /// Set the font size fn set_font_size(&mut self, size: f32) -> &mut Self { self.set("fontsize", &size.to_string(), false) } // Set direction of graph layout fn set_rank_direction(&mut self, rank_direction: RankDirection) -> &mut Self { self.set("rankdir", rank_direction.as_str(), false) } /// Sets an attribute. See the Graphviz [documentation](https://graphviz.org/doc/info/attrs.html) /// for a full list of available names and values. /// Set the arguement `quote` to true if the `value` should be written in quotes `"`, to escape /// any special characters. Note that any quote in the string need to be escaped before calling. /// This function does NOT check that `name` or `value` are valid DOT strings. fn set(&mut self, name: &str, value: &str, quote: bool) -> &mut Self; } /// An [`AttributesList`] sets the attributes of an edge or node. /// See the [`Attributes`] trait for more information on what fields can be set. pub struct AttributesList<'d, 'w> { statement: Statement<'d, 'w>, at_least_one_set: bool, } impl<'d, 'w> AttributesList<'d, 'w> { pub(crate) fn new(statement: Statement<'d, 'w>) -> Self { Self { statement, at_least_one_set: false, } } } impl<'d, 'w> Attributes for AttributesList<'d, 'w> { fn set(&mut self, name: &str, value: &str, quote: bool) -> &mut Self { if !self.at_least_one_set { self.at_least_one_set = true; self.statement.write(b" ["); } else { self.statement.write(b", "); } self.statement.write(name.as_bytes()); self.statement.write(b"="); if quote { self.statement.write_quoted(value.as_bytes()); } else { self.statement.write(value.as_bytes()); } self } } impl<'d, 'w> Drop for AttributesList<'d, 'w> { fn drop(&mut self) { if self.at_least_one_set { self.statement.write(b"]"); } } } /// Shape of a component. This list is not comprehensive, the full list /// is visible [here](https://graphviz.org/doc/info/shapes.html#polygon) /// and can instead be set using [`Attributes::set`]. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Shape { Circle, Record, Rectangle, None, Mdiamond, Mrecord, Msquare, } impl Shape { fn as_str(&self) -> &str { match self { Self::Circle => "circle", Self::Record => "record", Self::Mrecord => "Mrecord", Self::Mdiamond => "Mdiamond", Self::Rectangle => "rectangle", Self::Msquare => "Msquare", Self::None => "none", } } } /// Color of a line or fill. This list is far from comprehensive, the /// full list is visible [here](https://graphviz.org/doc/info/colors.html), /// and can instead be set using [`Attributes::set`] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Color { Black, Grey, LightGrey, PaleGreen, PaleTurquoise, Red, White, Blue, Gray20, } impl Color { pub fn as_str(&self) -> &str { match self { Self::Black => "black", Self::Grey => "gray", Self::LightGrey => "lightgray", Self::PaleGreen => "palegreen", Self::PaleTurquoise => "paleturquoise", Self::Red => "red", Self::White => "white", Self::Blue => "blue", Self::Gray20 => "gray20", } } } /// Style of design. Note that some of these or only valid for /// certain combinations of node, edge and cluster. See documentation /// [here](https://graphviz.org/docs/attr-types/style/) for more information. #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Style { /// Nodes and edges Dashed, /// Nodes and edges Dotted, /// Nodes and edges Solid, /// Nodes and edges Invisible, /// Nodes and edges Bold, /// Edges only Tapered, /// Nodes only Wedged, /// Only for elliptically-shaped nodes Diagonals, /// Node and clusters Filled, /// Node and clusters Striped, /// Node and clusters Rounded, /// Any Radial, /// Any Unfilled, } impl Style { fn as_str(&self) -> &str { match self { Self::Dashed => "dashed", Self::Dotted => "dotted", Self::Solid => "solid", Self::Invisible => "invis", Self::Bold => "bold", Self::Tapered => "tapered", Self::Wedged => "wedged", Self::Diagonals => "diagonals", Self::Filled => "filled", Self::Striped => "striped", Self::Rounded => "rounded", Self::Radial => "radial", Self::Unfilled => "", } } } /// Node rank type, for more information see /// [Graphviz documentation](https://graphviz.org/docs/attr-types/rankType/). #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Rank { Min, Same, Max, Source, Sink, } impl Rank { fn as_str(&self) -> &str { match self { Self::Source => "source", Self::Min => "min", Self::Same => "same", Self::Max => "max", Self::Sink => "sink", } } } /// Arrow types, used to set either head or tail, for more information see /// [Graphviz documentation](https://graphviz.org/docs/attr-types/arrowType/). #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum ArrowType { Normal, Dot, Inv, InvDot, ODit, InvODot, None, Tee, Empty, InvEmpty, Diamond, ODiamond, EDiamond, Crow, Box, OBox, Open, HalfOpen, Vee, } impl ArrowType { fn as_str(&self) -> &str { match self { Self::Normal => "normal", Self::Dot => "dot", Self::Inv => "inv", Self::InvDot => "invdot", Self::ODit => "odot", Self::InvODot => "invdot", Self::None => "none", Self::Tee => "tee", Self::Empty => "empty", Self::InvEmpty => "invempty", Self::Diamond => "diamond", Self::ODiamond => "odiamond", Self::EDiamond => "ediamond", Self::Crow => "crow", Self::Box => "box", Self::OBox => "obox", Self::Open => "open", Self::HalfOpen => "halfopen", Self::Vee => "vee", } } } /// Direction to layout graph, for more information see /// [Graphviz documentation](https://graphviz.org/docs/attr-types/rankdir/). #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum RankDirection { TopBottom, BottomTop, LeftRight, RightLeft, } impl RankDirection { fn as_str(&self) -> &str { match self { Self::TopBottom => "TB", Self::BottomTop => "BT", Self::LeftRight => "LR", Self::RightLeft => "RL", } } } dot-writer-0.1.3/src/lib.rs000064400000000000000000000116310072674642500136620ustar 00000000000000//! # Graphviz Writer //! //! This library is an ergonomic library for plotting graphs. It outputs the [Graphviz](https://www.graphviz.org/) //! language [DOT](https://www.graphviz.org/doc/info/lang.html). Graphs written in DOT can then be easily //! converted to SVG or other image formats using the Graphviz [dot executable](https://graphviz.org/doc/info/command.html). //! //! The structs in this library leverage the Rust type system and lifetimes to ensure that it's hard //! to use them to construct an invalid DOT graph. It's important to note that this means you need //! to make sure that child structs go out of scope before using their parents again. This is to make sure that //! the [`Drop`] writes the closing brackets correctly. The compiler will tell you if you forget to do this though. //! //! ## Non Goals //! //! This library only writes DOT in a strongly typed way. It doesn't read DOT or render DOT into image files. //! //! ## Usage //! //! The first part of the library is the [`DotWriter`] struct, which is constructed from a mutable reference to any struct //! that implements [`std::io::Write`] (for example [`std::io::stdout()`] or even just a [`Vec`]). //! This [`DotWriter`] can then be used to construct a graph: //! //! ``` //! use dot_writer::{Color, DotWriter}; //! //! let mut output_bytes = Vec::new(); //! { //! let mut writer = DotWriter::from(&mut output_bytes); //! writer.set_pretty_print(false); //! writer //! .digraph() //! .edge("Hello", "World"); // digraph goes out of scope here, writing closing bracket //! // writer goes out of scope here, freeing up output_bytes for reading //! } //! assert_eq!( //! String::from_utf8(output_bytes).unwrap(), //! "digraph{Hello->World;}" //! ); //! ``` //! //! If instead you used [`std::io::stdout()`] or wrote to a file using //! [File or BufReader](https://doc.rust-lang.org/std/fs/struct.File.html), //! you could then use the [dot executable](https://graphviz.org/doc/info/command.html) to create an image file: //! //! ```bash //! $ echo "digraph {Hello->World;}" | dot -Tsvg > mygraph.svg //! ``` //! //! This generate the following image (you can open in firefox or chrome, or switch the image type to `png` //! and use an image viewer of your choice): //! //! ![Hello World example graph](https://graphviz.org/Gallery/directed/hello.svg) //! //! Heres a more complex example, demonstrating clustering, colors, labeling and shapes: //! //! ``` //! use dot_writer::{Color, DotWriter, Attributes, Shape, Style}; //! //! let mut output_bytes = Vec::new(); //! { //! let mut writer = DotWriter::from(&mut output_bytes); //! writer.set_pretty_print(false); //! let mut digraph = writer.digraph(); //! { //! let mut cluster = digraph.cluster(); //! cluster.set_style(Style::Filled); //! cluster.set_color(Color::LightGrey); //! cluster.node_attributes() //! .set_style(Style::Filled) //! .set_color(Color::White); //! cluster.edge("a0", "a1").edge("a2").edge("a3"); //! cluster.set_label("process #1"); //! // cluster goes out of scope here to write closing bracket //! } //! { //! let mut cluster = digraph.cluster(); //! cluster.node_attributes() //! .set_style(Style::Filled); //! cluster.edge("b0", "b1").edge("b2").edge("b3"); //! cluster.set_label("process #2"); //! cluster.set_color(Color::Blue); //! // cluster goes out of scope here to write closing bracket //! } //! digraph.edge("start", "a0"); //! digraph.edge("start", "b0"); //! digraph.edge("a1", "b3"); //! digraph.edge("b2", "a3"); //! digraph.edge("a3", "a0"); //! digraph.edge("a3", "end"); //! digraph.edge("b3", "end"); //! digraph.node_named("start") //! .set_shape(Shape::Mdiamond); //! digraph.node_named("end") //! .set_shape(Shape::Msquare); //! // digraph goes out of scope here to write closing bracket //! // then writer goes out of scope here to free up output_bytes for reading //! } //! assert_eq!( //! String::from_utf8(output_bytes).unwrap(), //! "digraph{subgraph cluster_0{style=\"filled\";color=lightgray;node[style=\"filled\",color=white];a0->a1->a2->a3;label=\"process #1\";}subgraph cluster_1{node[style=\"filled\"];b0->b1->b2->b3;label=\"process #2\";color=blue;}start->a0;start->b0;a1->b3;b2->a3;a3->a0;a3->end;b3->end;start[shape=Mdiamond];end[shape=Msquare];}" //! ); //! ``` //! //! This produces (after render with dot) the following lovely graph: //! //! ![More complex example graph](https://graphviz.org/Gallery/directed/cluster.svg) #[allow(clippy::cargo, clippy::pedantic, clippy::nursery)] mod attribute; mod scope; mod writer; pub use attribute::{ ArrowType, Attributes, AttributesList, Color, Rank, RankDirection, Shape, Style, }; pub use scope::{EdgeList, Node, NodeId, PortId, PortPosId, PortPosition, Scope}; pub use writer::DotWriter; dot-writer-0.1.3/src/scope.rs000064400000000000000000000335240072674642500142320ustar 00000000000000use super::attribute::{Attributes, AttributesList}; use super::writer::{DotWriter, Line, Statement}; /// A [`Scope`] struct represents either a graph, digraph, subgraph or cluster. /// Its the workhorse of the DOT writing, and can be used to create new sub-scopes, /// add nodes, add edges, or adjust default attributes for any of the above. /// /// The only way to construct a top level graph or digraph scope is to call the /// [`DotWriter::graph`] or [`DotWriter::digraph`] functions on a new [`DotWriter`]. pub struct Scope<'d, 'w> { writer: &'d mut DotWriter<'w>, } impl<'d, 'w> Scope<'d, 'w> { pub(crate) fn new(writer: &'d mut DotWriter<'w>, prefix: &[u8]) -> Scope<'d, 'w> { { let mut line = Line::new(writer); line.write_with_whitespace(prefix); line.write(b" {"); } writer.indent(); Self { writer } } /// Starts a new nested subgraph, returning another Scope for writing to that subgraph. pub fn subgraph(&mut self) -> Scope<'_, 'w> { Scope::new(self.writer, b"subgraph") } /// Starts a new nested cluster subgraph, returning another Scope for writing to it. /// A cluster is a special case of a subgraph which groups its child nodes together. /// See the "Subgraphs and Clusters" section of the /// [Graphviz documentation](https://graphviz.org/doc/info/lang.html) /// for more information. pub fn cluster(&mut self) -> Scope<'_, 'w> { let label = format!("subgraph cluster_{}", self.writer.next_id()); Scope::new(self.writer, label.as_bytes()) } /// Returns a struct for writing the default attributes for all subgraphs from now on. pub fn graph_attributes(&mut self) -> AttributesList<'_, 'w> { let mut statement = Statement::new(self.writer); statement.write(b"graph"); AttributesList::new(statement) } /// Returns a struct for writing the default attributes for all edges from now on. pub fn edge_attributes(&mut self) -> AttributesList<'_, 'w> { let mut statement = Statement::new(self.writer); statement.write(b"edge"); AttributesList::new(statement) } /// Returns a struct for writing the default attributes for all nodes from now on. pub fn node_attributes(&mut self) -> AttributesList<'_, 'w> { let mut statement = Statement::new(self.writer); statement.write(b"node"); AttributesList::new(statement) } /// Creates a new node, with an automatic default id of the format `node_x` /// where x is an incerementing integer. You don't have to declare nodes before /// using them in a call to [`Scope::edge`], but you do have to declare them using this /// function if you want to set specifc attributes for this node (font etc). /// /// The returned value can be used to get the automatically generated id, /// and also to set the attributes. pub fn node_auto(&mut self) -> Node<'_, 'w> { let id = self.writer.next_id(); self.node_named(format!("node_{}", id)) } /// Creates a new node, with the specified id. You don't have to declare nodes before /// using them in a call to [`Scope::edge`], but you do have to declare them using this /// function if you want to set specific attributes for this node (font etc). /// /// The returned value can be used to get the assigned name, /// and also to set the attributes. pub fn node_named>(&mut self, id: S) -> Node<'_, 'w> { Node::new(Statement::new(self.writer), id.into()) } /// Add a new edge joining `start_node_id` and `end_node_id` nodes. /// Note that nodes do not need to be already defined by [`Scope::node_auto`] /// or by [`Scope::node_named`] (unless you want to set node-specific attributes). /// Arguments can be just strings, or you can use the [`Node::id`] of an already /// defined node: /// /// ``` /// use dot_writer::DotWriter; /// /// let mut output_bytes = Vec::new(); /// { /// let mut writer = DotWriter::from(&mut output_bytes); /// writer.set_pretty_print(false); /// let mut digraph = writer.digraph(); /// let a = digraph.node_auto().id(); /// digraph.edge(a, "b"); /// } /// assert_eq!( /// std::str::from_utf8(&output_bytes).unwrap(), /// "digraph{node_0;node_0->b;}" /// ); /// ``` pub fn edge(&mut self, start_node_id: S, end_node_id: E) -> EdgeList<'_, 'w> where S: AsRef<[u8]>, E: AsRef<[u8]>, { EdgeList::new(Statement::new(self.writer), start_node_id, end_node_id) } /// Add N-1 edges joining all node ids or subgraphs in the iterator, /// in the same manner as [`Scope::edge`]. /// The return value will be None if less than 2 items are passed in. /// /// ``` /// use dot_writer::DotWriter; /// /// let mut output_bytes = Vec::new(); /// { /// let mut writer = DotWriter::from(&mut output_bytes); /// writer.set_pretty_print(false); /// let mut digraph = writer.digraph(); /// digraph.edges(["a", "b", "c"]); /// } /// assert_eq!( /// std::str::from_utf8(&output_bytes).unwrap(), /// "digraph{a->b->c;}" /// ); /// ``` pub fn edges(&mut self, items: I) -> Option> where I: IntoIterator, E: AsRef<[u8]>, { let mut iter = items.into_iter(); let mut edge_list = self.edge(iter.next()?, iter.next()?); for item in iter { edge_list.edge(item); } Some(edge_list) } } impl<'d, 'w> Attributes for Scope<'d, 'w> { fn set(&mut self, name: &str, value: &str, quote: bool) -> &mut Self { { let mut statement = Statement::new(self.writer); statement.write(name.as_bytes()); statement.write(b"="); if quote { statement.write_quoted(value.as_bytes()); } else { statement.write(value.as_bytes()); } } self } } impl<'d, 'w> Drop for Scope<'d, 'w> { fn drop(&mut self) { self.writer.unindent(); Line::new(self.writer).write(b"}"); } } /// An [`EdgeList`] is returned from [`Scope::edge`] and can be used to set the attributes /// of an edge, or to chain additional nodes onto the edge statement. pub struct EdgeList<'d, 'w> { statement: Statement<'d, 'w>, } impl<'d, 'w> EdgeList<'d, 'w> { fn new, E: AsRef<[u8]>>( mut statement: Statement<'d, 'w>, start_node_id: S, end_node_id: E, ) -> EdgeList<'d, 'w> { statement.write(start_node_id.as_ref()); statement.write_edge_operator(); statement.write(end_node_id.as_ref()); EdgeList { statement } } /// Adds another edge from the last node added to `node_id`. /// Note that nodes do not need to be already defined by [`Scope::node_auto`] /// or by [`Scope::node_named`] (unless you want to set node-specifc attributes). /// /// ``` /// use dot_writer::DotWriter; /// /// let mut output_bytes = Vec::new(); /// { /// let mut writer = DotWriter::from(&mut output_bytes); /// writer.set_pretty_print(false); /// writer.digraph().edge("a", "b").edge("c").edge("d"); /// } /// assert_eq!( /// std::str::from_utf8(&output_bytes).unwrap(), /// "digraph{a->b->c->d;}" /// ); /// /// ``` pub fn edge>(&mut self, node_id: N) -> &mut Self { self.statement.write_edge_operator(); self.statement.write(node_id.as_ref()); self } /// Start writing the attributes section of an edge definition. /// See the returned structure for what attributes can be set. /// Note that no more nodes can be added after this function is called. pub fn attributes(self) -> AttributesList<'d, 'w> { AttributesList::new(self.statement) } } /// A [`Node`] is returned from [`Scope::node_named`] or [`Scope::node_auto`], /// and allows for getting the id of the node for future reference via [`Node::id`], /// /// Importantly it also implements [`Attributes`] for setting attributes /// to override existing defaults for this specific node. pub struct Node<'d, 'w> { attributes: AttributesList<'d, 'w>, id: String, } impl<'d, 'w> Node<'d, 'w> { fn new(mut statement: Statement<'d, 'w>, id: String) -> Self { statement.write(id.as_bytes()); Node { attributes: AttributesList::new(statement), id, } } /// Returns a copy of the [`Node`] id, for later use with [`Scope::edge`]. /// Note that as the node definition will not finish writing until after /// this [`Node`] goes out of scope, you'll need to save the id if you /// want to draw an edge to it later. This function is most usefull when /// the edge was automatically generated with [`Scope::node_auto`]: /// ``` /// use dot_writer::DotWriter; /// /// let mut output_bytes = Vec::new(); /// { /// let mut writer = DotWriter::from(&mut output_bytes); /// writer.set_pretty_print(false); /// let mut digraph = writer.digraph(); /// let a_id = { /// let mut sub_graph = digraph.subgraph(); /// let a_id = sub_graph.node_auto().id(); /// // sub_graph goes out of scope here to close bracket /// // but id is kept for later call /// a_id /// }; /// digraph.edge(a_id, "b"); /// } /// assert_eq!( /// std::str::from_utf8(&output_bytes).unwrap(), /// "digraph{subgraph{node_0;}node_0->b;}" /// ); /// /// ``` pub fn id(&self) -> NodeId { NodeId { id: self.id.clone(), } } } impl<'d, 'w> std::ops::DerefMut for Node<'d, 'w> { fn deref_mut(&mut self) -> &mut AttributesList<'d, 'w> { &mut self.attributes } } impl<'d, 'w> std::ops::Deref for Node<'d, 'w> { type Target = AttributesList<'d, 'w>; fn deref(&self) -> &AttributesList<'d, 'w> { &self.attributes } } /// A [`NodeId`] wraps a string storing the Id of a Node /// It's designed for use with the [`Scope::edge`] function, /// and for creating [`PortId`] using [`NodeId::port`]. #[derive(Clone, Debug)] pub struct NodeId { id: String, } impl NodeId { /// Creates a [`PortId`] for refering to a port. These are specific sub parts of /// a [`Shape::Record`](`crate::attribute::Shape::Record`) /// or [`Shape::Mrecord`](`crate::attribute::Shape::Mrecord`) /// (for more information see "Record-based Nodes" /// on the [Graphviz documentation](https://graphviz.org/doc/info/shapes.html)). pub fn port(&self, port_id: &str) -> PortId { PortId { id: format!("{}:{}", self.id, port_id), } } } impl From for NodeId { fn from(id: String) -> Self { Self { id } } } impl From for String { fn from(node_id: NodeId) -> Self { node_id.id } } impl AsRef<[u8]> for NodeId { fn as_ref(&self) -> &[u8] { self.id.as_bytes() } } /// A [`PortId`] wraps a string referning to the nnode id and port of a specifc record /// or Mrecord node (for more information see "Record-based Nodes" /// in the [Graphviz documentation](https://graphviz.org/doc/info/shapes.html)). #[derive(Clone, Debug)] pub struct PortId { id: String, } impl PortId { /// Creates a [`PortPosId`] for refering to a specific position on a record or Mrecord /// port for an edge to attach to. pub fn position(&self, position: PortPosition) -> PortPosId { PortPosId { id: format!("{}:{}", self.id, position.as_ref()), } } } impl From for PortId { fn from(id: String) -> Self { Self { id } } } impl From for String { fn from(port_id: PortId) -> Self { port_id.id } } impl AsRef<[u8]> for PortId { fn as_ref(&self) -> &[u8] { self.id.as_bytes() } } /// A [`PortPosId`] wraps a string referning to the [`NodeId`], [`PortId`] and [`PortPosition`] /// of a specifc record or Mrecord node (for more information see "portPos" /// in the [Graphviz documentation](https://graphviz.org/docs/attr-types/portPos/)). #[derive(Clone, Debug)] pub struct PortPosId { id: String, } impl From for PortPosId { fn from(id: String) -> Self { Self { id } } } impl From for String { fn from(port_pos_id: PortPosId) -> Self { port_pos_id.id } } impl AsRef<[u8]> for PortPosId { fn as_ref(&self) -> &[u8] { self.id.as_bytes() } } /// Refers the position to add an edge for of a specifc record or Mrecord node /// (for more information see "portPos" /// in the [Graphviz documentation](https://graphviz.org/docs/attr-types/portPos/)). #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum PortPosition { North, NorthEast, East, SouthEast, South, SouthWest, West, NorthWest, Centre, Auto, } impl AsRef for PortPosition { fn as_ref(&self) -> &str { match self { Self::North => "n", Self::NorthEast => "ne", Self::East => "e", Self::SouthEast => "se", Self::South => "s", Self::SouthWest => "sw", Self::West => "w", Self::NorthWest => "nw", Self::Centre => "c", Self::Auto => "_", } } } #[cfg(test)] mod test { use super::*; #[test] fn test_manual_and_auto_nodes() { let output = DotWriter::write_string(|writer| { let mut digraph = writer.digraph(); digraph.node_auto(); let a = digraph.node_named("a").id(); digraph.node_named(NodeId::from(String::from("b"))); digraph.edge(a, "b").edge(String::from("c")); }); assert_eq!(output, "digraph{node_0;a;b;a->b->c;}") } } dot-writer-0.1.3/src/writer.rs000064400000000000000000000162710072674642500144350ustar 00000000000000use std::io::Write; use super::scope::Scope; const INDENT_STEP: usize = 2; /// The entry point struct for writing a DOT graph. /// See the examples on the index for how to use it. /// This struct must live for the livetime of writing the graph, /// and also outlive the [`Write`] struct it points to, /// because it's [`Drop`] trait will finish writing graph. pub struct DotWriter<'w> { writer: &'w mut dyn Write, indent: usize, next_id: usize, digraph: bool, pretty_print: bool, } /// This is the only constructor, which requires a [`Write`] /// to borrow and write the DOT output to. /// Defaults to pretty printing the output (see [`DotWriter::set_pretty_print`]). impl<'w, W: Write> From<&'w mut W> for DotWriter<'w> { fn from(writer: &'w mut W) -> Self { Self { writer, indent: 0, next_id: 0, digraph: false, pretty_print: true, } } } impl<'w> DotWriter<'w> { /// If set to true, then output will have additional whitespace /// including newlines and indentation to make the output more /// human friendly. If false then this will be left out and the output /// will be more compact. Defaults to true. For example disabled: /// /// ``` /// use dot_writer::DotWriter; /// /// let mut bytes = Vec::new(); /// let mut writer = DotWriter::from(&mut bytes); /// writer.set_pretty_print(false); /// writer.graph().subgraph().cluster().subgraph(); /// println!("{}", std::str::from_utf8(&bytes).unwrap()); /// ``` /// /// Gives you: /// /// ` /// graph {subgraph {subgraph cluster_0 {subgraph {}}}} /// ` /// /// While enabled: /// /// ``` /// use dot_writer::DotWriter; /// /// let mut bytes = Vec::new(); /// let mut writer = DotWriter::from(&mut bytes); /// // writer.set_pretty_print(false); defaults to true anyway /// writer.graph().subgraph().cluster().subgraph(); /// println!("{}", std::str::from_utf8(&bytes).unwrap()); /// ``` /// /// Gives you: /// /// ```txt /// graph { /// subgraph { /// subgraph cluster_0 { /// subgraph {} /// } /// } /// } ///``` pub fn set_pretty_print(&mut self, pretty_print: bool) { self.pretty_print = pretty_print; } /// Start a new undirection graph. This uses the edge operator `--` /// automatically, which means edges should be rendered without any /// arrowheads by default. /// ``` /// use dot_writer::DotWriter; /// /// let mut bytes = Vec::new(); /// let mut writer = DotWriter::from(&mut bytes); /// writer.set_pretty_print(false); /// writer.graph().edge("a", "b"); /// assert_eq!( /// std::str::from_utf8(&bytes).unwrap(), /// "graph{a--b;}" /// ); /// ``` pub fn graph(&mut self) -> Scope<'_, 'w> { self.digraph = false; Scope::new(self, b"graph") } /// Start a new directed graph. This uses the edge operator `->` /// automatically, which means edges should be redered with an /// arrow head pointing to the head (end) node. /// ``` /// use dot_writer::DotWriter; /// /// let mut bytes = Vec::new(); /// let mut writer = DotWriter::from(&mut bytes); /// writer.set_pretty_print(false); /// writer.digraph().edge("a", "b"); /// assert_eq!( /// std::str::from_utf8(&bytes).unwrap(), /// "digraph{a->b;}" /// ); /// ``` pub fn digraph(&mut self) -> Scope<'_, 'w> { self.digraph = true; Scope::new(self, b"digraph") } /// Uses a callback to write DOT to a [`String`]. This is useful /// if you just want to write your dot code to a string rather than /// a file or stdout, and want less boiler plate setup code. /// It's used internally for unit testing, so the output is not /// pretty printed by default (but you can overwrite that by calling /// [`DotWriter::set_pretty_print`] from within the callback). /// /// ``` /// use dot_writer::DotWriter; /// /// let output = DotWriter::write_string(|writer| { /// let mut graph = writer.graph(); /// graph.edge("a", "b"); /// }); /// assert_eq!(output, "graph{a--b;}"); /// ``` pub fn write_string(builder: F) -> String { let mut bytes = Vec::new(); let mut writer = DotWriter::from(&mut bytes); writer.set_pretty_print(false); (builder)(&mut writer); String::from_utf8(bytes).unwrap() } } impl<'w> DotWriter<'w> { pub(crate) fn write(&mut self, bytes: &[u8]) { if self.pretty_print { self.writer.write_all(bytes).unwrap(); } else { for b in bytes.iter().filter(|b| !b.is_ascii_whitespace()) { self.writer.write_all(&[*b]).unwrap(); } } } pub(crate) fn write_with_whitespace(&mut self, bytes: &[u8]) { self.writer.write_all(bytes).unwrap(); } pub(crate) fn write_quoted(&mut self, bytes: &[u8]) { self.writer.write_all(b"\"").unwrap(); self.write_with_whitespace(bytes); self.writer.write_all(b"\"").unwrap(); } pub(crate) fn write_indent(&mut self) { for _ in 0..self.indent { self.writer.write_all(b" ").unwrap(); } } pub(crate) fn write_edge_operator(&mut self) { match self.digraph { true => self.write(b" -> "), false => self.write(b" -- "), } } pub(crate) fn indent(&mut self) { if self.pretty_print { self.indent += INDENT_STEP; } } pub(crate) fn unindent(&mut self) { if self.pretty_print { self.indent -= INDENT_STEP; } } pub(crate) fn next_id(&mut self) -> usize { let next_id = self.next_id; self.next_id += 1; next_id } } // // Statement // pub(crate) struct Statement<'d, 'w> { line: Line<'d, 'w>, } impl<'d, 'w> Statement<'d, 'w> { pub(crate) fn new(writer: &'d mut DotWriter<'w>) -> Self { Self { line: Line::new(writer), } } } impl<'d, 'w> std::ops::DerefMut for Statement<'d, 'w> { fn deref_mut(&mut self) -> &mut DotWriter<'w> { &mut *self.line } } impl<'d, 'w> std::ops::Deref for Statement<'d, 'w> { type Target = DotWriter<'w>; fn deref(&self) -> &DotWriter<'w> { &*self.line } } impl<'d, 'w> Drop for Statement<'d, 'w> { fn drop(&mut self) { self.line.write(b";"); } } // // Line // pub(crate) struct Line<'d, 'w> { writer: &'d mut DotWriter<'w>, } impl<'d, 'w> Line<'d, 'w> { pub(crate) fn new(writer: &'d mut DotWriter<'w>) -> Self { writer.write_indent(); Self { writer } } } impl<'d, 'w> std::ops::DerefMut for Line<'d, 'w> { fn deref_mut(&mut self) -> &mut DotWriter<'w> { self.writer } } impl<'d, 'w> std::ops::Deref for Line<'d, 'w> { type Target = DotWriter<'w>; fn deref(&self) -> &DotWriter<'w> { self.writer } } impl<'a, 'b: 'a> Drop for Line<'a, 'b> { fn drop(&mut self) { if self.writer.pretty_print { self.writer.write(b"\n"); } } } dot-writer-0.1.3/tests/integration.rs000064400000000000000000000026600072674642500160140ustar 00000000000000use dot_writer::*; #[test] fn test_trivial_graphs() { assert_eq!( DotWriter::write_string(|w| { w.graph().edge("a", "b"); }), "graph{a--b;}" ); assert_eq!( DotWriter::write_string(|w| { w.digraph().edge("a", "b"); }), "digraph{a->b;}" ); } #[test] fn test_nested_subgraphs() { assert_eq!( DotWriter::write_string(|w| { w.graph().subgraph().cluster().subgraph(); }), "graph{subgraph{subgraph cluster_0{subgraph{}}}}" ) } #[test] fn test_pretty_printing() { // NB \x20 is the ascii for a space (" ") // required to make the multiline string not strip the whitespace assert_eq!( DotWriter::write_string(|w| { w.set_pretty_print(true); let mut graph = w.graph(); let mut sub = graph.subgraph(); let mut cluster = sub.cluster(); let mut sub_sub = cluster.subgraph(); let a = sub_sub.node_named("a").id(); let b = sub_sub.node_named("b").id(); sub_sub.edge(a, b).attributes().set_color(Color::Red); }), "graph {\n\ \x20 subgraph {\n\ \x20 subgraph cluster_0 {\n\ \x20 subgraph {\n\ \x20 a;\n\ \x20 b;\n\ \x20 a -- b [color=red];\n\ \x20 }\n\ \x20 }\n\ \x20 }\n\ }\n" ) }