garbage 0.4.3

CLI tool for interacting with the freedesktop trashcan
Documentation
use std::env;
use std::ffi::{OsStr, OsString};
use std::fs;
use std::os::unix::ffi::OsStrExt;
use std::path::{Component, Path, PathBuf, MAIN_SEPARATOR};

use anyhow::Result;
use percent_encoding::AsciiSet;
use walkdir::WalkDir;

const MASK: &AsciiSet = percent_encoding::CONTROLS;

/// Converts a path to its absolute form WITHOUT resolving symlinks.
pub fn into_absolute(path: impl AsRef<Path>) -> Result<PathBuf> {
  let path = path.as_ref();

  let mut new_path = if !path.is_absolute() {
    env::current_dir()?
  } else {
    path.to_path_buf()
  };

  for component in path.components() {
    match component {
      Component::Prefix(_) => unimplemented!("windows lol"),
      Component::RootDir => return Ok(path.to_path_buf()),
      Component::CurDir => {}
      Component::ParentDir => {
        new_path.pop();
      }
      Component::Normal(s) => {
        new_path.push(s);
      }
    }
  }

  Ok(new_path)
}

pub fn get_uid() -> u64 {
  unsafe { libc::getuid().into() }
}

/// This function recursively copies all the contents of src into dst.
pub fn recursive_copy(
  src: impl AsRef<Path>,
  dst: impl AsRef<Path>,
) -> Result<()> {
  let src = src.as_ref();
  let dst = dst.as_ref();

  for entry in WalkDir::new(src)
    .contents_first(false)
    .follow_links(false)
    .same_file_system(true)
  {
    let entry = entry?;
    let path = entry.path();
    let relative_path = path.strip_prefix(src)?;

    // this must be the root
    if let None = relative_path.file_name() {
      fs::create_dir(dst)?;
      continue;
    }

    let target_name = dst.join(relative_path);
    if path.is_dir() {
      fs::create_dir(&target_name)?;
    } else {
      fs::copy(path, &target_name)?;
    }
  }

  Ok(())
}

/// Percent-encodes a path, but only the file names, not the separators.
pub fn percent_encode(path: impl AsRef<Path>) -> String {
  let path = path.as_ref();
  path
    .components()
    .filter_map(|segment| {
      match segment {
        Component::RootDir => Some(String::new()),
        Component::Prefix(_) => unreachable!("windows"),

        // TODO: separate type for normalized path
        Component::ParentDir => {
          unreachable!("path should be normalized")
        }
        Component::CurDir => unreachable!("path should be normalized"),

        Component::Normal(s) => {
          Some(percent_encoding::percent_encode(s.as_bytes(), MASK).to_string())
        }
      }
    })
    .collect::<Vec<_>>()
    .join(&MAIN_SEPARATOR.to_string())
}

pub(crate) fn concat_os_str_iter<'a>(ss: &Vec<&'a OsStr>) -> OsString {
  let mut len = 0;
  for s in ss {
    len += s.len();
  }

  let mut buf = vec![0; len];
  let mut c = 0;
  for s in ss {
    let segment = &mut buf[c..c + s.len()];
    segment.copy_from_slice(s.as_bytes());
    c += s.len();
  }

  OsStr::from_bytes(&buf).to_os_string()
}

/// Concatenates an arbitrary number of OsStrs
#[macro_export]
macro_rules! concat_os_str {
    ($($str:expr),* $(,)?) => {
        {
            let _wtf = vec![
                $($str.as_ref(),)*
            ];
            crate::utils::concat_os_str_iter(&_wtf)
        }
    };
}

#[cfg(test)]
mod tests {
  use super::*;
  #[test]
  fn test_absolute() {
    let cwd = env::current_dir().unwrap();
    let path = PathBuf::from("..");
    let parent = cwd.parent().unwrap();
    assert_eq!(into_absolute(&path).unwrap(), parent);

    let path2 = path.join(cwd.file_name().unwrap());
    assert_eq!(into_absolute(&path2).unwrap(), cwd);
  }
}