Skip to content

Commit 9b52c5b

Browse files
committed
generalize graphviz library to handle HTML tags and other such things
1 parent f84d53c commit 9b52c5b

File tree

2 files changed

+54
-14
lines changed

2 files changed

+54
-14
lines changed

src/libgraphviz/lib.rs

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,13 @@ pub enum LabelText<'a> {
313313
/// are also the escape sequences `\l` which left-justifies the
314314
/// preceding line and `\r` which right-justifies it.
315315
EscStr(Cow<'a, str>),
316+
317+
/// This uses a graphviz [HTML string label][html]. The string is
318+
/// printed exactly as given, but between `<` and `>`. **No
319+
/// escaping is performed.**
320+
///
321+
/// [html]: http://www.graphviz.org/content/node-shapes#html
322+
HtmlStr(Cow<'a, str>),
316323
}
317324

318325
/// The style for a node or edge.
@@ -453,6 +460,14 @@ pub trait Labeller<'a,N,E> {
453460
/// is a valid DOT identifier.
454461
fn node_id(&'a self, n: &N) -> Id<'a>;
455462

463+
/// Maps `n` to one of the [graphviz `shape` names][1]. If `None`
464+
/// is returned, no `shape` attribute is specified.
465+
///
466+
/// [1]: http://www.graphviz.org/content/node-shapes
467+
fn node_shape(&'a self, _node: &N) -> Option<LabelText<'a>> {
468+
None
469+
}
470+
456471
/// Maps `n` to a label that will be used in the rendered output.
457472
/// The label need not be unique, and may be the empty string; the
458473
/// default is just the output from `node_id`.
@@ -479,6 +494,16 @@ pub trait Labeller<'a,N,E> {
479494
}
480495
}
481496

497+
/// Escape tags in such a way that it is suitable for inclusion in a
498+
/// Graphviz HTML label.
499+
pub fn escape_html(s: &str) -> String {
500+
s
501+
.replace("&", "&amp;")
502+
.replace("\"", "&quot;")
503+
.replace("<", "&lt;")
504+
.replace(">", "&gt;")
505+
}
506+
482507
impl<'a> LabelText<'a> {
483508
pub fn label<S:IntoCow<'a, str>>(s: S) -> LabelText<'a> {
484509
LabelStr(s.into_cow())
@@ -488,6 +513,10 @@ impl<'a> LabelText<'a> {
488513
EscStr(s.into_cow())
489514
}
490515

516+
pub fn html<S:IntoCow<'a, str>>(s: S) -> LabelText<'a> {
517+
HtmlStr(s.into_cow())
518+
}
519+
491520
fn escape_char<F>(c: char, mut f: F) where F: FnMut(char) {
492521
match c {
493522
// not escaping \\, since Graphviz escString needs to
@@ -505,10 +534,12 @@ impl<'a> LabelText<'a> {
505534
}
506535

507536
/// Renders text as string suitable for a label in a .dot file.
508-
pub fn escape(&self) -> String {
537+
/// This includes quotes or suitable delimeters.
538+
pub fn to_dot_string(&self) -> String {
509539
match self {
510-
&LabelStr(ref s) => s.escape_default(),
511-
&EscStr(ref s) => LabelText::escape_str(&s[..]),
540+
&LabelStr(ref s) => format!("\"{}\"", s.escape_default()),
541+
&EscStr(ref s) => format!("\"{}\"", LabelText::escape_str(&s[..])),
542+
&HtmlStr(ref s) => format!("<{}>", s),
512543
}
513544
}
514545

@@ -524,6 +555,7 @@ impl<'a> LabelText<'a> {
524555
} else {
525556
s
526557
},
558+
HtmlStr(s) => s,
527559
}
528560
}
529561

@@ -612,14 +644,15 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
612644
try!(indent(w));
613645
let id = g.node_id(n);
614646

615-
let escaped = &g.node_label(n).escape();
647+
let escaped = &g.node_label(n).to_dot_string();
648+
let shape;
616649

617650
let mut text = vec![id.as_slice()];
618651

619652
if !options.contains(&RenderOption::NoNodeLabels) {
620-
text.push("[label=\"");
653+
text.push("[label=");
621654
text.push(escaped);
622-
text.push("\"]");
655+
text.push("]");
623656
}
624657

625658
let style = g.node_style(n);
@@ -629,12 +662,19 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
629662
text.push("\"]");
630663
}
631664

665+
if let Some(s) = g.node_shape(n) {
666+
shape = s.to_dot_string();
667+
text.push("[shape=");
668+
text.push(&shape);
669+
text.push("]");
670+
}
671+
632672
text.push(";");
633673
try!(writeln(w, &text));
634674
}
635675

636676
for e in g.edges().iter() {
637-
let escaped_label = &g.edge_label(e).escape();
677+
let escaped_label = &g.edge_label(e).to_dot_string();
638678
try!(indent(w));
639679
let source = g.source(e);
640680
let target = g.target(e);
@@ -644,9 +684,9 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
644684
let mut text = vec![source_id.as_slice(), " -> ", target_id.as_slice()];
645685

646686
if !options.contains(&RenderOption::NoEdgeLabels) {
647-
text.push("[label=\"");
687+
text.push("[label=");
648688
text.push(escaped_label);
649-
text.push("\"]");
689+
text.push("]");
650690
}
651691

652692
let style = g.edge_style(e);
@@ -667,7 +707,7 @@ pub fn render_opts<'a, N:Clone+'a, E:Clone+'a, G:Labeller<'a,N,E>+GraphWalk<'a,N
667707
mod tests {
668708
use self::NodeLabels::*;
669709
use super::{Id, Labeller, Nodes, Edges, GraphWalk, render, Style};
670-
use super::LabelText::{self, LabelStr, EscStr};
710+
use super::LabelText::{self, LabelStr, EscStr, HtmlStr};
671711
use std::io;
672712
use std::io::prelude::*;
673713
use std::borrow::IntoCow;
@@ -805,12 +845,12 @@ mod tests {
805845
fn node_id(&'a self, n: &Node) -> Id<'a> { self.graph.node_id(n) }
806846
fn node_label(&'a self, n: &Node) -> LabelText<'a> {
807847
match self.graph.node_label(n) {
808-
LabelStr(s) | EscStr(s) => EscStr(s),
848+
LabelStr(s) | EscStr(s) | HtmlStr(s) => EscStr(s),
809849
}
810850
}
811851
fn edge_label(&'a self, e: & &'a Edge) -> LabelText<'a> {
812852
match self.graph.edge_label(e) {
813-
LabelStr(s) | EscStr(s) => EscStr(s),
853+
LabelStr(s) | EscStr(s) | HtmlStr(s) => EscStr(s),
814854
}
815855
}
816856
}

src/test/compile-fail/feature-gate-rustc-attrs.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212

1313
// Test that `#[rustc_*]` attributes are gated by `rustc_attrs` feature gate.
1414

15-
#[rustc_variance] //~ ERROR the `#[rustc_variance]` attribute is an experimental feature
16-
#[rustc_error] //~ ERROR the `#[rustc_error]` attribute is an experimental feature
15+
#[rustc_variance] //~ ERROR the `#[rustc_variance]` attribute is just used for rustc unit tests and will never be stable
16+
#[rustc_error] //~ ERROR the `#[rustc_error]` attribute is just used for rustc unit tests and will never be stable
1717
#[rustc_move_fragments] //~ ERROR the `#[rustc_move_fragments]` attribute is an experimental feature
1818
#[rustc_foo]
1919
//~^ ERROR unless otherwise specified, attributes with the prefix `rustc_` are reserved for internal compiler diagnostics

0 commit comments

Comments
 (0)