use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SearchPathElement {
pub provider: String,
pub path: String,
}
impl SearchPathElement {
pub fn new(provider: impl Into<String>, path: impl Into<String>) -> Self {
Self {
provider: provider.into(),
path: path.into(),
}
}
pub fn scheme(&self) -> Option<&str> {
self.path.find("://").map(|idx| &self.path[..idx])
}
pub fn path_without_scheme(&self) -> &str {
if let Some(idx) = self.path.find("://") {
&self.path[idx + 3..]
} else {
&self.path
}
}
}
impl fmt::Display for SearchPathElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "provider={}, path={}", self.provider, self.path)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SearchPathQuery {
pub provider: Option<String>,
pub path: Option<String>,
}
impl SearchPathQuery {
pub fn new() -> Self {
Self::default()
}
pub fn by_provider(provider: impl Into<String>) -> Self {
Self {
provider: Some(provider.into()),
path: None,
}
}
pub fn by_path(path: impl Into<String>) -> Self {
Self {
provider: None,
path: Some(path.into()),
}
}
pub fn by_both(provider: impl Into<String>, path: impl Into<String>) -> Self {
Self {
provider: Some(provider.into()),
path: Some(path.into()),
}
}
pub fn matches(&self, element: &SearchPathElement) -> bool {
let provider_match = self
.provider
.as_ref()
.map(|p| p == &element.provider)
.unwrap_or(true);
let path_match = self
.path
.as_ref()
.map(|p| p == &element.path)
.unwrap_or(true);
if self.provider.is_none() && self.path.is_none() {
return false;
}
provider_match && path_match
}
}
#[derive(Clone, Debug, Default)]
pub struct ConfigSearchPath {
elements: Vec<SearchPathElement>,
}
impl ConfigSearchPath {
pub fn new() -> Self {
Self::default()
}
pub fn from_elements(elements: Vec<SearchPathElement>) -> Self {
Self { elements }
}
pub fn get_path(&self) -> &[SearchPathElement] {
&self.elements
}
pub fn get_path_mut(&mut self) -> &mut Vec<SearchPathElement> {
&mut self.elements
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn find_first_match(&self, query: &SearchPathQuery) -> i32 {
for (idx, element) in self.elements.iter().enumerate() {
if query.matches(element) {
return idx as i32;
}
}
-1
}
pub fn find_last_match(&self, query: &SearchPathQuery) -> i32 {
for (idx, element) in self.elements.iter().enumerate().rev() {
if query.matches(element) {
return idx as i32;
}
}
-1
}
pub fn append(&mut self, provider: impl Into<String>, path: impl Into<String>) {
self.elements.push(SearchPathElement::new(provider, path));
}
pub fn append_after(
&mut self,
provider: impl Into<String>,
path: impl Into<String>,
anchor: &SearchPathQuery,
) {
let element = SearchPathElement::new(provider, path);
let idx = self.find_last_match(anchor);
if idx >= 0 {
self.elements.insert((idx + 1) as usize, element);
} else {
self.elements.push(element);
}
}
pub fn prepend(&mut self, provider: impl Into<String>, path: impl Into<String>) {
self.elements
.insert(0, SearchPathElement::new(provider, path));
}
pub fn prepend_before(
&mut self,
provider: impl Into<String>,
path: impl Into<String>,
anchor: &SearchPathQuery,
) {
let element = SearchPathElement::new(provider, path);
let idx = self.find_first_match(anchor);
if idx > 0 {
self.elements.insert(idx as usize, element);
} else {
self.elements.insert(0, element);
}
}
pub fn remove(&mut self, query: &SearchPathQuery) -> usize {
let len_before = self.elements.len();
self.elements.retain(|e| !query.matches(e));
len_before - self.elements.len()
}
pub fn clear(&mut self) {
self.elements.clear();
}
pub fn contains(&self, query: &SearchPathQuery) -> bool {
self.find_first_match(query) >= 0
}
pub fn get(&self, index: usize) -> Option<&SearchPathElement> {
self.elements.get(index)
}
pub fn iter(&self) -> impl Iterator<Item = &SearchPathElement> {
self.elements.iter()
}
}
impl IntoIterator for ConfigSearchPath {
type Item = SearchPathElement;
type IntoIter = std::vec::IntoIter<SearchPathElement>;
fn into_iter(self) -> Self::IntoIter {
self.elements.into_iter()
}
}
impl<'a> IntoIterator for &'a ConfigSearchPath {
type Item = &'a SearchPathElement;
type IntoIter = std::slice::Iter<'a, SearchPathElement>;
fn into_iter(self) -> Self::IntoIter {
self.elements.iter()
}
}
impl fmt::Display for ConfigSearchPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[")?;
for (idx, element) in self.elements.iter().enumerate() {
if idx > 0 {
write!(f, ", ")?;
}
write!(f, "{}", element)?;
}
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_search_path_element_new() {
let elem = SearchPathElement::new("hydra", "file://conf");
assert_eq!(elem.provider, "hydra");
assert_eq!(elem.path, "file://conf");
}
#[test]
fn test_search_path_element_scheme() {
let elem = SearchPathElement::new("hydra", "file://conf");
assert_eq!(elem.scheme(), Some("file"));
let elem2 = SearchPathElement::new("hydra", "pkg://myapp.conf");
assert_eq!(elem2.scheme(), Some("pkg"));
let elem3 = SearchPathElement::new("hydra", "conf");
assert_eq!(elem3.scheme(), None);
}
#[test]
fn test_search_path_element_path_without_scheme() {
let elem = SearchPathElement::new("hydra", "file://conf");
assert_eq!(elem.path_without_scheme(), "conf");
let elem2 = SearchPathElement::new("hydra", "pkg://myapp.conf");
assert_eq!(elem2.path_without_scheme(), "myapp.conf");
let elem3 = SearchPathElement::new("hydra", "conf");
assert_eq!(elem3.path_without_scheme(), "conf");
}
#[test]
fn test_search_path_query_matches() {
let elem = SearchPathElement::new("hydra", "file://conf");
let query = SearchPathQuery::by_provider("hydra");
assert!(query.matches(&elem));
let query = SearchPathQuery::by_path("file://conf");
assert!(query.matches(&elem));
let query = SearchPathQuery::by_both("hydra", "file://conf");
assert!(query.matches(&elem));
let query = SearchPathQuery::by_provider("other");
assert!(!query.matches(&elem));
let query = SearchPathQuery::by_path("pkg://conf");
assert!(!query.matches(&elem));
let query = SearchPathQuery::new();
assert!(!query.matches(&elem));
}
#[test]
fn test_config_search_path_append() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf1");
sp.append("main", "file://conf2");
assert_eq!(sp.len(), 2);
assert_eq!(sp.get(0).unwrap().provider, "hydra");
assert_eq!(sp.get(1).unwrap().provider, "main");
}
#[test]
fn test_config_search_path_prepend() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf1");
sp.prepend("main", "file://conf2");
assert_eq!(sp.len(), 2);
assert_eq!(sp.get(0).unwrap().provider, "main");
assert_eq!(sp.get(1).unwrap().provider, "hydra");
}
#[test]
fn test_config_search_path_find() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf1");
sp.append("main", "file://conf2");
sp.append("hydra", "file://conf3");
let query = SearchPathQuery::by_provider("hydra");
assert_eq!(sp.find_first_match(&query), 0);
assert_eq!(sp.find_last_match(&query), 2);
let query = SearchPathQuery::by_path("file://conf2");
assert_eq!(sp.find_first_match(&query), 1);
let query = SearchPathQuery::by_provider("other");
assert_eq!(sp.find_first_match(&query), -1);
}
#[test]
fn test_config_search_path_append_after() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf1");
sp.append("main", "file://conf2");
let anchor = SearchPathQuery::by_provider("hydra");
sp.append_after("plugin", "file://plugin_conf", &anchor);
assert_eq!(sp.len(), 3);
assert_eq!(sp.get(0).unwrap().provider, "hydra");
assert_eq!(sp.get(1).unwrap().provider, "plugin");
assert_eq!(sp.get(2).unwrap().provider, "main");
}
#[test]
fn test_config_search_path_prepend_before() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf1");
sp.append("main", "file://conf2");
let anchor = SearchPathQuery::by_provider("main");
sp.prepend_before("plugin", "file://plugin_conf", &anchor);
assert_eq!(sp.len(), 3);
assert_eq!(sp.get(0).unwrap().provider, "hydra");
assert_eq!(sp.get(1).unwrap().provider, "plugin");
assert_eq!(sp.get(2).unwrap().provider, "main");
}
#[test]
fn test_config_search_path_remove() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf1");
sp.append("main", "file://conf2");
sp.append("hydra", "file://conf3");
let query = SearchPathQuery::by_provider("hydra");
let removed = sp.remove(&query);
assert_eq!(removed, 2);
assert_eq!(sp.len(), 1);
assert_eq!(sp.get(0).unwrap().provider, "main");
}
#[test]
fn test_config_search_path_contains() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf1");
assert!(sp.contains(&SearchPathQuery::by_provider("hydra")));
assert!(!sp.contains(&SearchPathQuery::by_provider("other")));
}
#[test]
fn test_config_search_path_display() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf");
let s = format!("{}", sp);
assert!(s.contains("provider=hydra"));
assert!(s.contains("path=file://conf"));
}
#[test]
fn test_config_search_path_iter() {
let mut sp = ConfigSearchPath::new();
sp.append("hydra", "file://conf1");
sp.append("main", "file://conf2");
let providers: Vec<_> = sp.iter().map(|e| e.provider.as_str()).collect();
assert_eq!(providers, vec!["hydra", "main"]);
}
}