chrony_confile/span.rs
1//! Source location tracking for directives and parse errors.
2//!
3//! [`Span`] records the file name, line range, and column range of a parsed directive.
4//! It is used in error reporting and debugging to provide precise source locations.
5
6use std::fmt;
7use std::path::PathBuf;
8
9/// Represents a location (span) in a source file.
10///
11/// Records the file name (if known), line range, and column range of a parsed
12/// configuration element. Used in error reporting to provide precise source locations.
13///
14/// # Examples
15///
16/// ```rust
17/// use chrony_confile::Span;
18/// use std::path::PathBuf;
19///
20/// let span = Span::new(Some(PathBuf::from("chrony.conf")), 10, 1, 40);
21/// assert_eq!(span.to_string(), "chrony.conf:10:1");
22/// ```
23#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
24pub struct Span {
25 /// The source file path, if known.
26 pub file: Option<PathBuf>,
27 /// The starting line number (1-indexed).
28 pub line_start: usize,
29 /// The ending line number (1-indexed).
30 pub line_end: usize,
31 /// The starting column (1-indexed).
32 pub col_start: usize,
33 /// The ending column (1-indexed).
34 pub col_end: usize,
35}
36
37impl Span {
38 /// Creates a new span at a single line.
39 pub fn new(file: Option<PathBuf>, line: usize, col_start: usize, col_end: usize) -> Self {
40 Self { file, line_start: line, line_end: line, col_start, col_end }
41 }
42
43 /// Merges two spans from the same file, producing a span that covers both.
44 ///
45 /// # Panics
46 ///
47 /// Panics if the two spans are from different files (in debug builds).
48 pub fn merge(&self, other: &Self) -> Self {
49 debug_assert_eq!(self.file, other.file, "cannot merge spans from different files");
50 Self {
51 file: self.file.clone(),
52 line_start: self.line_start.min(other.line_start),
53 line_end: self.line_end.max(other.line_end),
54 col_start: if self.line_start <= other.line_start { self.col_start } else { other.col_start },
55 col_end: if self.line_end >= other.line_end { self.col_end } else { other.col_end },
56 }
57 }
58}
59
60impl fmt::Display for Span {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 match &self.file {
63 Some(file) => write!(f, "{}:{}:{}", file.display(), self.line_start, self.col_start),
64 None => write!(f, "line {}:{}", self.line_start, self.col_start),
65 }
66 }
67}