diff --git a/mk/crates.mk b/mk/crates.mk index fc0afa6df62..bc8567d3c95 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 00000000000..986a14a7b57 --- /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 00000000000..5b687e27e9e --- /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)) + } +}