kind-report 0.1.3

Report module for the kind compiler
Documentation
use std::{fmt::Write, path::PathBuf};

use pathdiff::diff_paths;

use crate::data::*;
use crate::{report::*, RenderConfig};

use super::{CodeBlock, Compact, Renderable, Res};

fn mark_code(
    markers: &mut [&(Point, Point, &Marker)],
    code_line: &str,
    fmt: &mut dyn Write,
) -> std::fmt::Result {
    markers.sort_by(|x, y| x.0.column.cmp(&y.0.column));
    let mut start = 0;

    for marker in markers {
        if start < marker.0.column {
            write!(fmt, "{}", &code_line[start..marker.0.column])?;
            start = marker.0.column;
        }

        let end = if marker.0.line == marker.1.line {
            marker.1.column
        } else {
            code_line.len()
        };

        if start < end {
            write!(fmt, "{{{{{}}}}}", &code_line[start..end])?;
            start = end;
        }
    }

    if start < code_line.len() {
        write!(fmt, "{}", &code_line[start..code_line.len()])?;
    }

    writeln!(fmt)?;
    Ok(())
}

impl Renderable<Compact> for Word {
    fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
        match self {
            Word::Normal(str) => write!(fmt, "{} ", str),
            Word::Dimmed(str) => write!(fmt, "{} ", str),
            Word::White(str) => write!(fmt, "{} ", str),
            Word::Painted(_, str) => write!(fmt, "{} ", str),
        }
    }
}

impl Renderable<Compact> for Subtitle {
    fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
        match self {
            Subtitle::Field(_, phr) => writeln!(fmt, "{}", phr.to_lowercase()),
            Subtitle::Normal(_, phr) => writeln!(fmt, "- {}", phr),
            Subtitle::Bold(_, phr) => writeln!(fmt, "- {}", phr),
            Subtitle::Phrase(_, words) => {
                write!(fmt, "- ")?;
                Renderable::<Compact>::render(words, fmt, cache, config)?;
                writeln!(fmt)
            }
            Subtitle::LineBreak => Ok(()),
        }
    }
}

impl<'a> Renderable<Compact> for Subtitles<'a> {
    fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
        if !self.0.is_empty() {
            writeln!(fmt)?;
        }

        Renderable::<Compact>::render(self.0, fmt, cache, config)
    }
}

impl Renderable<Compact> for Severity {
    fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
        use Severity::*;

        let painted = match self {
            Error => "error:",
            Warning => "warn:",
            Info => "info:",
        };

        write!(fmt, " {} ", painted)
    }
}

impl<'a> Renderable<Compact> for Header<'a> {
    fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
        writeln!(fmt, "{}", &self.title.to_lowercase())
    }
}

impl<'a> Renderable<Compact> for CodeBlock<'a> {
    fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
        writeln!(fmt, "location")?;
        let guide = LineGuide::get(self.code);

        let (lines_set, mut by_line, _) = group_marker_lines(&guide, self.markers);

        let code_lines: Vec<&'a str> = self.code.lines().collect();

        let mut lines: Vec<usize> = lines_set
            .into_iter()
            .filter(|x| *x < code_lines.len())
            .collect();

        lines.sort();

        for line in lines {
            let mut empty_vec = Vec::new();
            let row = by_line.get_mut(&line).unwrap_or(&mut empty_vec);

            let mut inline_markers: Vec<&(Point, Point, &Marker)> =
                row.iter().filter(|x| x.0.line == x.1.line).collect();

            if !inline_markers.is_empty() {
                mark_code(&mut inline_markers, code_lines[line], fmt)?;
            } else {
                writeln!(fmt, "{}", code_lines[line])?;
            }
        }

        Ok(())
    }
}

impl<'a> Renderable<Compact> for Markers<'a> {
    fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
        let groups = group_markers(self.0);
        let current = PathBuf::from(".").canonicalize().unwrap();

        for (ctx, markers) in groups.iter() {
            let (file, code) = cache.fetch(*ctx).unwrap();
            let path = diff_paths(&file.clone(), current.clone()).unwrap_or(file);

            let block = CodeBlock {
                code,
                path: &path,
                markers,
            };

            Renderable::<Compact>::render(&block, fmt, cache, config)?;
        }

        Ok(())
    }
}

impl Renderable<Compact> for DiagnosticFrame {
    fn render(&self, fmt: &mut dyn Write, cache: &dyn FileCache, config: &RenderConfig) -> Res {
        Renderable::<Compact>::render(&self.header(), fmt, cache, config)?;
        Renderable::<Compact>::render(&self.subtitles(), fmt, cache, config)?;
        Renderable::<Compact>::render(&self.markers(), fmt, cache, config)?;

        Ok(())
    }
}

impl Renderable<Compact> for Log {
    fn render(&self, fmt: &mut dyn Write, _: &dyn FileCache, _: &RenderConfig) -> Res {
        match self {
            Log::Compiled(_) => writeln!(fmt, "compiled"),
            Log::Checked(_) => writeln!(fmt, "checked"),
            _ => Ok(()),
        }
    }
}