pochoir-common 0.12.2

Common utilities for the pochoir template engine
Documentation
use std::{
    error, fmt,
    ops::{Deref, DerefMut, Range},
    path::{Path, PathBuf},
};

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct SpanPosition {
    pub row: usize,
    pub col: usize,
}

/// A structure representing a parsed node along with its corresponding span of text.
///
/// It implements [`Deref`] and [`DerefMut`] to simply use methods and fields of the underlying
/// node through dereferencing.
///
/// Keep in mind that the stored span counts bytes, not characters, so "☀️" has a span of `0..6`.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Spanned<T> {
    node: T,
    file_path: PathBuf,
    span: Range<usize>,
}

impl<T> Spanned<T> {
    pub fn new(node: T) -> Self {
        Self {
            node,
            file_path: PathBuf::default(),
            span: Range::default(),
        }
    }

    pub fn map_spanned<T2, F: Fn(T) -> T2>(self, f: F) -> Spanned<T2> {
        let node = f(self.node);

        Spanned {
            node,
            file_path: self.file_path,
            span: self.span,
        }
    }

    #[must_use]
    pub fn with_span(mut self, span: Range<usize>) -> Self {
        self.span = span;
        self
    }

    pub fn set_span(&mut self, span: Range<usize>) {
        self.span = span;
    }

    pub fn span(&self) -> &Range<usize> {
        &self.span
    }

    #[must_use]
    pub fn with_file_path<P: AsRef<Path>>(mut self, file_path: P) -> Self {
        self.file_path = file_path.as_ref().into();
        self
    }

    pub fn set_file_path<P: AsRef<Path>>(&mut self, file_path: P) {
        self.file_path = file_path.as_ref().into();
    }

    pub fn file_path(&self) -> &Path {
        &self.file_path
    }

    pub fn is_dummy_span(&self) -> bool {
        self.span.end == 0 && self.span.start == 0
    }

    /// Get the row/column position of the span in the source given as argument.
    ///
    /// If the span contains a start/end range and the source is too short, `None` is returned.
    pub fn get_position(&self, contents: &str) -> Option<SpanPosition> {
        let section_before = &contents.get(..self.span.start)?;
        let row = section_before.matches('\n').count() + 1;
        let last_line = section_before
            .rfind('\n')
            .map_or(0, |last_line| last_line + 1);
        let col = section_before[last_line..].chars().count() + 1;

        Some(SpanPosition { row, col })
    }

    pub fn into_inner(self) -> T {
        self.node
    }
}

impl<T> From<T> for Spanned<T> {
    fn from(node: T) -> Self {
        Self::new(node)
    }
}

impl<T> Deref for Spanned<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.node
    }
}

impl<T> DerefMut for Spanned<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.node
    }
}

impl<T: fmt::Display> fmt::Display for Spanned<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.node.fmt(f)
    }
}

impl<T: error::Error> error::Error for Spanned<T> {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        self.node.source()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn utf8() {
        let source = "It is \u{2600}\u{fe0f} today!";
        let span = Spanned::new(()).with_span(12..18);
        assert_eq!(
            span.get_position(source),
            Some(SpanPosition { row: 1, col: 9 })
        );
    }
}