dendron 0.1.4

Generic tree data structure
Documentation
//! Pretty debug printing of a subtree.

use core::fmt::{self, Write as _};

use alloc::vec::Vec;

use crate::node::internal::{DepthFirstLinkTraverser, IntraTreeLink};
use crate::traverse::DftEvent;

/// State of an indent block.
#[derive(Clone, Copy)]
struct IndentedBlockState {
    /// Whether this is the last item.
    is_last_item: bool,
    /// Whether the line is the first line.
    is_first_line: bool,
}

impl IndentedBlockState {
    /// Returns the indent string for the indent type.
    #[inline]
    fn as_str(self) -> &'static str {
        match (self.is_last_item, self.is_first_line) {
            (false, true) => "|-- ",
            (false, false) => "|   ",
            (true, true) => "`-- ",
            (true, false) => "    ",
        }
    }

    /// Returns the leading part of the indent string.
    #[inline]
    fn as_str_leading(self) -> &'static str {
        match (self.is_last_item, self.is_first_line) {
            (false, true) => "|--",
            (false, false) => "|",
            (true, true) => "`--",
            (true, false) => "",
        }
    }

    /// Returns the trailing whitespaces part of the indent string.
    #[inline]
    fn as_str_trailing_spaces(self) -> &'static str {
        match (self.is_last_item, self.is_first_line) {
            (_, true) => " ",
            (false, false) => "   ",
            (true, false) => "    ",
        }
    }

    /// Returns whether the indent string consists of only whitespaces.
    #[inline]
    #[must_use]
    fn is_all_whitespace(self) -> bool {
        self.is_last_item && !self.is_first_line
    }
}

/// State of the line writing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum LineState {
    /// Before any character of the indent is written to the current line.
    BeforeIndent,
    /// Indents are partially written.
    ///
    /// More precisely, trailing whitespaces are not yet written.
    PartialIndent,
    /// Writing content.
    Content,
}

/// Indent writer for the debug printer.
struct IndentWriter<'a, 'b> {
    /// Backend formatter.
    fmt: &'b mut fmt::Formatter<'a>,
    /// State of the line writing.
    line_state: LineState,
    /// Indents.
    indents: Vec<IndentedBlockState>,
    /// The number of pending whitespace-only indents.
    pending_ws_only_indent_level: usize,
}

impl<'a, 'b> IndentWriter<'a, 'b> {
    /// Creates a new `PadAdapter`.
    #[inline]
    fn new(fmt: &'b mut fmt::Formatter<'a>) -> Self {
        Self {
            fmt,
            line_state: LineState::BeforeIndent,
            indents: Vec::new(),
            pending_ws_only_indent_level: 0,
        }
    }

    /// Opens the next item.
    ///
    /// Writes a newline if necessary and prepares to write the next item.
    ///
    /// This should **not** be called for the root item.
    fn open_item(&mut self, is_last_item: bool) -> fmt::Result {
        if self.line_state != LineState::BeforeIndent {
            self.fmt.write_char('\n')?;
            self.line_state = LineState::BeforeIndent;
            self.pending_ws_only_indent_level = 0;
        }
        if let Some(indent) = self.indents.last_mut() {
            indent.is_first_line = false;
        }
        self.indents.push(IndentedBlockState {
            is_last_item,
            is_first_line: true,
        });

        Ok(())
    }

    /// Closes the current item.
    ///
    /// Returns `Ok(())` if an item is successfully closed.
    /// Returns `Err(())` if there are no items that can be closed, i.e. the
    /// current item is the root.
    #[inline]
    fn close_item(&mut self) -> Result<(), ()> {
        match self.indents.pop() {
            Some(_) => Ok(()),
            None => Err(()),
        }
    }

    /// Writes the indent except for the trailing whitespaces.
    fn write_indent_partial(&mut self) -> fmt::Result {
        self.pending_ws_only_indent_level = self
            .indents
            .iter()
            .rev()
            .take_while(|i| i.is_all_whitespace())
            .count();

        let ws_indent_first_level = self.indents.len() - self.pending_ws_only_indent_level;
        let indents_to_print = &self.indents[..ws_indent_first_level];
        if let Some(last) = indents_to_print.last() {
            for indent in &indents_to_print[..(indents_to_print.len() - 1)] {
                self.fmt.write_str(indent.as_str())?;
            }
            self.fmt.write_str(last.as_str_leading())?;
        }

        Ok(())
    }

    /// Writes the rest of the indents which are partially written.
    fn complete_partial_indent(&mut self) -> fmt::Result {
        debug_assert_eq!(self.line_state, LineState::PartialIndent);
        if let Some(last_non_ws_indent_level) =
            (self.indents.len() - self.pending_ws_only_indent_level).checked_sub(1)
        {
            self.fmt
                .write_str(self.indents[last_non_ws_indent_level].as_str_trailing_spaces())?;
        }
        for _ in 0..self.pending_ws_only_indent_level {
            self.fmt.write_str("    ")?;
        }
        self.pending_ws_only_indent_level = 0;

        Ok(())
    }
}

impl fmt::Write for IndentWriter<'_, '_> {
    fn write_str(&mut self, mut s: &str) -> fmt::Result {
        while !s.is_empty() {
            // There remains something to print.

            if self.line_state == LineState::BeforeIndent {
                self.write_indent_partial()?;
                self.line_state = LineState::PartialIndent;
            }

            let (line_end, ends_with_newline) = match s.find('\n') {
                Some(pos) => (pos + 1, true),
                None => (s.len(), false),
            };
            let content = &s[..line_end];
            if !content.is_empty() {
                debug_assert_ne!(
                    self.line_state,
                    LineState::BeforeIndent,
                    "[consistency] indent must be written since there are something to write"
                );
                if self.line_state == LineState::PartialIndent {
                    self.complete_partial_indent()?;
                }
                if let Some(level) = self.indents.last_mut() {
                    level.is_first_line = level.is_first_line && !ends_with_newline;
                }
                self.fmt.write_str(content)?;

                self.line_state = if ends_with_newline {
                    LineState::BeforeIndent
                } else {
                    LineState::Content
                };
            }
            s = &s[line_end..];
        }

        Ok(())
    }
}

/// Tree printer for debugging.
///
/// This is provided mainly for debugging purpose. Node that the output format
/// is not guaranteed to be stable, and any format changes won't be considered
/// as breaking changes.
///
/// For usage and output examples, see
/// [`Node::debug_pretty_print`][`crate::Node::debug_pretty_print`] method.
#[derive(Clone, Copy)]
pub struct DebugPrettyPrint<'a, T> {
    /// Root node link of the (sub)tree to print.
    link: &'a IntraTreeLink<T>,
}

impl<'a, T> DebugPrettyPrint<'a, T> {
    /// Creates a new `DebugPrint` object for the node.
    #[inline]
    pub(crate) fn new(link: &'a IntraTreeLink<T>) -> Self {
        Self { link }
    }
}

impl<'a, T: fmt::Display> fmt::Display for DebugPrettyPrint<'a, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let is_alternate = f.alternate();
        let mut writer = IndentWriter::new(f);
        let mut traverser = self.link.depth_first_traverse();

        // Print the first (root) node.
        traverser.next();
        match self.link.try_borrow_data() {
            Ok(data) => {
                if is_alternate {
                    write!(writer, "{:#}", *data)?
                } else {
                    write!(writer, "{}", *data)?
                }
            }
            Err(_) => writer.write_str("(borrowed)")?,
        }

        // Print the descendants.
        while let Some(link) = prepare_next_node_printing(&mut writer, &mut traverser)? {
            match link.try_borrow_data() {
                Ok(data) => {
                    if is_alternate {
                        write!(writer, "{:#}", *data)?
                    } else {
                        write!(writer, "{}", *data)?
                    }
                }
                Err(_) => writer.write_str("(borrowed)")?,
            }
        }

        Ok(())
    }
}

impl<'a, T: fmt::Debug> fmt::Debug for DebugPrettyPrint<'a, T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let is_alternate = f.alternate();
        let mut writer = IndentWriter::new(f);
        let mut traverser = self.link.depth_first_traverse();

        // Print the first (root) node.
        traverser.next();
        match self.link.try_borrow_data() {
            Ok(data) => {
                if is_alternate {
                    write!(writer, "{:#?}", *data)?
                } else {
                    write!(writer, "{:?}", *data)?
                }
            }
            Err(_) => writer.write_str("(borrowed)")?,
        }

        // Print the descendants.
        while let Some(link) = prepare_next_node_printing(&mut writer, &mut traverser)? {
            match link.try_borrow_data() {
                Ok(data) => {
                    if is_alternate {
                        write!(writer, "{:#?}", *data)?
                    } else {
                        write!(writer, "{:?}", *data)?
                    }
                }
                Err(_) => writer.write_str("(borrowed)")?,
            }
        }

        Ok(())
    }
}

/// Prepares printing of next node.
///
/// Internally, this searches next node open and adjust indent level and prefix.
fn prepare_next_node_printing<T>(
    writer: &mut IndentWriter<'_, '_>,
    traverser: &mut DepthFirstLinkTraverser<'_, T>,
) -> Result<Option<IntraTreeLink<T>>, fmt::Error> {
    for ev in traverser {
        let link = match ev {
            DftEvent::Open(link) => link,
            DftEvent::Close(_) => {
                if writer.close_item().is_ok() {
                    // Closed a non-root node.
                    continue;
                } else {
                    // Closed the root node.
                    break;
                }
            }
        };
        writer.open_item(link.is_last_sibling())?;

        return Ok(Some(link));
    }

    Ok(None)
}