use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceLocation {
pub repository: PathBuf,
pub file_path: PathBuf,
pub line: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub git_ref: Option<String>,
}
impl SourceLocation {
pub fn new(repository: impl Into<PathBuf>, file_path: impl Into<PathBuf>, line: usize) -> Self {
Self {
repository: repository.into(),
file_path: file_path.into(),
line,
git_ref: None,
}
}
pub fn with_git_ref(
repository: impl Into<PathBuf>,
file_path: impl Into<PathBuf>,
line: usize,
git_ref: impl Into<String>,
) -> Self {
Self {
repository: repository.into(),
file_path: file_path.into(),
line,
git_ref: Some(git_ref.into()),
}
}
pub fn full_path(&self) -> PathBuf {
self.repository.join(&self.file_path)
}
pub fn display(&self) -> String {
format!("{}:{}", self.file_path.display(), self.line)
}
pub fn display_full(&self) -> String {
if let Some(ref git_ref) = self.git_ref {
format!(
"{}:{}:{} (at {})",
self.repository.display(),
self.file_path.display(),
self.line,
git_ref
)
} else {
format!(
"{}:{}:{}",
self.repository.display(),
self.file_path.display(),
self.line
)
}
}
}
impl fmt::Display for SourceLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.display())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_source_location_display() {
let loc = SourceLocation::new("/repo", "docs/SOL-001.md", 5);
assert_eq!(loc.display(), "docs/SOL-001.md:5");
}
#[test]
fn test_source_location_full_path() {
let loc = SourceLocation::new("/repo", "docs/SOL-001.md", 5);
assert_eq!(loc.full_path(), PathBuf::from("/repo/docs/SOL-001.md"));
}
#[test]
fn test_source_location_with_git_ref() {
let loc = SourceLocation::with_git_ref("/repo", "docs/SOL-001.md", 5, "main");
assert_eq!(loc.git_ref, Some("main".to_string()));
}
}