use std::collections::HashMap;
use super::container::ReadSeek;
use crate::{Error, Result};
pub type OpenSourceFn = fn(uri: &str) -> Result<Box<dyn ReadSeek>>;
#[derive(Default)]
pub struct SourceRegistry {
schemes: HashMap<String, OpenSourceFn>,
}
impl SourceRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register(&mut self, scheme: &str, opener: OpenSourceFn) {
self.schemes.insert(scheme.to_ascii_lowercase(), opener);
}
pub fn open(&self, uri_str: &str) -> Result<Box<dyn ReadSeek>> {
let (scheme, _) = split_scheme(uri_str);
let scheme = scheme.to_ascii_lowercase();
if let Some(opener) = self.schemes.get(&scheme) {
return opener(uri_str);
}
if let Some(opener) = self.schemes.get("file") {
return opener(uri_str);
}
Err(Error::Unsupported(format!(
"no source driver for scheme '{scheme}' (URI: {uri_str})"
)))
}
pub fn schemes(&self) -> impl Iterator<Item = &str> {
self.schemes.keys().map(|s| s.as_str())
}
}
pub(crate) fn split_scheme(uri: &str) -> (&str, &str) {
if let Some(idx) = uri.find(':') {
let (scheme, rest) = uri.split_at(idx);
let rest = &rest[1..];
if scheme.len() == 1 && scheme.chars().next().unwrap().is_ascii_alphabetic() {
return ("file", uri);
}
let valid = !scheme.is_empty()
&& scheme.chars().next().unwrap().is_ascii_alphabetic()
&& scheme
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '+' | '-' | '.'));
if !valid {
return ("file", uri);
}
let rest = rest.strip_prefix("//").unwrap_or(rest);
return (scheme, rest);
}
("file", uri)
}