xrml 0.1.0

eXtensible Rust Markup Language — recursive acronym: HRML (HRML Markup Language) and TRML (TOML-like Markup Language)
Documentation
pub type TemplateResult<T> = Result<T, TemplateError>;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TemplateErrorKind {
    Code,
    Internal,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TemplateErrorPhase {
    Parse,
    Resolve,
    Render,
    Io,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateErrorLocation {
    pub line: usize,
    pub column: usize,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TemplateError {
    pub kind: TemplateErrorKind,
    pub phase: TemplateErrorPhase,
    pub template_path: Option<String>,
    pub directive: Option<String>,
    pub location: Option<TemplateErrorLocation>,
    pub message: String,
}

impl TemplateError {
    fn new(kind: TemplateErrorKind, phase: TemplateErrorPhase, message: impl Into<String>) -> Self {
        Self {
            kind,
            phase,
            template_path: None,
            directive: None,
            location: None,
            message: message.into(),
        }
    }

    pub(crate) fn code(phase: TemplateErrorPhase, message: impl Into<String>) -> Self {
        Self::new(TemplateErrorKind::Code, phase, message)
    }

    pub(crate) fn internal(phase: TemplateErrorPhase, message: impl Into<String>) -> Self {
        Self::new(TemplateErrorKind::Internal, phase, message)
    }

    pub(crate) fn with_template_path(mut self, template_path: impl Into<String>) -> Self {
        self.template_path = Some(template_path.into());
        self
    }

    pub(crate) fn with_directive(mut self, directive: impl Into<String>) -> Self {
        self.directive = Some(directive.into());
        self
    }

    pub(crate) fn with_location(mut self, line: usize, column: usize) -> Self {
        self.location = Some(TemplateErrorLocation { line, column });
        self
    }
}

impl std::fmt::Display for TemplateError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let label = match self.kind {
            TemplateErrorKind::Code => "error",
            TemplateErrorKind::Internal => "internal error",
        };
        let phase = match self.phase {
            TemplateErrorPhase::Parse => "parse",
            TemplateErrorPhase::Resolve => "resolve",
            TemplateErrorPhase::Render => "render",
            TemplateErrorPhase::Io => "io",
        };

        let path = self.template_path.as_deref().unwrap_or("<unknown>");
        let loc = self
            .location
            .as_ref()
            .map(|l| format!("{}:{}", l.line, l.column))
            .unwrap_or_else(|| "?:?".to_string());

        let directive_note = self
            .directive
            .as_ref()
            .map(|d| format!("  in `<?{}?>`\n", d))
            .unwrap_or_default();

        write!(
            f,
            "\n  \u{00d7} {label} [{phase}] in {path}:{loc}\n   {}\n{dir}  \u{2500}\u{2500} help: verify the template matches HRML directive syntax\n",
            self.message,
            dir = directive_note,
            label = label,
            phase = phase,
            path = path,
            loc = loc,
        )
    }
}

impl std::error::Error for TemplateError {}