use std::{
fs::read_to_string,
path::{Path, PathBuf},
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Location {
pub line: usize,
pub column: usize,
}
impl Location {
pub fn new(line: usize, column: usize) -> Self {
Self { line, column }
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Document {
pub content: String,
pub path: Option<PathBuf>,
}
impl Document {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self, std::io::Error> {
let path = path.as_ref().to_path_buf();
let content = read_to_string(&path)?;
Ok(Self {
content,
path: Some(path),
})
}
pub fn from_content(content: impl Into<String>) -> Self {
Self {
content: content.into(),
path: None,
}
}
pub fn find_section(&self, search: &str) -> Option<Section> {
let cloned_document = self.clone();
let start = cloned_document.content.find(search).unwrap();
let end = start + search.len();
let start = Location {
line: cloned_document.content[..start].matches('\n').count() + 1,
column: start
- cloned_document.content[..start]
.rfind('\n')
.map(|v| v + 1)
.unwrap_or(0),
};
let end = Location {
line: cloned_document.content[..end].matches('\n').count() + 1,
column: end
- cloned_document.content[..end]
.rfind('\n')
.map(|v| v + 1)
.unwrap_or(0),
};
Some(Section {
start,
end,
document: cloned_document,
label: String::new(),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct Section {
pub start: Location,
pub end: Location,
pub document: Document,
pub label: String,
}
impl Section {
pub fn from_span_and_path<P: AsRef<Path>>(
span: &proc_macro2::Span,
path: P,
) -> Result<Self, std::io::Error> {
let document = Document::from_path(path)?;
Ok(Self::from_span_and_document(span, document))
}
pub fn from_span_and_document(span: &proc_macro2::Span, document: Document) -> Self {
let start = Location {
line: span.start().line,
column: span.start().column,
};
let end = Location {
line: span.end().line,
column: span.end().column,
};
Self {
start,
end,
document,
label: "".into(),
}
}
pub fn from_search(search: &str, content: impl Into<String>) -> Option<Section> {
let document = Document::from_content(content);
document.find_section(search)
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = label.into();
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_find_in_document() {
let test_span = Section::from_search("toast", "mod toast {").unwrap();
assert_eq!(test_span.start.line, 1);
assert_eq!(test_span.start.column, 4);
assert_eq!(test_span.end.line, 1);
assert_eq!(test_span.end.column, 9);
let test_span = Section::from_search(
r#"toast"#,
r#"#[attr]
mod toast {
let x = 5;
}"#,
)
.unwrap();
assert_eq!(test_span.start.line, 2);
assert_eq!(test_span.start.column, 4);
assert_eq!(test_span.end.line, 2);
assert_eq!(test_span.end.column, 9);
let test_span = Section::from_search(
r#"toast {
let x"#,
r#"#[attr]
mod toast {
let x = 5;
}"#,
)
.unwrap();
assert_eq!(test_span.start.line, 2);
assert_eq!(test_span.start.column, 4);
assert_eq!(test_span.end.line, 3);
assert_eq!(test_span.end.column, 9);
let test_span = Section::from_search(
r#"mod toast {"#,
r#"#[attr]
mod toast {
let x = 5;
}"#,
)
.unwrap();
assert_eq!(test_span.start.line, 2);
assert_eq!(test_span.start.column, 0);
assert_eq!(test_span.end.line, 2);
assert_eq!(test_span.end.column, 11);
let test_span = Section::from_search(
r#"
mod toast {"#,
r#"#[attr]
mod toast {
let x = 5;
}"#,
)
.unwrap();
assert_eq!(test_span.start.line, 1);
assert_eq!(test_span.start.column, 7);
assert_eq!(test_span.end.line, 2);
assert_eq!(test_span.end.column, 11);
let test_span = Section::from_search(
r#"mod toast {
"#,
r#"#[attr]
mod toast {
let x = 5;
}"#,
)
.unwrap();
assert_eq!(test_span.start.line, 2);
assert_eq!(test_span.start.column, 0);
assert_eq!(test_span.end.line, 3);
assert_eq!(test_span.end.column, 0);
}
}