elvsc

19 commits
Updated 2026-04-30 16:50:05
src
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,
        }
    }
}