diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/gix-config-value/src | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-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')
-rw-r--r-- | vendor/gix-config-value/src/boolean.rs | 99 | ||||
-rw-r--r-- | vendor/gix-config-value/src/color.rs | 346 | ||||
-rw-r--r-- | vendor/gix-config-value/src/integer.rs | 156 | ||||
-rw-r--r-- | vendor/gix-config-value/src/lib.rs | 47 | ||||
-rw-r--r-- | vendor/gix-config-value/src/path.rs | 195 | ||||
-rw-r--r-- | vendor/gix-config-value/src/types.rs | 48 |
6 files changed, 891 insertions, 0 deletions
diff --git a/vendor/gix-config-value/src/boolean.rs b/vendor/gix-config-value/src/boolean.rs new file mode 100644 index 000000000..908e11a30 --- /dev/null +++ b/vendor/gix-config-value/src/boolean.rs @@ -0,0 +1,99 @@ +use std::{borrow::Cow, convert::TryFrom, ffi::OsString, fmt::Display}; + +use bstr::{BStr, BString, ByteSlice}; + +use crate::{Boolean, Error}; + +fn bool_err(input: impl Into<BString>) -> Error { + Error::new( + "Booleans need to be 'no', 'off', 'false', '' or 'yes', 'on', 'true' or any number", + input, + ) +} + +impl TryFrom<OsString> for Boolean { + type Error = Error; + + fn try_from(value: OsString) -> Result<Self, Self::Error> { + let value = gix_path::os_str_into_bstr(&value) + .map_err(|_| Error::new("Illformed UTF-8", std::path::Path::new(&value).display().to_string()))?; + Self::try_from(value) + } +} + +/// # Warning +/// +/// The direct usage of `try_from("string")` is discouraged as it will produce the wrong result for values +/// obtained from `core.bool-implicit-true`, which have no separator and are implicitly true. +/// This method chooses to work correctly for `core.bool-empty=`, which is an empty string and resolves +/// to being `false`. +/// +/// Instead of this, obtain booleans with `config.boolean(…)`, which handles the case were no separator is +/// present correctly. +impl TryFrom<&BStr> for Boolean { + type Error = Error; + + fn try_from(value: &BStr) -> Result<Self, Self::Error> { + if parse_true(value) { + Ok(Boolean(true)) + } else if parse_false(value) { + Ok(Boolean(false)) + } else { + use std::str::FromStr; + if let Some(integer) = value.to_str().ok().and_then(|s| i64::from_str(s).ok()) { + Ok(Boolean(integer != 0)) + } else { + Err(bool_err(value)) + } + } + } +} + +impl Boolean { + /// Return true if the boolean is a true value. + /// + /// Note that the inner value is accessible directly as well. + pub fn is_true(self) -> bool { + self.0 + } +} + +impl TryFrom<Cow<'_, BStr>> for Boolean { + type Error = Error; + fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> { + Self::try_from(c.as_ref()) + } +} + +impl Display for Boolean { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl From<Boolean> for bool { + fn from(b: Boolean) -> Self { + b.0 + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Boolean { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_bool(self.0) + } +} + +fn parse_true(value: &BStr) -> bool { + value.eq_ignore_ascii_case(b"yes") || value.eq_ignore_ascii_case(b"on") || value.eq_ignore_ascii_case(b"true") +} + +fn parse_false(value: &BStr) -> bool { + value.eq_ignore_ascii_case(b"no") + || value.eq_ignore_ascii_case(b"off") + || value.eq_ignore_ascii_case(b"false") + || value.is_empty() +} diff --git a/vendor/gix-config-value/src/color.rs b/vendor/gix-config-value/src/color.rs new file mode 100644 index 000000000..74e0dd4ac --- /dev/null +++ b/vendor/gix-config-value/src/color.rs @@ -0,0 +1,346 @@ +#![allow(missing_docs)] +use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; + +use bstr::{BStr, BString}; + +use crate::{Color, Error}; + +impl Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut write_space = None; + if let Some(fg) = self.foreground { + fg.fmt(f)?; + write_space = Some(()); + } + + if let Some(bg) = self.background { + if write_space.take().is_some() { + write!(f, " ")?; + } + bg.fmt(f)?; + write_space = Some(()) + } + + if !self.attributes.is_empty() { + if write_space.take().is_some() { + write!(f, " ")?; + } + self.attributes.fmt(f)?; + } + Ok(()) + } +} + +fn color_err(input: impl Into<BString>) -> Error { + Error::new( + "Colors are specific color values and their attributes, like 'brightred', or 'blue'", + input, + ) +} + +impl TryFrom<&BStr> for Color { + type Error = Error; + + fn try_from(s: &BStr) -> Result<Self, Self::Error> { + let s = std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?; + enum ColorItem { + Value(Name), + Attr(Attribute), + } + + let items = s.split_whitespace().filter_map(|s| { + if s.is_empty() { + return None; + } + + Some( + Name::from_str(s) + .map(ColorItem::Value) + .or_else(|_| Attribute::from_str(s).map(ColorItem::Attr)), + ) + }); + + let mut foreground = None; + let mut background = None; + let mut attributes = Attribute::empty(); + for item in items { + match item { + Ok(item) => match item { + ColorItem::Value(v) => { + if foreground.is_none() { + foreground = Some(v); + } else if background.is_none() { + background = Some(v); + } else { + return Err(color_err(s)); + } + } + ColorItem::Attr(a) => attributes |= a, + }, + Err(_) => return Err(color_err(s)), + } + } + + Ok(Color { + foreground, + background, + attributes, + }) + } +} + +impl TryFrom<Cow<'_, BStr>> for Color { + type Error = Error; + + fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> { + Self::try_from(c.as_ref()) + } +} + +/// Discriminating enum for names of [`Color`] values. +/// +/// `gix-config` supports the eight standard colors, their bright variants, an +/// ANSI color code, or a 24-bit hex value prefixed with an octothorpe/hash. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum Name { + Normal, + Default, + Black, + BrightBlack, + Red, + BrightRed, + Green, + BrightGreen, + Yellow, + BrightYellow, + Blue, + BrightBlue, + Magenta, + BrightMagenta, + Cyan, + BrightCyan, + White, + BrightWhite, + Ansi(u8), + Rgb(u8, u8, u8), +} + +impl Display for Name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Normal => write!(f, "normal"), + Self::Default => write!(f, "default"), + Self::Black => write!(f, "black"), + Self::BrightBlack => write!(f, "brightblack"), + Self::Red => write!(f, "red"), + Self::BrightRed => write!(f, "brightred"), + Self::Green => write!(f, "green"), + Self::BrightGreen => write!(f, "brightgreen"), + Self::Yellow => write!(f, "yellow"), + Self::BrightYellow => write!(f, "brightyellow"), + Self::Blue => write!(f, "blue"), + Self::BrightBlue => write!(f, "brightblue"), + Self::Magenta => write!(f, "magenta"), + Self::BrightMagenta => write!(f, "brightmagenta"), + Self::Cyan => write!(f, "cyan"), + Self::BrightCyan => write!(f, "brightcyan"), + Self::White => write!(f, "white"), + Self::BrightWhite => write!(f, "brightwhite"), + Self::Ansi(num) => num.fmt(f), + Self::Rgb(r, g, b) => write!(f, "#{r:02x}{g:02x}{b:02x}"), + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Name { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl FromStr for Name { + type Err = Error; + + fn from_str(mut s: &str) -> Result<Self, Self::Err> { + let bright = if let Some(rest) = s.strip_prefix("bright") { + s = rest; + true + } else { + false + }; + + match s { + "normal" if !bright => return Ok(Self::Normal), + "-1" if !bright => return Ok(Self::Normal), + "normal" if bright => return Err(color_err(s)), + "default" if !bright => return Ok(Self::Default), + "default" if bright => return Err(color_err(s)), + "black" if !bright => return Ok(Self::Black), + "black" if bright => return Ok(Self::BrightBlack), + "red" if !bright => return Ok(Self::Red), + "red" if bright => return Ok(Self::BrightRed), + "green" if !bright => return Ok(Self::Green), + "green" if bright => return Ok(Self::BrightGreen), + "yellow" if !bright => return Ok(Self::Yellow), + "yellow" if bright => return Ok(Self::BrightYellow), + "blue" if !bright => return Ok(Self::Blue), + "blue" if bright => return Ok(Self::BrightBlue), + "magenta" if !bright => return Ok(Self::Magenta), + "magenta" if bright => return Ok(Self::BrightMagenta), + "cyan" if !bright => return Ok(Self::Cyan), + "cyan" if bright => return Ok(Self::BrightCyan), + "white" if !bright => return Ok(Self::White), + "white" if bright => return Ok(Self::BrightWhite), + _ => (), + } + + if let Ok(v) = u8::from_str(s) { + return Ok(Self::Ansi(v)); + } + + if let Some(s) = s.strip_prefix('#') { + if s.len() == 6 { + let rgb = ( + u8::from_str_radix(&s[..2], 16), + u8::from_str_radix(&s[2..4], 16), + u8::from_str_radix(&s[4..], 16), + ); + + if let (Ok(r), Ok(g), Ok(b)) = rgb { + return Ok(Self::Rgb(r, g, b)); + } + } + } + + Err(color_err(s)) + } +} + +impl TryFrom<&BStr> for Name { + type Error = Error; + + fn try_from(s: &BStr) -> Result<Self, Self::Error> { + Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) + } +} + +bitflags::bitflags! { + /// Discriminating enum for [`Color`] attributes. + /// + /// `gix-config` supports modifiers and their negators. The negating color + /// attributes are equivalent to having a `no` or `no-` prefix to the normal + /// variant. + #[derive(Default)] + pub struct Attribute: u32 { + const BOLD = 1 << 1; + const DIM = 1 << 2; + const ITALIC = 1 << 3; + const UL = 1 << 4; + const BLINK = 1 << 5; + const REVERSE = 1 << 6; + const STRIKE = 1 << 7; + /// Reset is special as we have to be able to parse it, without git actually doing anything with it + const RESET = 1 << 8; + + const NO_DIM = 1 << 21; + const NO_BOLD = 1 << 22; + const NO_ITALIC = 1 << 23; + const NO_UL = 1 << 24; + const NO_BLINK = 1 << 25; + const NO_REVERSE = 1 << 26; + const NO_STRIKE = 1 << 27; + } +} + +impl Display for Attribute { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut write_space = None; + for bit in 1..std::mem::size_of::<Attribute>() * 8 { + let attr = match Attribute::from_bits(1 << bit) { + Some(attr) => attr, + None => continue, + }; + if self.contains(attr) { + if write_space.take().is_some() { + write!(f, " ")? + } + match attr { + Attribute::RESET => write!(f, "reset"), + Attribute::BOLD => write!(f, "bold"), + Attribute::NO_BOLD => write!(f, "nobold"), + Attribute::DIM => write!(f, "dim"), + Attribute::NO_DIM => write!(f, "nodim"), + Attribute::UL => write!(f, "ul"), + Attribute::NO_UL => write!(f, "noul"), + Attribute::BLINK => write!(f, "blink"), + Attribute::NO_BLINK => write!(f, "noblink"), + Attribute::REVERSE => write!(f, "reverse"), + Attribute::NO_REVERSE => write!(f, "noreverse"), + Attribute::ITALIC => write!(f, "italic"), + Attribute::NO_ITALIC => write!(f, "noitalic"), + Attribute::STRIKE => write!(f, "strike"), + Attribute::NO_STRIKE => write!(f, "nostrike"), + _ => unreachable!("BUG: add new attribute flag"), + }?; + write_space = Some(()); + } + } + Ok(()) + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Attribute { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl FromStr for Attribute { + type Err = Error; + + fn from_str(mut s: &str) -> Result<Self, Self::Err> { + let inverted = if let Some(rest) = s.strip_prefix("no-").or_else(|| s.strip_prefix("no")) { + s = rest; + true + } else { + false + }; + + match s { + "reset" if !inverted => Ok(Attribute::RESET), + "reset" if inverted => Err(color_err(s)), + "bold" if !inverted => Ok(Attribute::BOLD), + "bold" if inverted => Ok(Attribute::NO_BOLD), + "dim" if !inverted => Ok(Attribute::DIM), + "dim" if inverted => Ok(Attribute::NO_DIM), + "ul" if !inverted => Ok(Attribute::UL), + "ul" if inverted => Ok(Attribute::NO_UL), + "blink" if !inverted => Ok(Attribute::BLINK), + "blink" if inverted => Ok(Attribute::NO_BLINK), + "reverse" if !inverted => Ok(Attribute::REVERSE), + "reverse" if inverted => Ok(Attribute::NO_REVERSE), + "italic" if !inverted => Ok(Attribute::ITALIC), + "italic" if inverted => Ok(Attribute::NO_ITALIC), + "strike" if !inverted => Ok(Attribute::STRIKE), + "strike" if inverted => Ok(Attribute::NO_STRIKE), + _ => Err(color_err(s)), + } + } +} + +impl TryFrom<&BStr> for Attribute { + type Error = Error; + + fn try_from(s: &BStr) -> Result<Self, Self::Error> { + Self::from_str(std::str::from_utf8(s).map_err(|err| color_err(s).with_err(err))?) + } +} diff --git a/vendor/gix-config-value/src/integer.rs b/vendor/gix-config-value/src/integer.rs new file mode 100644 index 000000000..b287899ec --- /dev/null +++ b/vendor/gix-config-value/src/integer.rs @@ -0,0 +1,156 @@ +use std::{borrow::Cow, convert::TryFrom, fmt::Display, str::FromStr}; + +use bstr::{BStr, BString}; + +use crate::{Error, Integer}; + +impl Integer { + /// Canonicalize values as simple decimal numbers. + /// An optional suffix of k, m, or g (case-insensitive), will cause the + /// value to be multiplied by 1024 (k), 1048576 (m), or 1073741824 (g) respectively. + /// + /// Returns the result if there is no multiplication overflow. + pub fn to_decimal(&self) -> Option<i64> { + match self.suffix { + None => Some(self.value), + Some(suffix) => match suffix { + Suffix::Kibi => self.value.checked_mul(1024), + Suffix::Mebi => self.value.checked_mul(1024 * 1024), + Suffix::Gibi => self.value.checked_mul(1024 * 1024 * 1024), + }, + } + } +} + +impl Display for Integer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.value)?; + if let Some(suffix) = self.suffix { + write!(f, "{suffix}") + } else { + Ok(()) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Integer { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + if let Some(suffix) = self.suffix { + serializer.serialize_i64(self.value << suffix.bitwise_offset()) + } else { + serializer.serialize_i64(self.value) + } + } +} + +fn int_err(input: impl Into<BString>) -> Error { + Error::new( + "Integers needs to be positive or negative numbers which may have a suffix like 1k, 42, or 50G", + input, + ) +} + +impl TryFrom<&BStr> for Integer { + type Error = Error; + + fn try_from(s: &BStr) -> Result<Self, Self::Error> { + let s = std::str::from_utf8(s).map_err(|err| int_err(s).with_err(err))?; + if let Ok(value) = s.parse() { + return Ok(Self { value, suffix: None }); + } + + if s.len() <= 1 { + return Err(int_err(s)); + } + + let (number, suffix) = s.split_at(s.len() - 1); + if let (Ok(value), Ok(suffix)) = (number.parse(), suffix.parse()) { + Ok(Self { + value, + suffix: Some(suffix), + }) + } else { + Err(int_err(s)) + } + } +} + +impl TryFrom<Cow<'_, BStr>> for Integer { + type Error = Error; + + fn try_from(c: Cow<'_, BStr>) -> Result<Self, Self::Error> { + Self::try_from(c.as_ref()) + } +} + +/// Integer suffixes that are supported by `gix-config`. +/// +/// These values are base-2 unit of measurements, not the base-10 variants. +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub enum Suffix { + Kibi, + Mebi, + Gibi, +} + +impl Suffix { + /// Returns the number of bits that the suffix shifts left by. + #[must_use] + pub const fn bitwise_offset(self) -> usize { + match self { + Self::Kibi => 10, + Self::Mebi => 20, + Self::Gibi => 30, + } + } +} + +impl Display for Suffix { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Kibi => write!(f, "k"), + Self::Mebi => write!(f, "m"), + Self::Gibi => write!(f, "g"), + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for Suffix { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: serde::Serializer, + { + serializer.serialize_str(match self { + Self::Kibi => "k", + Self::Mebi => "m", + Self::Gibi => "g", + }) + } +} + +impl FromStr for Suffix { + type Err = (); + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "k" | "K" => Ok(Self::Kibi), + "m" | "M" => Ok(Self::Mebi), + "g" | "G" => Ok(Self::Gibi), + _ => Err(()), + } + } +} + +impl TryFrom<&BStr> for Suffix { + type Error = (); + + fn try_from(s: &BStr) -> Result<Self, Self::Error> { + Self::from_str(std::str::from_utf8(s).map_err(|_| ())?) + } +} diff --git a/vendor/gix-config-value/src/lib.rs b/vendor/gix-config-value/src/lib.rs new file mode 100644 index 000000000..130674e5a --- /dev/null +++ b/vendor/gix-config-value/src/lib.rs @@ -0,0 +1,47 @@ +//! Parsing for data types used in `gix-config` files to allow their use from environment variables and other sources. +//! +//! ## Feature Flags +#![cfg_attr( + feature = "document-features", + cfg_attr(doc, doc = ::document_features::document_features!()) +)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![deny(missing_docs, rust_2018_idioms, unsafe_code)] + +/// The error returned when any config value couldn't be instantiated due to malformed input. +#[derive(Debug, thiserror::Error, Eq, PartialEq)] +#[allow(missing_docs)] +#[error("Could not decode '{input}': {message}")] +pub struct Error { + pub message: &'static str, + pub input: bstr::BString, + #[source] + pub utf8_err: Option<std::str::Utf8Error>, +} + +impl Error { + /// Create a new value error from `message`, with `input` being what's causing the error. + pub fn new(message: &'static str, input: impl Into<bstr::BString>) -> Self { + Error { + message, + input: input.into(), + utf8_err: None, + } + } + + pub(crate) fn with_err(mut self, err: std::str::Utf8Error) -> Self { + self.utf8_err = Some(err); + self + } +} + +mod boolean; +/// +pub mod color; +/// +pub mod integer; +/// +pub mod path; + +mod types; +pub use types::{Boolean, Color, Integer, Path}; 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()) + } +} diff --git a/vendor/gix-config-value/src/types.rs b/vendor/gix-config-value/src/types.rs new file mode 100644 index 000000000..ac3911dfd --- /dev/null +++ b/vendor/gix-config-value/src/types.rs @@ -0,0 +1,48 @@ +use crate::{color, integer}; + +/// Any value that may contain a foreground color, background color, a +/// collection of color (text) modifiers, or a combination of any of the +/// aforementioned values, like `red` or `brightgreen`. +/// +/// Note that `gix-config` allows color values to simply be a collection of +/// [`color::Attribute`]s, and does not require a [`color::Name`] for either the +/// foreground or background color. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)] +pub struct Color { + /// A provided foreground color + pub foreground: Option<color::Name>, + /// A provided background color + pub background: Option<color::Name>, + /// A potentially empty set of text attributes + pub attributes: color::Attribute, +} + +/// Any value that can be interpreted as an integer. +/// +/// This supports any numeric value that can fit in a [`i64`], excluding the +/// suffix. The suffix is parsed separately from the value itself, so if you +/// wish to obtain the true value of the integer, you must account for the +/// suffix after fetching the value. [`integer::Suffix`] provides +/// [`bitwise_offset()`][integer::Suffix::bitwise_offset] to help with the +/// math, or [to_decimal()][Integer::to_decimal()] for obtaining a usable value in one step. +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Integer { + /// The value, without any suffix modification + pub value: i64, + /// A provided suffix, if any. + pub suffix: Option<integer::Suffix>, +} + +/// Any value that can be interpreted as a boolean. +#[derive(Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +#[allow(missing_docs)] +pub struct Boolean(pub bool); + +/// Any value that can be interpreted as a path to a resource on disk. +/// +/// Git represents file paths as byte arrays, modeled here as owned or borrowed byte sequences. +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] +pub struct Path<'a> { + /// The path string, un-interpolated + pub value: std::borrow::Cow<'a, bstr::BStr>, +} |