orphanage 0.5.6

Random collection of stuff that is still searching for a home.
Documentation
//! Utility functions that extend `std::path`.
//!
//! There's some functionality that overlaps with [`fs`](super::fs), but the
//! main difference is that functions in `fs` assume the file system objects
//! exists, while functions in the `path` submodule (generally) make no such
//! assumptions.

use std::{
  fs,
  path::{Path, PathBuf}
};

use crate::err::Error;

/// Path extensions.
#[allow(clippy::module_name_repetitions)]
pub trait PathExt {
  /// Expand a path and return it.
  ///
  /// # Errors
  /// [`Error::BadFormat`] means the input path could not be expanded.
  fn expand(input: &str) -> Result<PathBuf, Error> {
    match shellexpand::full(&input) {
      Ok(value) => Ok(PathBuf::from(value.into_owned())),
      Err(e) => Err(Error::BadFormat(format!("Unable to expand path; {e}")))
    }
  }

  /// Expand a path, run a closure on the expanded path, and then return the
  /// expanded path.
  ///
  /// # Errors
  /// [`Error::BadFormat`] means the input path could not be expanded.
  fn expand_and<F>(input: &str, f: F) -> Result<PathBuf, Error>
  where
    F: FnOnce(&Path) -> Result<(), Error>
  {
    let expanded = PathBuf::expand(input)?;
    f(&expanded)?;
    Ok(expanded)
  }

  /// Expand a path, run a closure on the expanded path, canonicalize the path
  /// and then return it.
  ///
  /// The canonicalization occurs _after_ the closure has been called because
  /// the the canonicalization may not be possible until the closure has run
  /// (for instance, if the closure is meant to create the file system object
  /// that is meant to be canonicalized).
  ///
  /// # Errors
  /// [`Error::BadFormat`] means the input path could not be expanded.
  fn expand_and_canon<F>(input: &str, f: F) -> Result<PathBuf, Error>
  where
    F: FnOnce(&Path) -> Result<(), Error>
  {
    let expanded = PathBuf::expand_and(input, f)?;
    Ok(fs::canonicalize(&expanded)?)
  }
}

impl PathExt for PathBuf {}


/// Expand a string path and return it as a `PathBuf`.
///
/// The expansion is done using [`shellexpand::full()`].
///
/// # Errors
/// [`Error::BadFormat`] means the path could not be expanded.
pub fn expand(input: impl AsRef<str>) -> Result<PathBuf, Error> {
  match shellexpand::full(&input) {
    Ok(value) => Ok(PathBuf::from(value.into_owned())),
    Err(e) => Err(Error::BadFormat(format!("Unable to expand path; {e}")))
  }
}

/// Expand a string path to absolute path and return it as a `PathBuf`.
///
/// The expansion is performed by [`expand()`].  If the path is determined to
/// be relative it is made absolute by calling [`std::env::current_dir()`] and
/// joining it with the relative path.
#[allow(clippy::missing_errors_doc)]
pub fn expabs(input: impl AsRef<str>) -> Result<PathBuf, Error> {
  let exppth = expand(input)?;
  abspath(exppth)
}

/// Return the full path to an input path.
///
/// If the input path is absolute this function will return a `PathBuf`
/// version of the input.  Otherwise the input path will be appended to the
/// current working directory to generate the returned `PathBuf`.
#[allow(clippy::missing_errors_doc)]
pub fn abspath<P>(pth: P) -> Result<PathBuf, Error>
where
  P: AsRef<Path>
{
  fn inner(pth: &Path) -> Result<PathBuf, Error> {
    if pth.is_absolute() {
      Ok(pth.to_path_buf())
    } else {
      let cwd = std::env::current_dir()?;
      Ok(cwd.join(pth))
    }
  }

  inner(pth.as_ref())
}

/// Given a base directory and a path `pth`, return `pth` if it is absolute,
/// otherwise join the base directory and `pth` and return it.
///
/// ```
/// use std::path::{Path, PathBuf};
/// use orphanage::path::abs_or_base;
///
/// let basedir = Path::new("/foo");
/// #[cfg(unix)]
/// assert_eq!(abs_or_base(&basedir, "hello"), PathBuf::from("/foo/hello"));
/// #[cfg(unix)]
/// assert_eq!(abs_or_base(&basedir, "/bar/hello"),
///   PathBuf::from("/bar/hello"));
/// ```
pub fn abs_or_base(basedir: &Path, pth: impl AsRef<Path>) -> PathBuf {
  fn inner(basedir: &Path, pth: &Path) -> PathBuf {
    assert!(basedir.is_absolute());
    if pth.is_absolute() {
      pth.to_path_buf()
    } else {
      basedir.join(pth)
    }
  }

  inner(basedir, pth.as_ref())
}


/// Replace '\` with `/` in a `PathBuf`.
///
/// On unixy platforms, `\` isn't treated a a path separator, while on Windows
/// both `\` and `/` are treated as path separators, which means that using `/`
/// on Windows is the most portable option.
///
/// The current implementation converts the path to a string, using
/// `Path::to_str()`, replaces the backslashes with slashes and then converts
/// the string back to a `PathBuf`.  An implication of this is that only paths
/// that are utf-8 compliant can be processed.
#[must_use]
#[allow(clippy::option_if_let_else)]
pub fn win2unix_sep(pth: impl AsRef<Path>) -> PathBuf {
  let pth = pth.as_ref();
  if let Some(s) = pth.to_str() {
    let converted = s.replace('\\', "/");
    PathBuf::from(converted)
  } else {
    pth.to_path_buf()
  }
}


/*
/// Expand, perform action on expanded path.
pub fn expand_and<F>(input: impl AsRef<str>, f: F) -> Result<PathBuf, Error>
where
  F: FnOnce(&Path) -> Result<(), Error>
{
  let expanded = match shellexpand::full(&input) {
    Ok(value) => Ok(PathBuf::from(value.into_owned())),
    Err(e) => Err(Error::BadFormat(format!("Unable to expand path; {e}")))
  }?;

  // Call closure to process the expanded path
  f(&expanded)?;

  Ok(expanded)
}

/// Expand, perform action on expanded path and return canonicalized path.
pub fn expand_canonizalize<F>(
  input: impl AsRef<str>,
  f: F
) -> Result<PathBuf, Error>
where
  F: FnOnce(&Path) -> Result<(), Error>
{
  let expanded = expand_and(input, f)?;

  // Canonicalize
  Ok(fs::canonicalize(&expanded)?)
}
*/

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :