From b7374182f7298183f261a0905ac10f5c8c9701ad Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Fri, 25 Apr 2014 19:10:03 +0200 Subject: [PATCH 1/3] Add a `graphviz` crate for making .dot files to layout and render graphs. --- mk/crates.mk | 3 +- src/libgraphviz/lib.rs | 746 +++++++++++++++++++++++++++++ src/libgraphviz/maybe_owned_vec.rs | 71 +++ 3 files changed, 819 insertions(+), 1 deletion(-) create mode 100644 src/libgraphviz/lib.rs create mode 100644 src/libgraphviz/maybe_owned_vec.rs diff --git a/mk/crates.mk b/mk/crates.mk index fc0afa6df6271..bc8567d3c9565 100644 --- a/mk/crates.mk +++ b/mk/crates.mk @@ -51,7 +51,7 @@ TARGET_CRATES := libc std green rustuv native flate arena glob term semver \ uuid serialize sync getopts collections num test time rand \ - workcache url log regex + workcache url log regex graphviz HOST_CRATES := syntax rustc rustdoc fourcc hexfloat regex_macros CRATES := $(TARGET_CRATES) $(HOST_CRATES) TOOLS := compiletest rustdoc rustc @@ -67,6 +67,7 @@ DEPS_rustdoc := rustc native:sundown serialize sync getopts collections \ test time DEPS_flate := std native:miniz DEPS_arena := std collections +DEPS_graphviz := std DEPS_glob := std DEPS_serialize := std collections log DEPS_term := std collections diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs new file mode 100644 index 0000000000000..986a14a7b573c --- /dev/null +++ b/src/libgraphviz/lib.rs @@ -0,0 +1,746 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +/*! Generate files suitable for use with [Graphviz](http://www.graphviz.org/) + +The `render` function generates output (e.g. a `output.dot` file) for +use with [Graphviz](http://www.graphviz.org/) by walking a labelled +graph. (Graphviz can then automatically lay out the nodes and edges +of the graph, and also optionally render the graph as an image or +other [output formats]( +http://www.graphviz.org/content/output-formats), such as SVG.) + +Rather than impose some particular graph data structure on clients, +this library exposes two traits that clients can implement on their +own structs before handing them over to the rendering function. + +Note: This library does not yet provide access to the full +expressiveness of the [DOT language]( +http://www.graphviz.org/doc/info/lang.html). For example, there are +many [attributes](http://www.graphviz.org/content/attrs) related to +providing layout hints (e.g. left-to-right versus top-down, which +algorithm to use, etc). The current intention of this library is to +emit a human-readable .dot file with very regular structure suitable +for easy post-processing. + +# Examples + +The first example uses a very simple graph representation: a list of +pairs of ints, representing the edges (the node set is implicit). +Each node label is derived directly from the int representing the node, +while the edge labels are all empty strings. + +This example also illustrates how to use the `Borrowed` variant of +`MaybeOwnedVector` to return a slice into the edge list, rather than +constructing a copy from scratch. + +The output from this example renders five nodes, with the first four +forming a diamond-shaped acyclic graph and then pointing to the fifth +which is cyclic. + +```rust +use dot = graphviz; + +type Nd = int; +type Ed = (int,int); +struct Edges(Vec); + +pub fn main() { + use std::io::File; + let edges = Edges(vec!((0,1), (0,2), (1,3), (2,3), (3,4), (4,4))); + let mut f = File::create(&Path::new("example1.dot")); + dot::render(&edges, &mut f).unwrap() +} + +impl<'a> dot::Labeller<'a, Nd, Ed> for Edges { + fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example1") } + + fn node_id(&'a self, n: &Nd) -> dot::Id<'a> { + dot::Id::new(format!("N{}", *n)) + } +} + +impl<'a> dot::GraphWalk<'a, Nd, Ed> for Edges { + fn nodes(&self) -> dot::Nodes<'a,Nd> { + // (assumes that |N| \approxeq |E|) + let &Edges(ref v) = self; + let mut nodes = Vec::with_capacity(v.len()); + for &(s,t) in v.iter() { + nodes.push(s); nodes.push(t); + } + nodes.sort(); + nodes.dedup(); + nodes.move_iter().collect() + } + + fn edges(&'a self) -> dot::Edges<'a,Ed> { + let &Edges(ref edges) = self; + dot::maybe_owned_vec::Borrowed(edges.as_slice()) + } + + fn source(&self, e: &Ed) -> Nd { let &(s,_) = e; s } + + fn target(&self, e: &Ed) -> Nd { let &(_,t) = e; t } +} +``` + +Output from first example (in `example1.dot`): + +```DOT +digraph example1 { + N0[label="N0"]; + N1[label="N1"]; + N2[label="N2"]; + N3[label="N3"]; + N4[label="N4"]; + N0 -> N1[label=""]; + N0 -> N2[label=""]; + N1 -> N3[label=""]; + N2 -> N3[label=""]; + N3 -> N4[label=""]; + N4 -> N4[label=""]; +} +``` + +The second example illustrates using `node_label` and `edge_label` to +add labels to the nodes and edges in the rendered graph. The graph +here carries both `nodes` (the label text to use for rendering a +particular node), and `edges` (again a list of `(source,target)` +indices). + +This example also illustrates how to use a type (in this case the edge +type) that shares substructure with the graph: the edge type here is a +direct reference to the `(source,target)` pair stored in the graph's +internal vector (rather than passing around a copy of the pair +itself). Note that in this case, this implies that `fn edges(&'a +self)` must construct a fresh `Vec<&'a (uint,uint)>` from the +`Vec<(uint,uint)>` edges stored in `self`. + +The output from this example renders four nodes that make up the +Hasse-diagram for the subsets of the set `{x, y}`. Each edge is +labelled with the ⊆ character (specified using the HTML character +entity `&sube`). + +```rust +use dot = graphviz; +use std::str; +use std::io::File; + +type Nd = uint; +type Ed<'a> = &'a (uint, uint); +struct Graph { nodes: Vec<&'static str>, edges: Vec<(uint,uint)> } + +pub fn main() { + let nodes = vec!("{x,y}","{x}","{y}","{}"); + let edges = vec!((0,1), (0,2), (1,3), (2,3)); + let graph = Graph { nodes: nodes, edges: edges }; + + let mut f = File::create(&Path::new("example2.dot")); + dot::render(&graph, &mut f).unwrap() +} + +impl<'a> dot::Labeller<'a, Nd, Ed<'a>> for Graph { + fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example2") } + fn node_id(&'a self, n: &Nd) -> dot::Id<'a> { + dot::Id::new(format!("N{}", n)) + } + fn node_label<'a>(&'a self, n: &Nd) -> dot::LabelText<'a> { + dot::LabelStr(str::Slice(self.nodes.get(*n).as_slice())) + } + fn edge_label<'a>(&'a self, _: &Ed) -> dot::LabelText<'a> { + dot::LabelStr(str::Slice("⊆")) + } +} + +impl<'a> dot::GraphWalk<'a, Nd, Ed<'a>> for Graph { + fn nodes(&self) -> dot::Nodes<'a,Nd> { range(0,self.nodes.len()).collect() } + fn edges(&'a self) -> dot::Edges<'a,Ed<'a>> { self.edges.iter().collect() } + fn source(&self, e: &Ed) -> Nd { let & &(s,_) = e; s } + fn target(&self, e: &Ed) -> Nd { let & &(_,t) = e; t } +} +``` + +The third example is similar to the second, except now each node and +edge now carries a reference to the string label for each node as well +as that node's index. (This is another illustration of how to share +structure with the graph itself, and why one might want to do so.) + +The output from this example is the same as the second example: the +Hasse-diagram for the subsets of the set `{x, y}`. + +```rust +use dot = graphviz; +use std::str; +use std::io::File; + +type Nd<'a> = (uint, &'a str); +type Ed<'a> = (Nd<'a>, Nd<'a>); +struct Graph { nodes: Vec<&'static str>, edges: Vec<(uint,uint)> } + +pub fn main() { + let nodes = vec!("{x,y}","{x}","{y}","{}"); + let edges = vec!((0,1), (0,2), (1,3), (2,3)); + let graph = Graph { nodes: nodes, edges: edges }; + + let mut f = File::create(&Path::new("example3.dot")); + dot::render(&graph, &mut f).unwrap() +} + +impl<'a> dot::Labeller<'a, Nd<'a>, Ed<'a>> for Graph { + fn graph_id(&'a self) -> dot::Id<'a> { dot::Id::new("example3") } + fn node_id(&'a self, n: &Nd<'a>) -> dot::Id<'a> { + dot::Id::new(format!("N{:u}", n.val0())) + } + fn node_label<'a>(&'a self, n: &Nd<'a>) -> dot::LabelText<'a> { + let &(i, _) = n; + dot::LabelStr(str::Slice(self.nodes.get(i).as_slice())) + } + fn edge_label<'a>(&'a self, _: &Ed<'a>) -> dot::LabelText<'a> { + dot::LabelStr(str::Slice("⊆")) + } +} + +impl<'a> dot::GraphWalk<'a, Nd<'a>, Ed<'a>> for Graph { + fn nodes(&'a self) -> dot::Nodes<'a,Nd<'a>> { + self.nodes.iter().map(|s|s.as_slice()).enumerate().collect() + } + fn edges(&'a self) -> dot::Edges<'a,Ed<'a>> { + self.edges.iter() + .map(|&(i,j)|((i, self.nodes.get(i).as_slice()), + (j, self.nodes.get(j).as_slice()))) + .collect() + } + fn source(&self, e: &Ed<'a>) -> Nd<'a> { let &(s,_) = e; s } + fn target(&self, e: &Ed<'a>) -> Nd<'a> { let &(_,t) = e; t } +} +``` + +# References + +* [Graphviz](http://www.graphviz.org/) + +* [DOT language](http://www.graphviz.org/doc/info/lang.html) + +*/ + +#![crate_id = "graphviz#0.11-pre"] +#![crate_type = "rlib"] +#![crate_type = "dylib"] +#![license = "MIT/ASL2"] +#![doc(html_logo_url = "http://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "http://www.rust-lang.org/favicon.ico", + html_root_url = "http://static.rust-lang.org/doc/master")] + +#![experimental] + +use std::io; +use std::str; +use self::maybe_owned_vec::MaybeOwnedVector; + +pub mod maybe_owned_vec; + +/// The text for a graphviz label on a node or edge. +pub enum LabelText<'a> { + /// This kind of label preserves the text directly as is. + /// + /// Occurrences of backslashes (`\`) are escaped, and thus appear + /// as backslashes in the rendered label. + LabelStr(str::MaybeOwned<'a>), + + /// This kind of label uses the graphviz label escString type: + /// http://www.graphviz.org/content/attrs#kescString + /// + /// Occurrences of backslashes (`\`) are not escaped; instead they + /// are interpreted as initiating an escString escape sequence. + /// + /// Escape sequences of particular interest: in addition to `\n` + /// to break a line (centering the line preceding the `\n`), there + /// are also the escape sequences `\l` which left-justifies the + /// preceding line and `\r` which right-justifies it. + EscStr(str::MaybeOwned<'a>), +} + +// There is a tension in the design of the labelling API. +// +// For example, I considered making a `Labeller` trait that +// provides labels for `T`, and then making the graph type `G` +// implement `Labeller` and `Labeller`. However, this is +// not possible without functional dependencies. (One could work +// around that, but I did not explore that avenue heavily.) +// +// Another approach that I actually used for a while was to make a +// `Label` trait that is implemented by the client-specific +// Node and Edge types (as well as an implementation on Graph itself +// for the overall name for the graph). The main disadvantage of this +// second approach (compared to having the `G` type parameter +// implement a Labelling service) that I have encountered is that it +// makes it impossible to use types outside of the current crate +// directly as Nodes/Edges; you need to wrap them in newtype'd +// structs. See e.g. the `No` and `Ed` structs in the examples. (In +// practice clients using a graph in some other crate would need to +// provide some sort of adapter shim over the graph anyway to +// interface with this library). +// +// Another approach would be to make a single `Labeller` trait +// that provides three methods (graph_label, node_label, edge_label), +// and then make `G` implement `Labeller`. At first this did not +// appeal to me, since I had thought I would need separate methods on +// each data variant for dot-internal identifiers versus user-visible +// labels. However, the identifier/label distinction only arises for +// nodes; graphs themselves only have identifiers, and edges only have +// labels. +// +// So in the end I decided to use the third approach described above. + +/// `Id` is a Graphviz `ID`. +pub struct Id<'a> { + name: str::MaybeOwned<'a>, +} + +impl<'a> Id<'a> { + /// Creates an `Id` named `name`. + /// + /// The caller must ensure that the input conforms to an + /// identifier format: it must be a non-empty string made up of + /// alphanumeric or underscore characters, not beginning with a + /// digit (i.e. the regular expression `[a-zA-Z_][a-zA-Z_0-9]*`). + /// + /// (Note: this format is a strict subset of the `ID` format + /// defined by the DOT language. This function may change in the + /// future to accept a broader subset, or the entirety, of DOT's + /// `ID` format.) + pub fn new>(name: Name) -> Id<'a> { + let name = name.into_maybe_owned(); + { + let mut chars = name.as_slice().chars(); + assert!(is_letter_or_underscore(chars.next().unwrap())); + assert!(chars.all(is_constituent)); + } + return Id{ name: name }; + + fn is_letter_or_underscore(c: char) -> bool { + in_range('a', c, 'z') || in_range('A', c, 'Z') || c == '_' + } + fn is_constituent(c: char) -> bool { + is_letter_or_underscore(c) || in_range('0', c, '9') + } + fn in_range(low: char, c: char, high: char) -> bool { + low as uint <= c as uint && c as uint <= high as uint + } + } + + pub fn as_slice(&'a self) -> &'a str { + self.name.as_slice() + } + + pub fn name(self) -> str::MaybeOwned<'a> { + self.name + } +} + +/// Each instance of a type that implements `Label` maps to a +/// unique identifier with respect to `C`, which is used to identify +/// it in the generated .dot file. They can also provide more +/// elaborate (and non-unique) label text that is used in the graphviz +/// rendered output. + +/// The graph instance is responsible for providing the DOT compatible +/// identifiers for the nodes and (optionally) rendered labels for the nodes and +/// edges, as well as an identifier for the graph itself. +pub trait Labeller<'a,N,E> { + /// Must return a DOT compatible identifier naming the graph. + fn graph_id(&'a self) -> Id<'a>; + + /// Maps `n` to a unique identifier with respect to `self`. The + /// implementor is responsible for ensuring that the returned name + /// is a valid DOT identifier. + fn node_id(&'a self, n: &N) -> Id<'a>; + + /// Maps `n` to a label that will be used in the rendered output. + /// The label need not be unique, and may be the empty string; the + /// default is just the output from `node_id`. + fn node_label(&'a self, n: &N) -> LabelText<'a> { + LabelStr(self.node_id(n).name) + } + + /// Maps `e` to a label that will be used in the rendered output. + /// The label need not be unique, and may be the empty string; the + /// default is in fact the empty string. + fn edge_label(&'a self, e: &E) -> LabelText<'a> { + let _ignored = e; + LabelStr(str::Slice("")) + } +} + +impl<'a> LabelText<'a> { + fn escape_char(c: char, f: |char|) { + match c { + // not escaping \\, since Graphviz escString needs to + // interpret backslashes; see EscStr above. + '\\' => f(c), + _ => c.escape_default(f) + } + } + fn escape_str(s: &str) -> StrBuf { + let mut out = StrBuf::with_capacity(s.len()); + for c in s.chars() { + LabelText::escape_char(c, |c| out.push_char(c)); + } + out + } + + /// Renders text as string suitable for a label in a .dot file. + pub fn escape(&self) -> ~str { + match self { + &LabelStr(ref s) => s.as_slice().escape_default(), + &EscStr(ref s) => LabelText::escape_str(s.as_slice()).into_owned(), + } + } +} + +pub type Nodes<'a,N> = MaybeOwnedVector<'a,N>; +pub type Edges<'a,E> = MaybeOwnedVector<'a,E>; + +// (The type parameters in GraphWalk should be associated items, +// when/if Rust supports such.) + +/// GraphWalk is an abstraction over a directed graph = (nodes,edges) +/// made up of node handles `N` and edge handles `E`, where each `E` +/// can be mapped to its source and target nodes. +/// +/// The lifetime parameter `'a` is exposed in this trait (rather than +/// introduced as a generic parameter on each method declaration) so +/// that a client impl can choose `N` and `E` that have substructure +/// that is bound by the self lifetime `'a`. +/// +/// The `nodes` and `edges` method each return instantiations of +/// `MaybeOwnedVector` to leave implementors the freedom to create +/// entirely new vectors or to pass back slices into internally owned +/// vectors. +pub trait GraphWalk<'a, N, E> { + /// Returns all the nodes in this graph. + fn nodes(&'a self) -> Nodes<'a, N>; + /// Returns all of the edges in this graph. + fn edges(&'a self) -> Edges<'a, E>; + /// The source node for `edge`. + fn source(&'a self, edge: &E) -> N; + /// The target node for `edge`. + fn target(&'a self, edge: &E) -> N; +} + +/// Renders directed graph `g` into the writer `w` in DOT syntax. +/// (Main entry point for the library.) +pub fn render<'a, N, E, G:Labeller<'a,N,E>+GraphWalk<'a,N,E>, W:Writer>( + g: &'a G, + w: &mut W) -> io::IoResult<()> +{ + fn writeln(w: &mut W, arg: &[&str]) -> io::IoResult<()> { + for &s in arg.iter() { try!(w.write_str(s)); } + w.write_char('\n') + } + + fn indent(w: &mut W) -> io::IoResult<()> { + w.write_str(" ") + } + + try!(writeln(w, ["digraph ", g.graph_id().as_slice(), " {"])); + for n in g.nodes().iter() { + try!(indent(w)); + let id = g.node_id(n); + let escaped = g.node_label(n).escape(); + try!(writeln(w, [id.as_slice(), + "[label=\"", escaped.as_slice(), "\"];"])); + } + + for e in g.edges().iter() { + let escaped_label = g.edge_label(e).escape(); + try!(indent(w)); + let source = g.source(e); + let target = g.target(e); + let source_id = g.node_id(&source); + let target_id = g.node_id(&target); + try!(writeln(w, [source_id.as_slice(), " -> ", target_id.as_slice(), + "[label=\"", escaped_label.as_slice(), "\"];"])); + } + + writeln(w, ["}"]) +} + +#[cfg(test)] +mod tests { + use super::{Id, LabelText, LabelStr, EscStr, Labeller}; + use super::{Nodes, Edges, GraphWalk, render}; + use std::io::{MemWriter, BufReader, IoResult}; + use std::str; + + /// each node is an index in a vector in the graph. + type Node = uint; + struct Edge { + from: uint, to: uint, label: &'static str + } + + fn Edge(from: uint, to: uint, label: &'static str) -> Edge { + Edge { from: from, to: to, label: label } + } + + struct LabelledGraph { + /// The name for this graph. Used for labelling generated `digraph`. + name: &'static str, + + /// Each node is an index into `node_labels`; these labels are + /// used as the label text for each node. (The node *names*, + /// which are unique identifiers, are derived from their index + /// in this array.) + /// + /// If a node maps to None here, then just use its name as its + /// text. + node_labels: Vec>, + + /// Each edge relates a from-index to a to-index along with a + /// label; `edges` collects them. + edges: Vec, + } + + // A simple wrapper around LabelledGraph that forces the labels to + // be emitted as EscStr. + struct LabelledGraphWithEscStrs { + graph: LabelledGraph + } + + enum NodeLabels { + AllNodesLabelled(Vec), + UnlabelledNodes(uint), + SomeNodesLabelled(Vec>), + } + + type Trivial = NodeLabels<&'static str>; + + impl NodeLabels<&'static str> { + fn to_opt_strs(self) -> Vec> { + match self { + UnlabelledNodes(len) + => Vec::from_elem(len, None).move_iter().collect(), + AllNodesLabelled(lbls) + => lbls.move_iter().map( + |l|Some(l)).collect(), + SomeNodesLabelled(lbls) + => lbls.move_iter().collect(), + } + } + } + + impl LabelledGraph { + fn new(name: &'static str, + node_labels: Trivial, + edges: Vec) -> LabelledGraph { + LabelledGraph { + name: name, + node_labels: node_labels.to_opt_strs(), + edges: edges + } + } + } + + impl LabelledGraphWithEscStrs { + fn new(name: &'static str, + node_labels: Trivial, + edges: Vec) -> LabelledGraphWithEscStrs { + LabelledGraphWithEscStrs { + graph: LabelledGraph::new(name, node_labels, edges) + } + } + } + + fn id_name<'a>(n: &Node) -> Id<'a> { + Id::new(format!("N{:u}", *n)) + } + + impl<'a> Labeller<'a, Node, &'a Edge> for LabelledGraph { + fn graph_id(&'a self) -> Id<'a> { + Id::new(self.name.as_slice()) + } + fn node_id(&'a self, n: &Node) -> Id<'a> { + id_name(n) + } + fn node_label(&'a self, n: &Node) -> LabelText<'a> { + match self.node_labels.get(*n) { + &Some(ref l) => LabelStr(str::Slice(l.as_slice())), + &None => LabelStr(id_name(n).name()), + } + } + fn edge_label(&'a self, e: & &'a Edge) -> LabelText<'a> { + LabelStr(str::Slice(e.label.as_slice())) + } + } + + impl<'a> Labeller<'a, Node, &'a Edge> for LabelledGraphWithEscStrs { + fn graph_id(&'a self) -> Id<'a> { self.graph.graph_id() } + fn node_id(&'a self, n: &Node) -> Id<'a> { self.graph.node_id(n) } + fn node_label(&'a self, n: &Node) -> LabelText<'a> { + match self.graph.node_label(n) { + LabelStr(s) | EscStr(s) => EscStr(s), + } + } + fn edge_label(&'a self, e: & &'a Edge) -> LabelText<'a> { + match self.graph.edge_label(e) { + LabelStr(s) | EscStr(s) => EscStr(s), + } + } + } + + impl<'a> GraphWalk<'a, Node, &'a Edge> for LabelledGraph { + fn nodes(&'a self) -> Nodes<'a,Node> { + range(0u, self.node_labels.len()).collect() + } + fn edges(&'a self) -> Edges<'a,&'a Edge> { + self.edges.iter().collect() + } + fn source(&'a self, edge: & &'a Edge) -> Node { + edge.from + } + fn target(&'a self, edge: & &'a Edge) -> Node { + edge.to + } + } + + impl<'a> GraphWalk<'a, Node, &'a Edge> for LabelledGraphWithEscStrs { + fn nodes(&'a self) -> Nodes<'a,Node> { + self.graph.nodes() + } + fn edges(&'a self) -> Edges<'a,&'a Edge> { + self.graph.edges() + } + fn source(&'a self, edge: & &'a Edge) -> Node { + edge.from + } + fn target(&'a self, edge: & &'a Edge) -> Node { + edge.to + } + } + + fn test_input(g: LabelledGraph) -> IoResult<~str> { + let mut writer = MemWriter::new(); + render(&g, &mut writer).unwrap(); + let mut r = BufReader::new(writer.get_ref()); + r.read_to_str() + } + + // All of the tests use raw-strings as the format for the expected outputs, + // so that you can cut-and-paste the content into a .dot file yourself to + // see what the graphviz visualizer would produce. + + #[test] + fn empty_graph() { + let labels : Trivial = UnlabelledNodes(0); + let r = test_input(LabelledGraph::new("empty_graph", labels, vec!())); + assert_eq!(r.unwrap().as_slice(), +r#"digraph empty_graph { +} +"#); + } + + #[test] + fn single_node() { + let labels : Trivial = UnlabelledNodes(1); + let r = test_input(LabelledGraph::new("single_node", labels, vec!())); + assert_eq!(r.unwrap().as_slice(), +r#"digraph single_node { + N0[label="N0"]; +} +"#); + } + + #[test] + fn single_edge() { + let labels : Trivial = UnlabelledNodes(2); + let result = test_input(LabelledGraph::new("single_edge", labels, + vec!(Edge(0, 1, "E")))); + assert_eq!(result.unwrap().as_slice(), +r#"digraph single_edge { + N0[label="N0"]; + N1[label="N1"]; + N0 -> N1[label="E"]; +} +"#); + } + + #[test] + fn single_cyclic_node() { + let labels : Trivial = UnlabelledNodes(1); + let r = test_input(LabelledGraph::new("single_cyclic_node", labels, + vec!(Edge(0, 0, "E")))); + assert_eq!(r.unwrap().as_slice(), +r#"digraph single_cyclic_node { + N0[label="N0"]; + N0 -> N0[label="E"]; +} +"#); + } + + #[test] + fn hasse_diagram() { + let labels = AllNodesLabelled(vec!("{x,y}", "{x}", "{y}", "{}")); + let r = test_input(LabelledGraph::new( + "hasse_diagram", labels, + vec!(Edge(0, 1, ""), Edge(0, 2, ""), + Edge(1, 3, ""), Edge(2, 3, "")))); + assert_eq!(r.unwrap().as_slice(), +r#"digraph hasse_diagram { + N0[label="{x,y}"]; + N1[label="{x}"]; + N2[label="{y}"]; + N3[label="{}"]; + N0 -> N1[label=""]; + N0 -> N2[label=""]; + N1 -> N3[label=""]; + N2 -> N3[label=""]; +} +"#); + } + + #[test] + fn left_aligned_text() { + let labels = AllNodesLabelled(vec!( + "if test {\ + \\l branch1\ + \\l} else {\ + \\l branch2\ + \\l}\ + \\lafterward\ + \\l", + "branch1", + "branch2", + "afterward")); + + let mut writer = MemWriter::new(); + + let g = LabelledGraphWithEscStrs::new( + "syntax_tree", labels, + vec!(Edge(0, 1, "then"), Edge(0, 2, "else"), + Edge(1, 3, ";"), Edge(2, 3, ";" ))); + + render(&g, &mut writer).unwrap(); + let mut r = BufReader::new(writer.get_ref()); + let r = r.read_to_str(); + + assert_eq!(r.unwrap().as_slice(), +r#"digraph syntax_tree { + N0[label="if test {\l branch1\l} else {\l branch2\l}\lafterward\l"]; + N1[label="branch1"]; + N2[label="branch2"]; + N3[label="afterward"]; + N0 -> N1[label="then"]; + N0 -> N2[label="else"]; + N1 -> N3[label=";"]; + N2 -> N3[label=";"]; +} +"#); + } +} diff --git a/src/libgraphviz/maybe_owned_vec.rs b/src/libgraphviz/maybe_owned_vec.rs new file mode 100644 index 0000000000000..5b687e27e9efa --- /dev/null +++ b/src/libgraphviz/maybe_owned_vec.rs @@ -0,0 +1,71 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use std::container::Container; +use std::iter::FromIterator; +use std::slice; + +// Note: Once Dynamically Sized Types (DST) lands, this should be +// replaced with something like `enum Owned<'a, Sized? U>{ Owned(~U), +// Borrowed(&'a U) }`; and then `U` could be instantiated with `[T]` +// or `str`, etc. + +/// MaybeOwnedVector<'a,T> abstracts over `Vec` and `&'a [T]`. +/// +/// Some clients will have a pre-allocated vector ready to hand off in +/// a slice; others will want to create the set on the fly and hand +/// off ownership. +#[deriving(Eq)] +pub enum MaybeOwnedVector<'a,T> { + Growable(Vec), + Borrowed(&'a [T]), +} + +impl<'a,T> MaybeOwnedVector<'a,T> { + pub fn iter(&'a self) -> slice::Items<'a,T> { + match self { + &Growable(ref v) => v.iter(), + &Borrowed(ref v) => v.iter(), + } + } +} + +impl<'a,T> Container for MaybeOwnedVector<'a,T> { + fn len(&self) -> uint { + match self { + &Growable(ref v) => v.len(), + &Borrowed(ref v) => v.len(), + } + } +} + +// The `Vector` trait is provided in the prelude and is implemented on +// both `&'a [T]` and `Vec`, so it makes sense to try to support it +// seamlessly. The other vector related traits from the prelude do +// not appear to be implemented on both `&'a [T]` and `Vec`. (It +// is possible that this is an oversight in some cases.) +// +// In any case, with `Vector` in place, the client can just use +// `as_slice` if they prefer that over `match`. + +impl<'b,T> slice::Vector for MaybeOwnedVector<'b,T> { + fn as_slice<'a>(&'a self) -> &'a [T] { + match self { + &Growable(ref v) => v.as_slice(), + &Borrowed(ref v) => v.as_slice(), + } + } +} + +impl<'a,T> FromIterator for MaybeOwnedVector<'a,T> { + fn from_iter>(iterator: I) -> MaybeOwnedVector { + Growable(FromIterator::from_iter(iterator)) + } +} From 4a122a3185d77cf716dc52d2f54a0595cbccf861 Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Thu, 1 May 2014 15:17:36 +0200 Subject: [PATCH 2/3] Updated maybe_owned_vec with review feedback. Namely: * Added conversion traits both to and from the various vector types, analogous to how `str::MaybeOwned` works with `str::IntoMaybeOwned` and `str::Str`. This led me to add the `FixedLen` variant of `MaybeOwnedVector` for interoperability with `std::slice`. * Revised client example code to make use of `into_maybe_owned` * Added implementations of `Show` and `CloneableVector` for `MaybeOwnedVector`. * As suggested by kballard, added `into_vec` method that is analogous to `CloneableVector::into_owned` except it produces a `Vec` rather than a `~[T]`. --- src/libgraphviz/lib.rs | 27 ++++++--- src/libgraphviz/maybe_owned_vec.rs | 95 ++++++++++++++++++++++++------ 2 files changed, 96 insertions(+), 26 deletions(-) diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs index 986a14a7b573c..dff286ed8111c 100644 --- a/src/libgraphviz/lib.rs +++ b/src/libgraphviz/lib.rs @@ -10,7 +10,7 @@ /*! Generate files suitable for use with [Graphviz](http://www.graphviz.org/) -The `render` function generates output (e.g. a `output.dot` file) for +The `render` function generates output (e.g. an `output.dot` file) for use with [Graphviz](http://www.graphviz.org/) by walking a labelled graph. (Graphviz can then automatically lay out the nodes and edges of the graph, and also optionally render the graph as an image or @@ -37,9 +37,10 @@ pairs of ints, representing the edges (the node set is implicit). Each node label is derived directly from the int representing the node, while the edge labels are all empty strings. -This example also illustrates how to use the `Borrowed` variant of -`MaybeOwnedVector` to return a slice into the edge list, rather than -constructing a copy from scratch. +This example also illustrates how to use `MaybeOwnedVector` to return +an owned vector or a borrowed slice as appropriate: we construct the +node vector from scratch, but borrow the edge list (rather than +constructing a copy of all the edges from scratch). The output from this example renders five nodes, with the first four forming a diamond-shaped acyclic graph and then pointing to the fifth @@ -47,6 +48,7 @@ which is cyclic. ```rust use dot = graphviz; +use graphviz::maybe_owned_vec::IntoMaybeOwnedVector; type Nd = int; type Ed = (int,int); @@ -77,12 +79,12 @@ impl<'a> dot::GraphWalk<'a, Nd, Ed> for Edges { } nodes.sort(); nodes.dedup(); - nodes.move_iter().collect() + nodes.into_maybe_owned() } fn edges(&'a self) -> dot::Edges<'a,Ed> { let &Edges(ref edges) = self; - dot::maybe_owned_vec::Borrowed(edges.as_slice()) + edges.as_slice().into_maybe_owned() } fn source(&self, e: &Ed) -> Nd { let &(s,_) = e; s } @@ -119,9 +121,16 @@ This example also illustrates how to use a type (in this case the edge type) that shares substructure with the graph: the edge type here is a direct reference to the `(source,target)` pair stored in the graph's internal vector (rather than passing around a copy of the pair -itself). Note that in this case, this implies that `fn edges(&'a -self)` must construct a fresh `Vec<&'a (uint,uint)>` from the -`Vec<(uint,uint)>` edges stored in `self`. +itself). Note that this implies that `fn edges(&'a self)` must +construct a fresh `Vec<&'a (uint,uint)>` from the `Vec<(uint,uint)>` +edges stored in `self`. + +Since both the set of nodes and the set of edges are always +constructed from scratch via iterators, we use the `collect()` method +from the `Iterator` trait to collect the nodes and edges into freshly +constructed growable `Vec` values (rather use the `into_maybe_owned` +from the `IntoMaybeOwnedVector` trait as was used in the first example +above). The output from this example renders four nodes that make up the Hasse-diagram for the subsets of the set `{x, y}`. Each edge is diff --git a/src/libgraphviz/maybe_owned_vec.rs b/src/libgraphviz/maybe_owned_vec.rs index 5b687e27e9efa..649bad702eaef 100644 --- a/src/libgraphviz/maybe_owned_vec.rs +++ b/src/libgraphviz/maybe_owned_vec.rs @@ -8,44 +8,69 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use std::container::Container; +use std::fmt; use std::iter::FromIterator; use std::slice; -// Note: Once Dynamically Sized Types (DST) lands, this should be -// replaced with something like `enum Owned<'a, Sized? U>{ Owned(~U), -// Borrowed(&'a U) }`; and then `U` could be instantiated with `[T]` -// or `str`, etc. +// Note 1: It is not clear whether the flexibility of providing both +// the `Growable` and `FixedLen` variants is sufficiently useful. +// Consider restricting to just a two variant enum. -/// MaybeOwnedVector<'a,T> abstracts over `Vec` and `&'a [T]`. +// Note 2: Once Dynamically Sized Types (DST) lands, it might be +// reasonable to replace this with something like `enum MaybeOwned<'a, +// Sized? U>{ Owned(~U), Borrowed(&'a U) }`; and then `U` could be +// instantiated with `[T]` or `str`, etc. Of course, that would imply +// removing the `Growable` variant, which relates to note 1 above. +// Alternatively, we might add `MaybeOwned` for the general case but +// keep some form of `MaybeOwnedVector` to avoid unnecessary copying +// of the contents of `Vec`, since we anticipate that to be a +// frequent way to dynamically construct a vector. + +/// MaybeOwnedVector<'a,T> abstracts over `Vec`, `~[T]`, `&'a [T]`. /// /// Some clients will have a pre-allocated vector ready to hand off in /// a slice; others will want to create the set on the fly and hand -/// off ownership. -#[deriving(Eq)] +/// off ownership, via either `Growable` or `FixedLen` depending on +/// which kind of vector they have constucted. (The `FixedLen` +/// variant is provided for interoperability with `std::slice` methods +/// that return `~[T]`.) pub enum MaybeOwnedVector<'a,T> { Growable(Vec), + FixedLen(~[T]), Borrowed(&'a [T]), } +/// Trait for moving into a `MaybeOwnedVector` +pub trait IntoMaybeOwnedVector<'a,T> { + /// Moves self into a `MaybeOwnedVector` + fn into_maybe_owned(self) -> MaybeOwnedVector<'a,T>; +} + +impl<'a,T> IntoMaybeOwnedVector<'a,T> for Vec { + #[inline] + fn into_maybe_owned(self) -> MaybeOwnedVector<'a,T> { Growable(self) } +} + +impl<'a,T> IntoMaybeOwnedVector<'a,T> for ~[T] { + #[inline] + fn into_maybe_owned(self) -> MaybeOwnedVector<'a,T> { FixedLen(self) } +} + +impl<'a,T> IntoMaybeOwnedVector<'a,T> for &'a [T] { + #[inline] + fn into_maybe_owned(self) -> MaybeOwnedVector<'a,T> { Borrowed(self) } +} + impl<'a,T> MaybeOwnedVector<'a,T> { pub fn iter(&'a self) -> slice::Items<'a,T> { match self { &Growable(ref v) => v.iter(), + &FixedLen(ref v) => v.iter(), &Borrowed(ref v) => v.iter(), } } } -impl<'a,T> Container for MaybeOwnedVector<'a,T> { - fn len(&self) -> uint { - match self { - &Growable(ref v) => v.len(), - &Borrowed(ref v) => v.len(), - } - } -} - // The `Vector` trait is provided in the prelude and is implemented on // both `&'a [T]` and `Vec`, so it makes sense to try to support it // seamlessly. The other vector related traits from the prelude do @@ -59,6 +84,7 @@ impl<'b,T> slice::Vector for MaybeOwnedVector<'b,T> { fn as_slice<'a>(&'a self) -> &'a [T] { match self { &Growable(ref v) => v.as_slice(), + &FixedLen(ref v) => v.as_slice(), &Borrowed(ref v) => v.as_slice(), } } @@ -66,6 +92,41 @@ impl<'b,T> slice::Vector for MaybeOwnedVector<'b,T> { impl<'a,T> FromIterator for MaybeOwnedVector<'a,T> { fn from_iter>(iterator: I) -> MaybeOwnedVector { + // If we are building from scratch, might as well build the + // most flexible variant. Growable(FromIterator::from_iter(iterator)) } } + +impl<'a,T:fmt::Show> fmt::Show for MaybeOwnedVector<'a,T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_slice().fmt(f) + } +} + +impl<'a,T:Clone> CloneableVector for MaybeOwnedVector<'a,T> { + /// Returns a copy of `self`. + fn to_owned(&self) -> ~[T] { + self.as_slice().to_owned() + } + + /// Convert `self` into an owned slice, not making a copy if possible. + fn into_owned(self) -> ~[T] { + match self { + Growable(v) => v.as_slice().to_owned(), + FixedLen(v) => v, + Borrowed(v) => v.to_owned(), + } + } +} + +impl<'a,T:Clone> MaybeOwnedVector<'a,T> { + /// Convert `self` into a growable `Vec`, not making a copy if possible. + pub fn into_vec(self) -> Vec { + match self { + Growable(v) => v, + FixedLen(v) => Vec::from_slice(v.as_slice()), + Borrowed(v) => Vec::from_slice(v), + } + } +} From 67307d4e0864fa9a7de42e1d8bd55f67f251520b Mon Sep 17 00:00:00 2001 From: "Felix S. Klock II" Date: Mon, 5 May 2014 14:31:06 +0200 Subject: [PATCH 3/3] Placate rustdocs testable-by-default code blocks. --- src/libgraphviz/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs index dff286ed8111c..afe29ea2e070d 100644 --- a/src/libgraphviz/lib.rs +++ b/src/libgraphviz/lib.rs @@ -95,7 +95,7 @@ impl<'a> dot::GraphWalk<'a, Nd, Ed> for Edges { Output from first example (in `example1.dot`): -```DOT +```ignore digraph example1 { N0[label="N0"]; N1[label="N1"];