use crate::*;
use alux_ext::ext;
use ambassador::delegatable_trait;
#[delegatable_trait]
pub trait RsyncEndpointAlg: RsyncSorts {
fn endpoint(
&self,
user: Option<String>,
host: Option<String>,
path: impl Into<String>,
) -> Self::Endpoint;
}
#[delegatable_trait]
pub trait RsyncEndpointViewAlg: RsyncSorts {
fn endpoint_user<'a>(&self, endpoint: &'a Self::Endpoint) -> Option<&'a str>;
fn endpoint_host<'a>(&self, endpoint: &'a Self::Endpoint) -> Option<&'a str>;
fn endpoint_path<'a>(&self, endpoint: &'a Self::Endpoint) -> &'a str;
}
pub const RSYNC_SCHEME: &str = "rsync://";
pub const RSYNC_WORD: &str = "rsync";
#[ext(name = RsyncEndpointExt)]
pub impl<This> This
where
This: RsyncEndpointAlg + RsyncEndpointViewAlg,
{
fn read_endpoint(&self, input: &str) -> This::Endpoint {
let input = input.trim();
if let Some(rest) = input.strip_prefix(RSYNC_SCHEME) {
let (authority, path) = rest.split_once('/').unwrap_or((rest, ""));
let (user, host) = split_account(authority);
return self.endpoint(user, Some(host.to_owned()), format!("/{path}"));
}
if !input.contains("://")
&& let Some((authority, path)) = input.split_once(':')
&& !authority.contains('/')
&& !authority.is_empty()
{
let (user, host) = split_account(authority);
return self.endpoint(user, Some(host.to_owned()), path);
}
self.endpoint(None, None, input)
}
fn read_transfer(&self, line: &str) -> Option<(This::Endpoint, This::Endpoint)> {
let mut ends = line
.split_whitespace()
.filter(|word| !word.starts_with('-') && *word != RSYNC_WORD);
let source = ends.next()?;
let destination = ends.next()?;
ends.next()
.is_none()
.then(|| (self.read_endpoint(source), self.read_endpoint(destination)))
}
fn endpoint_remote(&self, endpoint: &This::Endpoint) -> bool {
self.endpoint_host(endpoint).is_some()
}
fn endpoint_text(&self, endpoint: &This::Endpoint) -> String {
let path = self.endpoint_path(endpoint);
let Some(host) = self.endpoint_host(endpoint) else {
return path.to_owned();
};
match self.endpoint_user(endpoint) {
Some(user) => format!("{user}@{host}:{path}"),
None => format!("{host}:{path}"),
}
}
fn endpoint_name<'a>(&self, endpoint: &'a This::Endpoint) -> &'a str {
let path = self.endpoint_path(endpoint);
path.trim_end_matches('/')
.rsplit('/')
.find(|segment| !segment.is_empty())
.unwrap_or(path)
}
}
fn split_account(authority: &str) -> (Option<String>, &str) {
authority
.split_once('@')
.map_or((None, authority), |(user, host)| {
(Some(user.to_owned()), host)
})
}