src/remote.rs
use core::{convert::Infallible, fmt, str::FromStr};
use std::{
path::{Path, PathBuf},
process::Command,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("invalid remote: must be of the form '<user>@<host>:<path>'")]
pub struct ParseRemoteError;
#[derive(Clone, Debug)]
pub struct Remote<'a> {
pub user: &'a str,
pub host: &'a str,
pub path: &'a Path,
}
impl<'a> TryFrom<&'a str> for Remote<'a> {
type Error = ParseRemoteError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
let (userhost, path) = value.split_once(':').unwrap_or_else(|| (value, "."));
let (user, host) = userhost.split_once('@').ok_or(ParseRemoteError)?;
if user.is_empty() || host.is_empty() {
return Err(ParseRemoteError);
}
Ok(Remote {
user,
host,
path: Path::new(path),
})
}
}
impl fmt::Display for Remote<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}@{}:{}", self.user, self.host, self.path.display())
}
}
#[derive(Clone, Debug)]
pub struct RemoteBuf {
pub user: String,
pub host: String,
pub path: PathBuf,
}
impl FromStr for RemoteBuf {
type Err = ParseRemoteError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Remote::try_from(s).map(Into::into)
}
}
impl<'a> TryFrom<&'a str> for RemoteBuf {
type Error = ParseRemoteError;
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
value.parse()
}
}
impl From<Remote<'_>> for RemoteBuf {
fn from(value: Remote<'_>) -> Self {
Self {
user: value.user.to_owned(),
host: value.host.to_owned(),
path: value.path.to_path_buf(),
}
}
}
impl From<&Remote<'_>> for RemoteBuf {
fn from(value: &Remote<'_>) -> Self {
Self {
user: value.user.to_owned(),
host: value.host.to_owned(),
path: value.path.to_path_buf(),
}
}
}
impl<'a> From<&'a RemoteBuf> for Remote<'a> {
fn from(value: &'a RemoteBuf) -> Self {
Remote {
user: &value.user,
host: &value.host,
path: &value.path,
}
}
}
impl fmt::Display for RemoteBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Remote::from(self).fmt(f)
}
}
impl RemoteBuf {
pub fn join(&self, value: impl AsRef<Path>) -> RemoteBuf {
RemoteBuf {
user: self.user.clone(),
host: self.host.clone(),
path: self.path.join(value),
}
}
}
#[derive(Clone, Debug)]
pub enum RemoteOrLocal<'a> {
Local(&'a Path),
Remote(Remote<'a>),
}
impl<'a> From<&'a str> for RemoteOrLocal<'a> {
fn from(value: &'a str) -> Self {
match Remote::try_from(value) {
Ok(remote) => Self::Remote(remote),
Err(_) => Self::Local(Path::new(value)),
}
}
}
impl<'a> From<&'a Path> for RemoteOrLocal<'a> {
fn from(value: &'a Path) -> Self {
Self::Local(value)
}
}
impl fmt::Display for RemoteOrLocal<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Local(path) => write!(f, "{}", path.display()),
Self::Remote(remote) => remote.fmt(f),
}
}
}
impl RemoteOrLocal<'_> {
pub fn join(&self, value: impl AsRef<Path>) -> RemoteOrLocalBuf {
match self {
Self::Local(local) => RemoteOrLocalBuf::Local(local.join(value)),
Self::Remote(Remote { user, host, path }) => RemoteOrLocalBuf::Remote(RemoteBuf {
user: (*user).to_owned(),
host: (*host).to_owned(),
path: path.join(value),
}),
}
}
pub fn shell_command(&self, script: &str) -> Command {
match self {
RemoteOrLocal::Local(_) => {
let mut cmd = Command::new("nu");
cmd.arg("-c").arg(script);
cmd
}
RemoteOrLocal::Remote(Remote { user, host, .. }) => {
let mut cmd = Command::new("ssh");
cmd.arg(format!("{user}@{host}")).arg(script);
cmd
}
}
}
pub fn path(&self) -> &Path {
match self {
Self::Local(local) => local,
Self::Remote(Remote { path, .. }) => path,
}
}
}
#[derive(Clone, Debug)]
pub enum RemoteOrLocalBuf {
Local(PathBuf),
Remote(RemoteBuf),
}
impl From<&str> for RemoteOrLocalBuf {
fn from(value: &str) -> Self {
match RemoteBuf::from_str(value) {
Ok(remote) => Self::Remote(remote),
Err(_) => Self::Local(PathBuf::from(value)),
}
}
}
impl FromStr for RemoteOrLocalBuf {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s))
}
}
impl From<PathBuf> for RemoteOrLocalBuf {
fn from(value: PathBuf) -> Self {
Self::Local(value)
}
}
impl From<RemoteBuf> for RemoteOrLocalBuf {
fn from(value: RemoteBuf) -> Self {
Self::Remote(value)
}
}
impl From<RemoteOrLocal<'_>> for RemoteOrLocalBuf {
fn from(value: RemoteOrLocal<'_>) -> Self {
match value {
RemoteOrLocal::Local(local) => Self::Local(local.to_path_buf()),
RemoteOrLocal::Remote(remote) => Self::Remote(remote.into()),
}
}
}
impl From<&RemoteOrLocal<'_>> for RemoteOrLocalBuf {
fn from(value: &RemoteOrLocal<'_>) -> Self {
match value {
RemoteOrLocal::Local(local) => Self::Local(local.to_path_buf()),
RemoteOrLocal::Remote(remote) => Self::Remote(remote.into()),
}
}
}
impl<'a> From<&'a RemoteOrLocalBuf> for RemoteOrLocal<'a> {
fn from(value: &'a RemoteOrLocalBuf) -> Self {
match value {
RemoteOrLocalBuf::Local(local) => RemoteOrLocal::Local(local),
RemoteOrLocalBuf::Remote(remote) => RemoteOrLocal::Remote(remote.into()),
}
}
}
impl fmt::Display for RemoteOrLocalBuf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
RemoteOrLocal::from(self).fmt(f)
}
}
impl RemoteOrLocalBuf {
pub fn join(&self, value: impl AsRef<Path>) -> RemoteOrLocalBuf {
match self {
Self::Local(local) => Self::Local(local.join(value)),
Self::Remote(remote) => Self::Remote(remote.join(value)),
}
}
pub fn shell_command(&self, script: &str) -> Command {
RemoteOrLocal::from(self).shell_command(script)
}
pub fn path(&self) -> &Path {
match self {
Self::Local(local) => local,
Self::Remote(RemoteBuf { path, .. }) => path,
}
}
}