]> git.lizzy.rs Git - rust.git/commitdiff
generalize graphviz library to handle HTML tags and other such things
authorNiko Matsakis <niko@alum.mit.edu>
Tue, 18 Aug 2015 21:50:56 +0000 (17:50 -0400)
committerNiko Matsakis <niko@alum.mit.edu>
Sun, 6 Sep 2015 11:27:22 +0000 (07:27 -0400)
src/libgraphviz/lib.rs
src/test/compile-fail/feature-gate-rustc-attrs.rs

index 172ae2746b87dd194220fe3c90acd970aa218c54..4b2c0189b6b46fb018e9d24b69277e2439f7acb8 100644 (file)
@@ -313,6 +313,13 @@ pub enum LabelText<'a> {
     /// are also the escape sequences `\l` which left-justifies the
     /// preceding line and `\r` which right-justifies it.
     EscStr(Cow<'a, str>),
+
+    /// This uses a graphviz [HTML string label][html]. The string is
+    /// printed exactly as given, but between `<` and `>`. **No
+    /// escaping is performed.**
+    ///
+    /// [html]: http://www.graphviz.org/content/node-shapes#html
+    HtmlStr(Cow<'a, str>),
 }
 
 /// The style for a node or edge.
@@ -453,6 +460,14 @@ pub trait Labeller<'a,N,E> {
     /// is a valid DOT identifier.
     fn node_id(&'a self, n: &N) -> Id<'a>;
 
+    /// Maps `n` to one of the [graphviz `shape` names][1]. If `None`
+    /// is returned, no `shape` attribute is specified.
+    ///
+    /// [1]: http://www.graphviz.org/content/node-shapes
+    fn node_shape(&'a self, _node: &N) -> Option<LabelText<'a>> {
+        None
+    }
+
     /// 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`.
@@ -479,6 +494,16 @@ fn edge_style(&'a self, _e: &E) -> Style {
     }
 }
 
+/// Escape tags in such a way that it is suitable for inclusion in a
+/// Graphviz HTML label.
+pub fn escape_html(s: &str) -> String {
+    s
+        .replace("&", "&amp;")
+        .replace("\"", "&quot;")
+        .replace("<", "&lt;")
+        .replace(">", "&gt;")
+}
+
 impl<'a> LabelText<'a> {
     pub fn label<S:IntoCow<'a, str>>(s: S) -> LabelText<'a> {
         LabelStr(s.into_cow())
@@ -488,6 +513,10 @@ pub fn escaped<S:IntoCow<'a, str>>(s: S) -> LabelText<'a> {
         EscStr(s.into_cow())
     }
 
+    pub fn html<S:IntoCow<'a, str>>(s: S) -> LabelText<'a> {
+        HtmlStr(s.into_cow())
+    }
+
     fn escape_char<F>(c: char, mut f: F) where F: FnMut(char) {
         match c {
             // not escaping \\, since Graphviz escString needs to
@@ -505,10 +534,12 @@ fn escape_str(s: &str) -> String {
     }
 
     /// Renders text as string suitable for a label in a .dot file.
-    pub fn escape(&self) -> String {
+    /// This includes quotes or suitable delimeters.
+    pub fn to_dot_string(&self) -> String {
         match self {
-            &LabelStr(ref s) => s.escape_default(),
-            &EscStr(ref s) => LabelText::escape_str(&s[..]),
+            &LabelStr(ref s) => format!("\"{}\"", s.escape_default()),
+            &EscStr(ref s) => format!("\"{}\"", LabelText::escape_str(&s[..])),
+            &HtmlStr(ref s) => format!("<{}>", s),
         }
     }
 
@@ -524,6 +555,7 @@ fn pre_escaped_content(self) -> Cow<'a, str> {
             } else {
                 s
             },
+            HtmlStr(s) => s,
         }
     }
 
@@ -612,14 +644,15 @@ fn indent<W:Write>(w: &mut W) -> io::Result<()> {
         try!(indent(w));
         let id = g.node_id(n);
 
-        let escaped = &g.node_label(n).escape();
+        let escaped = &g.node_label(n).to_dot_string();
+        let shape;
 
         let mut text = vec![id.as_slice()];
 
         if !options.contains(&RenderOption::NoNodeLabels) {
-            text.push("[label=\"");
+            text.push("[label=");
             text.push(escaped);
-            text.push("\"]");
+            text.push("]");
         }
 
         let style = g.node_style(n);
@@ -629,12 +662,19 @@ fn indent<W:Write>(w: &mut W) -> io::Result<()> {
             text.push("\"]");
         }
 
+        if let Some(s) = g.node_shape(n) {
+            shape = s.to_dot_string();
+            text.push("[shape=");
+            text.push(&shape);
+            text.push("]");
+        }
+
         text.push(";");
         try!(writeln(w, &text));
     }
 
     for e in g.edges().iter() {
-        let escaped_label = &g.edge_label(e).escape();
+        let escaped_label = &g.edge_label(e).to_dot_string();
         try!(indent(w));
         let source = g.source(e);
         let target = g.target(e);
@@ -644,9 +684,9 @@ fn indent<W:Write>(w: &mut W) -> io::Result<()> {
         let mut text = vec![source_id.as_slice(), " -> ", target_id.as_slice()];
 
         if !options.contains(&RenderOption::NoEdgeLabels) {
-            text.push("[label=\"");
+            text.push("[label=");
             text.push(escaped_label);
-            text.push("\"]");
+            text.push("]");
         }
 
         let style = g.edge_style(e);
@@ -667,7 +707,7 @@ fn indent<W:Write>(w: &mut W) -> io::Result<()> {
 mod tests {
     use self::NodeLabels::*;
     use super::{Id, Labeller, Nodes, Edges, GraphWalk, render, Style};
-    use super::LabelText::{self, LabelStr, EscStr};
+    use super::LabelText::{self, LabelStr, EscStr, HtmlStr};
     use std::io;
     use std::io::prelude::*;
     use std::borrow::IntoCow;
@@ -805,12 +845,12 @@ 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),
+                LabelStr(s) | EscStr(s) | HtmlStr(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),
+                LabelStr(s) | EscStr(s) | HtmlStr(s) => EscStr(s),
             }
         }
     }
index dab44b655fce82a5e788a818c202c5f8dae7dc20..125cec6183f6d0022fa4067f9f602b3728926efb 100644 (file)
@@ -12,8 +12,8 @@
 
 // Test that `#[rustc_*]` attributes are gated by `rustc_attrs` feature gate.
 
-#[rustc_variance] //~ ERROR the `#[rustc_variance]` attribute is an experimental feature
-#[rustc_error] //~ ERROR the `#[rustc_error]` attribute is an experimental feature
+#[rustc_variance] //~ ERROR the `#[rustc_variance]` attribute is just used for rustc unit tests and will never be stable
+#[rustc_error] //~ ERROR the `#[rustc_error]` attribute is just used for rustc unit tests and will never be stable
 #[rustc_move_fragments] //~ ERROR the `#[rustc_move_fragments]` attribute is an experimental feature
 #[rustc_foo]
 //~^ ERROR unless otherwise specified, attributes with the prefix `rustc_` are reserved for internal compiler diagnostics