rshtml_core 0.5.0

RsHtml: A Template Engine for Seamless HTML and Rust Integration.
Documentation
use crate::position::Position;
use std::{
    collections::HashMap,
    path::{Path, PathBuf},
};

pub struct Diagnostic {
    pub sources: HashMap<PathBuf, String>,
}

impl Diagnostic {
    pub fn new(sources: HashMap<PathBuf, String>) -> Self {
        Self { sources }
    }

    pub fn message(
        &self,
        file: &Path,
        position: &Position,
        title: &str,
        lines: &[usize],
        info: &str,
        name_len: usize,
    ) -> String {
        let (lines, source_snippet, left_pad) = if lines.is_empty() {
            (
                &[(position.0).0] as &[usize],
                self.source_first_line(file, position).unwrap_or_default(),
                ((position.0).0).to_string().len(),
            )
        } else {
            (
                lines,
                self.extract_source_snippet(file, position)
                    .unwrap_or_default(),
                ((position.1).0).to_string().len(),
            )
        };

        let lp = " ".repeat(left_pad);
        let file_info = self.files_to_info(file, position);
        let info = if info.is_empty() {
            "".to_string()
        } else {
            let hyphen = "-".repeat(name_len);
            format!("{lp} | {hyphen} {info}\n")
        };

        let mut source = String::new();

        let first_line = (position.0).0;

        for (i, source_line) in source_snippet.lines().enumerate() {
            let current_line = first_line + i;
            let lp = left_pad - current_line.to_string().len();
            let lp = " ".repeat(lp);

            let source_line = source_line
                .trim_matches(|c| c == ' ' || c == '\t')
                .to_string();

            if lines.contains(&current_line) {
                source.push_str(format!("{lp}{current_line} | {source_line}\n").as_str());
            }
        }

        let title = if !title.is_empty() {
            &format!("{title}\n")
        } else {
            ""
        };

        let lp = " ".repeat(left_pad);
        format!("{title}{lp} --> {file_info}\n{lp} |\n{source}{info}{lp} |",)
    }

    pub fn warning(
        &self,
        file: &Path,
        position: &Position,
        title: &str,
        lines: &[usize],
        info: &str,
        name_len: usize,
    ) -> String {
        let yellow = "\x1b[33m";
        let reset = "\x1b[0m";

        let warn = self.message(file, position, title, lines, info, name_len);

        format!("{yellow}warning:{reset} {warn}")
    }

    pub fn caution(
        &self,
        file: &Path,
        position: &Position,
        title: &str,
        lines: &[usize],
        info: &str,
        name_len: usize,
    ) -> String {
        let magenta = "\x1b[1;35m";
        let reset = "\x1b[0m";

        let cau = self.message(file, position, title, lines, info, name_len);

        format!("{magenta}caution:{reset} {cau}")
    }

    fn extract_source_snippet(&self, file: &Path, position: &Position) -> Option<&str> {
        let source = self.sources.get(file)?;
        let (start, end) = position.byte_positions();
        Some(&source[start..end])
    }

    fn source_first_line(&self, file: &Path, position: &Position) -> Option<&str> {
        self.sources
            .get(file)?
            .lines()
            .nth((position.0).0.saturating_sub(1))
    }

    fn files_to_info(&self, file: &Path, position: &Position) -> String {
        position.as_info(file)
    }
}

pub enum Level {
    Warning,
    Caution,
}