trogdor 1.0.0

Error handling for programming languages.
Documentation
use std::io;

use crate::span::Span;

pub struct Diagnostic {
    pub message: Message,
    pub help: Vec<Message>,
}

impl Diagnostic {
    pub fn new(message: impl ToString, span: impl Into<Option<Span>>) -> Self {
        Self {
            message: Message {
                value: message.to_string(),
                span: span.into(),
            },
            help: Vec::new(),
        }
    }

    pub fn help(&mut self, message: impl ToString, span: impl Into<Option<Span>>) -> &mut Self {
        self.help.push(Message {
            value: message.to_string(),
            span: span.into(),
        });
        self
    }

    pub fn write_to(
        &self,
        output: &mut impl io::Write,
        source: &str,
        tag: &str,
        color: &str,
        help_tag: &str,
        help_color: &str,
    ) -> io::Result<()> {
        self.message.write_to(output, source, tag, color)?;
        for msg in &self.help {
            msg.write_to(output, source, help_tag, help_color)?;
        }
        Ok(())
    }
}

pub struct Message {
    pub value: String,
    pub span: Option<Span>,
}

impl Message {
    pub fn write_to(
        &self,
        output: &mut impl io::Write,
        source: &str,
        tag: &str,
        color: &str,
    ) -> io::Result<()> {
        println!("\n{color}{tag}\x1b[0;1m: {}\x1b[22m", self.value);
        if let Some(span) = self.span {
            let lines = source
                .lines()
                .skip(span.start.ln)
                .take(span.end.ln - span.start.ln + 1)
                .enumerate();
            let len = lines.size_hint().1.map(|l| l - 1);
            for (i, line) in lines {
                let first = i == 0;
                let last = Some(i) == len;
                if first && last {
                    write!(
                        output,
                        "\x1b[2m{} |\x1b[22m {}{color}{}\x1b[0m{}",
                        span.start.ln + i + 1,
                        &line[0..span.start.col].replace("\t", "    "),
                        &line[span.start.col..span.end.col].replace("\t", "    "),
                        &line[span.end.col..].replace("\t", "    ")
                    )?;
                } else if first {
                    write!(
                        output,
                        "\x1b[2m{} |\x1b[22m {}{color}{}\x1b[0m",
                        span.start.ln + i + 1,
                        &line[0..span.start.col].replace("\t", "    "),
                        &line[span.start.col..].replace("\t", "    "),
                    )?;
                } else if last {
                    write!(
                        output,
                        "\x1b[2m{} |\x1b[22m {color}{}\x1b[0m{}",
                        span.start.ln + i + 1,
                        &line[0..span.end.col].replace("\t", "    "),
                        &line[span.end.col..].replace("\t", "    "),
                    )?;
                } else {
                    write!(
                        output,
                        "\x1b[2m{} |\x1b[22m \x1b[1;31m{}\x1b[0m",
                        span.start.ln + i + 1,
                        &line.replace("\t", "    "),
                    )?;
                }
            }
        }
        Ok(())
    }
}