summaryrefslogtreecommitdiffstats
path: root/vendor/gix-config-value/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-config-value/src')
-rw-r--r--vendor/gix-config-value/src/boolean.rs99
-rw-r--r--vendor/gix-config-value/src/color.rs346
-rw-r--r--vendor/gix-config-value/src/integer.rs156
-rw-r--r--vendor/gix-config-value/src/lib.rs47
-rw-r--r--vendor/gix-config-value/src/path.rs195
-rw-r--r--vendor/gix-config-value/src/types.rs48
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>,
+}