use std::cmp::Ordering;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq)]
enum Pattern {
Exact(String),
Prefix(String),
}
impl Pattern {
fn parse(input: &str) -> Self {
if let Some(prefix) = input.strip_suffix('*') {
Pattern::Prefix(prefix.to_lowercase())
} else {
Pattern::Exact(input.to_lowercase())
}
}
}
impl Ord for Pattern {
fn cmp(&self, other: &Self) -> Ordering {
match (self, other) {
(Pattern::Exact(s), Pattern::Exact(t)) => s.cmp(t),
(Pattern::Exact(_), Pattern::Prefix(_)) => Ordering::Less,
(Pattern::Prefix(_), Pattern::Exact(_)) => Ordering::Greater,
(Pattern::Prefix(s), Pattern::Prefix(t)) => match s.len().cmp(&t.len()) {
Ordering::Greater => Ordering::Less,
Ordering::Equal => s.cmp(t),
Ordering::Less => Ordering::Greater,
},
}
}
}
impl PartialOrd for Pattern {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct SourceLinkMappings {
mappings: BTreeMap<Pattern, String>,
}
impl<'a> Extend<(&'a str, &'a str)> for SourceLinkMappings {
fn extend<T: IntoIterator<Item = (&'a str, &'a str)>>(&mut self, iter: T) {
self.mappings.extend(
iter.into_iter()
.map(|(k, v)| (Pattern::parse(k), v.to_string())),
)
}
}
impl SourceLinkMappings {
pub fn new<'a, I: IntoIterator<Item = (&'a str, &'a str)>>(iter: I) -> Self {
let mut res = Self::default();
res.extend(iter);
res
}
pub fn is_empty(&self) -> bool {
self.mappings.is_empty()
}
pub fn resolve(&self, path: &str) -> Option<String> {
let path_lower = path.to_lowercase();
for (pattern, target) in &self.mappings {
match &pattern {
Pattern::Exact(value) => {
if value == &path_lower {
return Some(target.clone());
}
}
Pattern::Prefix(value) => {
if path_lower.starts_with(value) {
let replacement = path
.get(value.len()..)
.unwrap_or_default()
.replace('\\', "/");
return Some(target.replace('*', &replacement));
}
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mapping() {
let mappings = vec![
("C:\\src\\*", "http://MyDefaultDomain.com/src/*"),
("C:\\src\\fOO\\*", "http://MyFooDomain.com/src/*"),
(
"C:\\src\\foo\\specific.txt",
"http://MySpecificFoodDomain.com/src/specific.txt",
),
("C:\\src\\bar\\*", "http://MyBarDomain.com/src/*"),
("C:\\src\\file.txt", "https://example.com/file.txt"),
("/home/user/src/*", "https://linux.com/*"),
];
let mappings = SourceLinkMappings::new(mappings);
assert_eq!(mappings.mappings.len(), 6);
assert!(mappings.resolve("c:\\other\\path").is_none());
assert!(mappings.resolve("/home/path").is_none());
assert_eq!(
mappings.resolve("c:\\src\\bAr\\foo\\FiLe.txt").unwrap(),
"http://MyBarDomain.com/src/foo/FiLe.txt"
);
assert_eq!(
mappings.resolve("c:\\src\\foo\\FiLe.txt").unwrap(),
"http://MyFooDomain.com/src/FiLe.txt"
);
assert_eq!(
mappings.resolve("c:\\src\\foo\\SpEcIfIc.txt").unwrap(),
"http://MySpecificFoodDomain.com/src/specific.txt"
);
assert_eq!(
mappings.resolve("c:\\src\\other\\path").unwrap(),
"http://MyDefaultDomain.com/src/other/path"
);
assert_eq!(
mappings.resolve("c:\\src\\other\\path").unwrap(),
"http://MyDefaultDomain.com/src/other/path"
);
assert_eq!(
mappings.resolve("/home/user/src/Path/TO/file.txt").unwrap(),
"https://linux.com/Path/TO/file.txt"
);
}
}