chrony-confile 0.1.0

A full-featured Rust library for parsing, editing, validating, and serializing chrony configuration files
Documentation
//! Source location tracking for directives and parse errors.
//!
//! [`Span`] records the file name, line range, and column range of a parsed directive.
//! It is used in error reporting and debugging to provide precise source locations.

use std::fmt;
use std::path::PathBuf;

/// Represents a location (span) in a source file.
///
/// Records the file name (if known), line range, and column range of a parsed
/// configuration element. Used in error reporting to provide precise source locations.
///
/// # Examples
///
/// ```rust
/// use chrony_confile::Span;
/// use std::path::PathBuf;
///
/// let span = Span::new(Some(PathBuf::from("chrony.conf")), 10, 1, 40);
/// assert_eq!(span.to_string(), "chrony.conf:10:1");
/// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct Span {
    /// The source file path, if known.
    pub file: Option<PathBuf>,
    /// The starting line number (1-indexed).
    pub line_start: usize,
    /// The ending line number (1-indexed).
    pub line_end: usize,
    /// The starting column (1-indexed).
    pub col_start: usize,
    /// The ending column (1-indexed).
    pub col_end: usize,
}

impl Span {
    /// Creates a new span at a single line.
    pub fn new(file: Option<PathBuf>, line: usize, col_start: usize, col_end: usize) -> Self {
        Self { file, line_start: line, line_end: line, col_start, col_end }
    }

    /// Merges two spans from the same file, producing a span that covers both.
    ///
    /// # Panics
    ///
    /// Panics if the two spans are from different files (in debug builds).
    pub fn merge(&self, other: &Self) -> Self {
        debug_assert_eq!(self.file, other.file, "cannot merge spans from different files");
        Self {
            file: self.file.clone(),
            line_start: self.line_start.min(other.line_start),
            line_end: self.line_end.max(other.line_end),
            col_start: if self.line_start <= other.line_start { self.col_start } else { other.col_start },
            col_end: if self.line_end >= other.line_end { self.col_end } else { other.col_end },
        }
    }
}

impl fmt::Display for Span {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.file {
            Some(file) => write!(f, "{}:{}:{}", file.display(), self.line_start, self.col_start),
            None => write!(f, "line {}:{}", self.line_start, self.col_start),
        }
    }
}