From 9b52c5bfff16a5f95c04a01325c55881f652a802 Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 18 Aug 2015 17:50:56 -0400 Subject: [PATCH] generalize graphviz library to handle HTML tags and other such things --- src/libgraphviz/lib.rs | 64 +++++++++++++++---- .../compile-fail/feature-gate-rustc-attrs.rs | 4 +- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs index 172ae2746b8..4b2c0189b6b 100644 --- a/src/libgraphviz/lib.rs +++ b/src/libgraphviz/lib.rs @@ -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> { + 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("&", "&") + .replace("\"", """) + .replace("<", "<") + .replace(">", ">") +} + impl<'a> LabelText<'a> { pub fn label>(s: S) -> LabelText<'a> { LabelStr(s.into_cow()) @@ -488,6 +513,10 @@ pub fn escaped>(s: S) -> LabelText<'a> { EscStr(s.into_cow()) } + pub fn html>(s: S) -> LabelText<'a> { + HtmlStr(s.into_cow()) + } + fn escape_char(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: &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: &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: &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: &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), } } } diff --git a/src/test/compile-fail/feature-gate-rustc-attrs.rs b/src/test/compile-fail/feature-gate-rustc-attrs.rs index dab44b655fc..125cec6183f 100644 --- a/src/test/compile-fail/feature-gate-rustc-attrs.rs +++ b/src/test/compile-fail/feature-gate-rustc-attrs.rs @@ -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 -- 2.44.0