use proc_macro_error::{abort_call_site, ResultExt};
use crate::err_to_diagnostic;
#[derive(Debug, PartialEq, Eq)]
pub struct IncludeLocation<'a> {
pub path: &'a str,
pub range: IncludeRange<'a>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum IncludeRange<'a> {
Full,
Range {
from: Option<usize>,
to: Option<usize>,
},
Anchor { name: &'a str },
}
impl<'a> IncludeRange<'a> {
fn line(num: usize) -> Self {
Self::Range {
from: Some(num),
to: Some(num + 1),
}
}
fn left(num: usize) -> Self {
Self::Range {
from: None,
to: Some(num),
}
}
fn right(num: usize) -> Self {
Self::Range {
from: Some(num),
to: None,
}
}
fn range(from: usize, to: usize) -> Self {
Self::Range {
from: Some(from),
to: Some(to),
}
}
}
impl<'a> IncludeLocation<'a> {
pub fn parse(s: &'a str) -> Self {
let parts = s.split(':').collect::<Vec<_>>();
match &parts[..] {
[path] => Self {
path,
range: IncludeRange::Full,
},
[path, line] => {
let range = if let Ok(num) = line.parse() {
IncludeRange::line(num)
} else {
IncludeRange::Anchor { name: line }
};
Self { path, range }
}
[path, first, second] if first.is_empty() => {
let to = second
.parse()
.map_err(err_to_diagnostic)
.expect_or_abort("unable to parse 'to' include range component");
Self {
path,
range: IncludeRange::left(to),
}
}
[path, first, second] if second.is_empty() => {
let from = first
.parse()
.map_err(err_to_diagnostic)
.expect_or_abort("unable to parse 'from' include range component");
Self {
path,
range: IncludeRange::right(from),
}
}
[path, first, second] => {
let from = first
.parse()
.map_err(err_to_diagnostic)
.expect_or_abort("unable to parse 'from' include range component");
let to = second
.parse()
.map_err(err_to_diagnostic)
.expect_or_abort("unable to parse 'to' include range component");
Self {
path,
range: IncludeRange::range(from, to),
}
}
_ => abort_call_site!("unsupported include range layout"),
}
}
}
#[test]
fn test_parse_include_location() {
let test_cases = [
(
"file.rs",
IncludeLocation {
path: "file.rs",
range: IncludeRange::Full,
},
),
(
"file.rs:2",
IncludeLocation {
path: "file.rs",
range: IncludeRange::Range {
from: Some(2),
to: Some(3),
},
},
),
(
"file.rs::10",
IncludeLocation {
path: "file.rs",
range: IncludeRange::Range {
from: None,
to: Some(10),
},
},
),
(
"file.rs:2:",
IncludeLocation {
path: "file.rs",
range: IncludeRange::Range {
from: Some(2),
to: None,
},
},
),
(
"file.rs:2:10",
IncludeLocation {
path: "file.rs",
range: IncludeRange::Range {
from: Some(2),
to: Some(10),
},
},
),
(
"file.rs:component",
IncludeLocation {
path: "file.rs",
range: IncludeRange::Anchor { name: "component" },
},
),
];
for (path, expected) in test_cases {
let actual = IncludeLocation::parse(path);
assert_eq!(actual, expected);
}
}