summaryrefslogtreecommitdiffstats
path: root/vendor/gix-config-value/src/path.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:41 +0000
commit10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch)
treebdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-config-value/src/path.rs
parentReleasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff)
downloadrustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz
rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gix-config-value/src/path.rs')
-rw-r--r--vendor/gix-config-value/src/path.rs195
1 files changed, 195 insertions, 0 deletions
diff --git a/vendor/gix-config-value/src/path.rs b/vendor/gix-config-value/src/path.rs
new file mode 100644
index 000000000..99ee5cf2e
--- /dev/null
+++ b/vendor/gix-config-value/src/path.rs
@@ -0,0 +1,195 @@
+use std::{borrow::Cow, path::PathBuf};
+
+use bstr::BStr;
+
+use crate::Path;
+
+///
+pub mod interpolate {
+ use std::path::PathBuf;
+
+ /// Options for interpolating paths with [`Path::interpolate()`][crate::Path::interpolate()].
+ #[derive(Clone, Copy)]
+ pub struct Context<'a> {
+ /// The location where gitoxide or git is installed. If `None`, `%(prefix)` in paths will cause an error.
+ pub git_install_dir: Option<&'a std::path::Path>,
+ /// The home directory of the current user. If `None`, `~/` in paths will cause an error.
+ pub home_dir: Option<&'a std::path::Path>,
+ /// A function returning the home directory of a given user. If `None`, `~name/` in paths will cause an error.
+ pub home_for_user: Option<fn(&str) -> Option<PathBuf>>,
+ }
+
+ impl Default for Context<'_> {
+ fn default() -> Self {
+ Context {
+ git_install_dir: None,
+ home_dir: None,
+ home_for_user: Some(home_for_user),
+ }
+ }
+ }
+
+ /// The error returned by [`Path::interpolate()`][crate::Path::interpolate()].
+ #[derive(Debug, thiserror::Error)]
+ #[allow(missing_docs)]
+ pub enum Error {
+ #[error("{} is missing", .what)]
+ Missing { what: &'static str },
+ #[error("Ill-formed UTF-8 in {}", .what)]
+ Utf8Conversion {
+ what: &'static str,
+ #[source]
+ err: gix_path::Utf8Error,
+ },
+ #[error("Ill-formed UTF-8 in username")]
+ UsernameConversion(#[from] std::str::Utf8Error),
+ #[error("User interpolation is not available on this platform")]
+ UserInterpolationUnsupported,
+ }
+
+ /// Obtain the home directory for the given user `name` or return `None` if the user wasn't found
+ /// or any other error occurred.
+ /// It can be used as `home_for_user` parameter in [`Path::interpolate()`][crate::Path::interpolate()].
+ #[cfg_attr(windows, allow(unused_variables))]
+ pub fn home_for_user(name: &str) -> Option<PathBuf> {
+ #[cfg(not(any(target_os = "android", target_os = "windows")))]
+ {
+ let cname = std::ffi::CString::new(name).ok()?;
+ // SAFETY: calling this in a threaded program that modifies the pw database is not actually safe.
+ // TODO: use the `*_r` version, but it's much harder to use.
+ #[allow(unsafe_code)]
+ let pwd = unsafe { libc::getpwnam(cname.as_ptr()) };
+ if pwd.is_null() {
+ None
+ } else {
+ use std::os::unix::ffi::OsStrExt;
+ // SAFETY: pw_dir is a cstr and it lives as long as… well, we hope nobody changes the pw database while we are at it
+ // from another thread. Otherwise it lives long enough.
+ #[allow(unsafe_code)]
+ let cstr = unsafe { std::ffi::CStr::from_ptr((*pwd).pw_dir) };
+ Some(std::ffi::OsStr::from_bytes(cstr.to_bytes()).into())
+ }
+ }
+ #[cfg(any(target_os = "android", target_os = "windows"))]
+ {
+ None
+ }
+ }
+}
+
+impl<'a> std::ops::Deref for Path<'a> {
+ type Target = BStr;
+
+ fn deref(&self) -> &Self::Target {
+ self.value.as_ref()
+ }
+}
+
+impl<'a> AsRef<[u8]> for Path<'a> {
+ fn as_ref(&self) -> &[u8] {
+ self.value.as_ref()
+ }
+}
+
+impl<'a> AsRef<BStr> for Path<'a> {
+ fn as_ref(&self) -> &BStr {
+ self.value.as_ref()
+ }
+}
+
+impl<'a> From<Cow<'a, BStr>> for Path<'a> {
+ fn from(value: Cow<'a, BStr>) -> Self {
+ Path { value }
+ }
+}
+
+impl<'a> Path<'a> {
+ /// Interpolates this path into a path usable on the file system.
+ ///
+ /// If this path starts with `~/` or `~user/` or `%(prefix)/`
+ /// - `~/` is expanded to the value of `home_dir`. The caller can use the [dirs](https://crates.io/crates/dirs) crate to obtain it.
+ /// It it is required but not set, an error is produced.
+ /// - `~user/` to the specified user’s home directory, e.g `~alice` might get expanded to `/home/alice` on linux, but requires
+ /// the `home_for_user` function to be provided.
+ /// The interpolation uses `getpwnam` sys call and is therefore not available on windows.
+ /// - `%(prefix)/` is expanded to the location where `gitoxide` is installed.
+ /// This location is not known at compile time and therefore need to be
+ /// optionally provided by the caller through `git_install_dir`.
+ ///
+ /// Any other, non-empty path value is returned unchanged and error is returned in case of an empty path value or if required input
+ /// wasn't provided.
+ pub fn interpolate(
+ self,
+ interpolate::Context {
+ git_install_dir,
+ home_dir,
+ home_for_user,
+ }: interpolate::Context<'_>,
+ ) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {
+ if self.is_empty() {
+ return Err(interpolate::Error::Missing { what: "path" });
+ }
+
+ const PREFIX: &[u8] = b"%(prefix)/";
+ const USER_HOME: &[u8] = b"~/";
+ if self.starts_with(PREFIX) {
+ let git_install_dir = git_install_dir.ok_or(interpolate::Error::Missing {
+ what: "git install dir",
+ })?;
+ let (_prefix, path_without_trailing_slash) = self.split_at(PREFIX.len());
+ let path_without_trailing_slash =
+ gix_path::try_from_bstring(path_without_trailing_slash).map_err(|err| {
+ interpolate::Error::Utf8Conversion {
+ what: "path past %(prefix)",
+ err,
+ }
+ })?;
+ Ok(git_install_dir.join(path_without_trailing_slash).into())
+ } else if self.starts_with(USER_HOME) {
+ let home_path = home_dir.ok_or(interpolate::Error::Missing { what: "home dir" })?;
+ let (_prefix, val) = self.split_at(USER_HOME.len());
+ let val = gix_path::try_from_byte_slice(val).map_err(|err| interpolate::Error::Utf8Conversion {
+ what: "path past ~/",
+ err,
+ })?;
+ Ok(home_path.join(val).into())
+ } else if self.starts_with(b"~") && self.contains(&b'/') {
+ self.interpolate_user(home_for_user.ok_or(interpolate::Error::Missing {
+ what: "home for user lookup",
+ })?)
+ } else {
+ Ok(gix_path::from_bstr(self.value))
+ }
+ }
+
+ #[cfg(any(target_os = "windows", target_os = "android"))]
+ fn interpolate_user(
+ self,
+ _home_for_user: fn(&str) -> Option<PathBuf>,
+ ) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {
+ Err(interpolate::Error::UserInterpolationUnsupported)
+ }
+
+ #[cfg(not(any(target_os = "windows", target_os = "android")))]
+ fn interpolate_user(
+ self,
+ home_for_user: fn(&str) -> Option<PathBuf>,
+ ) -> Result<Cow<'a, std::path::Path>, interpolate::Error> {
+ let (_prefix, val) = self.split_at("/".len());
+ let i = val
+ .iter()
+ .position(|&e| e == b'/')
+ .ok_or(interpolate::Error::Missing { what: "/" })?;
+ let (username, path_with_leading_slash) = val.split_at(i);
+ let username = std::str::from_utf8(username)?;
+ let home = home_for_user(username).ok_or(interpolate::Error::Missing { what: "pwd user info" })?;
+ let path_past_user_prefix =
+ gix_path::try_from_byte_slice(&path_with_leading_slash["/".len()..]).map_err(|err| {
+ interpolate::Error::Utf8Conversion {
+ what: "path past ~user/",
+ err,
+ }
+ })?;
+ Ok(home.join(path_past_user_prefix).into())
+ }
+}