use std::marker::PhantomData;
use super::output::OutputRendererOptions;
use super::render::Ancestor;
use super::render::GraphRow;
use super::render::LinkLine;
use super::render::NodeLine;
use super::render::PadLine;
use super::render::Renderer;
use crate::pad::pad_lines;
pub struct AsciiLargeRenderer<N, R>
where
R: Renderer<N, Output = GraphRow<N>> + Sized,
{
inner: R,
options: OutputRendererOptions,
extra_pad_line: Option<String>,
_phantom: PhantomData<N>,
}
impl<N, R> AsciiLargeRenderer<N, R>
where
R: Renderer<N, Output = GraphRow<N>> + Sized,
{
pub(crate) fn new(inner: R, options: OutputRendererOptions) -> Self {
AsciiLargeRenderer {
inner,
options,
extra_pad_line: None,
_phantom: PhantomData,
}
}
}
impl<N, R> Renderer<N> for AsciiLargeRenderer<N, R>
where
N: Clone + Eq,
R: Renderer<N, Output = GraphRow<N>> + Sized,
{
type Output = String;
fn width(&self, node: Option<&N>, parents: Option<&Vec<Ancestor<N>>>) -> u64 {
self.inner
.width(node, parents)
.saturating_mul(3)
.saturating_sub(1)
.saturating_add(1)
}
fn reserve(&mut self, node: N) {
self.inner.reserve(node);
}
fn next_row(
&mut self,
node: N,
parents: Vec<Ancestor<N>>,
glyph: String,
message: String,
) -> String {
let line = self.inner.next_row(node, parents, glyph, message);
let mut out = String::new();
let mut message_lines = pad_lines(line.message.lines(), self.options.min_row_height);
let mut need_extra_pad_line = false;
if let Some(extra_pad_line) = self.extra_pad_line.take() {
out.push_str(extra_pad_line.trim_end());
out.push('\n');
}
let mut node_line = String::new();
for (i, entry) in line.node_line.iter().enumerate() {
match entry {
NodeLine::Node => {
if i > 0 {
node_line.push(' ');
}
node_line.push_str(&line.glyph);
node_line.push(' ');
}
NodeLine::Parent => node_line.push_str(if i > 0 { " | " } else { "| " }),
NodeLine::Ancestor => node_line.push_str(if i > 0 { " . " } else { ". " }),
NodeLine::Blank => node_line.push_str(if i > 0 { " " } else { " " }),
}
}
if let Some(msg) = message_lines.next() {
node_line.push(' ');
node_line.push_str(msg);
}
out.push_str(node_line.trim_end());
out.push('\n');
if let Some(link_row) = line.link_line {
let mut top_link_line = String::new();
let mut bot_link_line = String::new();
for (i, cur) in link_row.iter().enumerate() {
if i > 0 {
if cur.intersects(LinkLine::LEFT_MERGE_PARENT) {
top_link_line.push('/');
} else if cur.intersects(LinkLine::LEFT_MERGE_ANCESTOR) {
top_link_line.push('.');
} else if cur.intersects(LinkLine::HORIZ_PARENT) {
top_link_line.push('_');
} else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
top_link_line.push('.');
} else {
top_link_line.push(' ');
}
}
if cur.intersects(LinkLine::VERT_PARENT) {
top_link_line.push('|');
} else if cur.intersects(LinkLine::VERT_ANCESTOR) {
top_link_line.push('.');
} else if cur.intersects(LinkLine::ANY_MERGE) {
top_link_line.push(' ');
} else if cur.intersects(LinkLine::HORIZ_PARENT) {
top_link_line.push('_');
} else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
top_link_line.push('.');
} else {
top_link_line.push(' ');
}
if cur.intersects(LinkLine::RIGHT_MERGE_PARENT) {
top_link_line.push('\\');
} else if cur.intersects(LinkLine::RIGHT_MERGE_ANCESTOR) {
top_link_line.push('.');
} else if cur.intersects(LinkLine::HORIZ_PARENT) {
top_link_line.push('_');
} else if cur.intersects(LinkLine::HORIZ_ANCESTOR) {
top_link_line.push('.');
} else {
top_link_line.push(' ');
}
if i > 0 {
if cur.intersects(LinkLine::LEFT_FORK_PARENT) {
bot_link_line.push('\\');
} else if cur.intersects(LinkLine::LEFT_FORK_ANCESTOR) {
bot_link_line.push('.');
} else {
bot_link_line.push(' ');
}
}
if cur.intersects(LinkLine::VERT_PARENT) {
bot_link_line.push('|');
} else if cur.intersects(LinkLine::VERT_ANCESTOR) {
bot_link_line.push('.');
} else {
bot_link_line.push(' ');
}
if cur.intersects(LinkLine::RIGHT_FORK_PARENT) {
bot_link_line.push('/');
} else if cur.intersects(LinkLine::RIGHT_FORK_ANCESTOR) {
bot_link_line.push('.');
} else {
bot_link_line.push(' ');
}
}
if let Some(msg) = message_lines.next() {
top_link_line.push(' ');
top_link_line.push_str(msg);
}
if let Some(msg) = message_lines.next() {
bot_link_line.push(' ');
bot_link_line.push_str(msg);
}
out.push_str(top_link_line.trim_end());
out.push('\n');
out.push_str(bot_link_line.trim_end());
out.push('\n');
}
if let Some(term_row) = line.term_line {
let term_strs = ["| ", "~ "];
for term_str in term_strs.iter() {
let mut term_line = String::new();
for (i, term) in term_row.iter().enumerate() {
if i > 0 {
term_line.push(' ');
}
if *term {
term_line.push_str(term_str);
} else {
term_line.push_str(match line.pad_lines[i] {
PadLine::Parent => "| ",
PadLine::Ancestor => ". ",
PadLine::Blank => " ",
});
}
}
if let Some(msg) = message_lines.next() {
term_line.push(' ');
term_line.push_str(msg);
}
out.push_str(term_line.trim_end());
out.push('\n');
}
need_extra_pad_line = true;
}
let mut base_pad_line = String::new();
for (i, entry) in line.pad_lines.iter().enumerate() {
base_pad_line.push_str(match entry {
PadLine::Parent => {
if i > 0 {
" | "
} else {
"| "
}
}
PadLine::Ancestor => {
if i > 0 {
" . "
} else {
". "
}
}
PadLine::Blank => {
if i > 0 {
" "
} else {
" "
}
}
});
}
for msg in message_lines {
let mut pad_line = base_pad_line.clone();
pad_line.push(' ');
pad_line.push_str(msg);
out.push_str(pad_line.trim_end());
out.push('\n');
need_extra_pad_line = false;
}
if need_extra_pad_line {
self.extra_pad_line = Some(base_pad_line);
}
out
}
}
#[cfg(test)]
mod tests {
use super::super::test_fixtures;
use super::super::test_fixtures::TestFixture;
use super::super::test_utils::render_string;
use crate::GraphRowRenderer;
fn render(fixture: &TestFixture) -> String {
let mut renderer = GraphRowRenderer::new()
.output()
.with_min_row_height(3)
.build_ascii_large();
render_string(fixture, &mut renderer)
}
#[test]
fn basic() {
assert_eq!(
render(&test_fixtures::BASIC),
r#"
o C
|
|
o B
|
|
o A"#
);
}
#[test]
fn branches_and_merges() {
assert_eq!(
render(&test_fixtures::BRANCHES_AND_MERGES),
r#"
o W
|
|
o V
|\
| \
| o U
| |\
| | \
| | o T
| | |
| | |
| o | S
| |
| |
o | R
| |
| |
o | Q
|\ |
| \ |
| o | P
| |\_|_
| | | \
| | | o O
| | | |
| | | |
| | | o N
| | | |\
| | | | \
| o | | | M
| | | | |
| | | | |
| o | | | L
| | | | |
| | | | |
o | | | | K
| _|__|__|_/
|/ | | |
o | | | J
| | | |
| | | |
o | | | I
| / | |
|/ | |
o | | H
| | |
| | |
o | | G
|\____|_ |
| | \|
| | o F
| | /
| |/
| o E
| |
| |
o | D
| |
| |
o | C
| ___/
|/
o B
|
|
o A"#
);
}
#[test]
fn octopus_branch_and_merge() {
assert_eq!(
render(&test_fixtures::OCTOPUS_BRANCH_AND_MERGE),
r#"
o J
|\___
| \ \
| | o I
| | |
| | |
| o | H
| /|\_|____
|/ | \| \ \
| | | | o G
| | | | |
| | | | |
| | | o | E
| | | | /
| | | |/
| | o | D
| | |\ |
| | | \|
| o | | C
| | _|_/
| |/ |
o | | F
| / |
|/ |
o | B
| ___/
|/
o A"#
);
}
#[test]
fn reserved_column() {
assert_eq!(
render(&test_fixtures::RESERVED_COLUMN),
r#"
o Z
|
|
o Y
|
|
o X
/
/
| o W
| /
|/
o G
|
|
o F
|\
| \
| o E
| |
| |
| o D
|
|
o C
|
|
o B
|
|
o A"#
);
}
#[test]
fn ancestors() {
assert_eq!(
render(&test_fixtures::ANCESTORS),
r#"
o Z
|
|
o Y
/
/
o F
.
.
. o X
. /
./
| o W
| /
|/
o E
.
.
o D
|.
| .
| o C
| .
| .
o . B
| .
|.
o A"#
);
}
#[test]
fn split_parents() {
assert_eq!(
render(&test_fixtures::SPLIT_PARENTS),
r#"
o E
...___/.
. / / .
. o | . D
. / \ | .
./ \| .
| o . C
| | .
| |.
o | B
| ___/
|/
o A"#
);
}
#[test]
fn terminations() {
assert_eq!(
render(&test_fixtures::TERMINATIONS),
r#"
o K
|
|
| o J
| /
|/
o I
/|\
/ | \
| | |
| ~ |
| |
| o H
| |
| |
o | E
| ___/
|/
o D
|
~
o C
|
|
o B
|
~"#
);
}
#[test]
fn long_messages() {
assert_eq!(
render(&test_fixtures::LONG_MESSAGES),
r#"
o F
|\___ very long message 1
| \ \ very long message 2
| | | very long message 3
| | ~
| | very long message 4
| | very long message 5
| | very long message 6
| |
| o E
| |
| |
| o D
| |
| |
o | C
| / long message 1
|/ long message 2
| long message 3
|
o B
|
|
o A
| long message 1
~ long message 2
long message 3"#
);
}
}