git-repo-manager 0.10.0

Manage multiple git repositories. You configure the git repositories in a file, the program does the rest!
Documentation
use std::fmt;

use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
use thiserror::Error;

#[derive(Debug)]
pub struct EnvVariableName(String);

impl fmt::Display for EnvVariableName {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("Found non-utf8 path: {:?}", .path)]
    NonUtf8 { path: std::path::PathBuf },
    #[error("Failed getting env variable `{}`: {}", .variable, .error)]
    Env {
        variable: EnvVariableName,
        error: String,
    },
    #[error("Failed expanding path: {}", .error)]
    Expand { error: String },
    #[error("Failed getting current directory: {0}")]
    CurrentDir(std::io::Error),
}

pub fn from_std_path(from: &std::path::Path) -> Result<&Path, Error> {
    Path::from_path(from).ok_or_else(|| Error::NonUtf8 {
        path: from.to_owned(),
    })
}

pub fn from_std_path_buf(from: std::path::PathBuf) -> Result<PathBuf, Error> {
    PathBuf::from_path_buf(from).map_err(|original_path| Error::NonUtf8 {
        path: original_path,
    })
}

pub fn env_home() -> Result<PathBuf, Error> {
    Ok(PathBuf::from(std::env::var("HOME").map_err(|e| {
        Error::Env {
            variable: EnvVariableName("HOME".to_owned()),
            error: e.to_string(),
        }
    })?))
}

pub fn current_dir() -> Result<PathBuf, Error> {
    from_std_path_buf(std::env::current_dir().map_err(|err| Error::CurrentDir(err))?)
}

pub fn expand_path(path: &Path) -> Result<PathBuf, Error> {
    let home = &env_home()?;
    let expanded_path = match shellexpand::full_with_context(
        path,
        || Some(home.clone()),
        |name| -> Result<Option<String>, Error> {
            match name {
                "HOME" => Ok(Some(home.as_str().to_owned())),
                _ => Ok(None),
            }
        },
    ) {
        Ok(std::borrow::Cow::Borrowed(path)) => path.to_owned(),
        Ok(std::borrow::Cow::Owned(path)) => path,
        Err(e) => {
            return Err(Error::Expand {
                error: e.cause.to_string(),
            });
        }
    };

    Ok(Path::new(&expanded_path).to_path_buf())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn check_expand_tilde() -> Result<(), Error> {
        temp_env::with_var("HOME", Some("/home/test"), || {
            assert_eq!(
                expand_path(Path::new("~/file"))?,
                Path::new("/home/test/file")
            );
            Ok(())
        })
    }

    #[test]
    fn check_expand_invalid_tilde() -> Result<(), Error> {
        temp_env::with_var("HOME", Some("/home/test"), || {
            assert_eq!(
                expand_path(Path::new("/home/~/file"))?,
                Path::new("/home/~/file")
            );
            Ok(())
        })
    }

    #[test]
    fn check_expand_home() -> Result<(), Error> {
        temp_env::with_var("HOME", Some("/home/test"), || {
            assert_eq!(
                expand_path(Path::new("$HOME/file"))?,
                Path::new("/home/test/file")
            );
            assert_eq!(
                expand_path(Path::new("${HOME}/file"))?,
                Path::new("/home/test/file")
            );
            Ok(())
        })
    }
}