// Copyright © 2022 The Radicle Link Contributors // // This file is part of radicle-link, distributed under the GPLv3 with Radicle // Linking Exception. For full terms see the included LICENSE file. use std::{ borrow::{Borrow, Cow}, convert::TryFrom, fmt::{self, Display}, iter::{Extend, FromIterator}, ops::Deref, }; use crate::{ check, refspec::{PatternStr, PatternString}, Namespaced, Qualified, }; mod iter; pub use iter::{component, Component, Components, Iter}; #[cfg(feature = "percent-encoding")] pub use percent_encoding::PercentEncode; pub const HEADS: &RefStr = RefStr::from_str(str::HEADS); pub const MAIN: &RefStr = RefStr::from_str(str::MAIN); pub const MASTER: &RefStr = RefStr::from_str(str::MASTER); pub const NAMESPACES: &RefStr = RefStr::from_str(str::NAMESPACES); pub const NOTES: &RefStr = RefStr::from_str(str::NOTES); pub const ORIGIN: &RefStr = RefStr::from_str(str::ORIGIN); pub const REFS: &RefStr = RefStr::from_str(str::REFS); pub const REMOTES: &RefStr = RefStr::from_str(str::REMOTES); pub const TAGS: &RefStr = RefStr::from_str(str::TAGS); pub const REFS_HEADS_MAIN: &RefStr = RefStr::from_str(str::REFS_HEADS_MAIN); pub const REFS_HEADS_MASTER: &RefStr = RefStr::from_str(str::REFS_HEADS_MASTER); pub mod str { pub const HEADS: &str = "heads"; pub const MAIN: &str = "main"; pub const MASTER: &str = "master"; pub const NAMESPACES: &str = "namespaces"; pub const NOTES: &str = "notes"; pub const ORIGIN: &str = "origin"; pub const REFS: &str = "refs"; pub const REMOTES: &str = "remotes"; pub const TAGS: &str = "tags"; pub const REFS_HEADS_MAIN: &str = "refs/heads/main"; pub const REFS_HEADS_MASTER: &str = "refs/heads/master"; } pub mod bytes { use super::str; pub const HEADS: &[u8] = str::HEADS.as_bytes(); pub const MAIN: &[u8] = str::MAIN.as_bytes(); pub const MASTER: &[u8] = str::MASTER.as_bytes(); pub const NAMESPACES: &[u8] = str::NAMESPACES.as_bytes(); pub const NOTES: &[u8] = str::NOTES.as_bytes(); pub const ORIGIN: &[u8] = str::ORIGIN.as_bytes(); pub const REFS: &[u8] = str::REFS.as_bytes(); pub const REMOTES: &[u8] = str::REMOTES.as_bytes(); pub const TAGS: &[u8] = str::TAGS.as_bytes(); pub const REFS_HEADS_MAIN: &[u8] = str::REFS_HEADS_MAIN.as_bytes(); pub const REFS_HEADS_MASTER: &[u8] = str::REFS_HEADS_MASTER.as_bytes(); } const CHECK_OPTS: check::Options = check::Options { allow_pattern: false, allow_onelevel: true, }; #[repr(transparent)] #[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct RefStr(str); impl RefStr { pub fn try_from_str(s: &str) -> Result<&RefStr, check::Error> { TryFrom::try_from(s) } #[inline] pub fn as_str(&self) -> &str { self } #[inline] pub fn to_ref_string(&self) -> RefString { self.to_owned() } pub fn strip_prefix

(&self, base: P) -> Option<&RefStr> where P: AsRef, { self._strip_prefix(base.as_ref()) } fn _strip_prefix(&self, base: &RefStr) -> Option<&RefStr> { self.0 .strip_prefix(base.as_str()) .and_then(|s| s.strip_prefix('/')) .map(Self::from_str) } /// Join `other` onto `self`, yielding a new [`RefString`]. /// /// Consider to use [`RefString::and`] when chaining multiple fragments /// together, and the intermediate values are not needed. pub fn join(&self, other: R) -> RefString where R: AsRef, { self._join(other.as_ref()) } fn _join(&self, other: &RefStr) -> RefString { let mut buf = self.to_ref_string(); buf.push(other); buf } pub fn to_pattern

(&self, pattern: P) -> PatternString where P: AsRef, { self._to_pattern(pattern.as_ref()) } fn _to_pattern(&self, pattern: &PatternStr) -> PatternString { self.to_owned().with_pattern(pattern) } #[inline] pub fn qualified(&self) -> Option { Qualified::from_refstr(self) } #[inline] pub fn to_namespaced(&self) -> Option { self.into() } pub fn iter(&self) -> Iter { self.0.split('/') } pub fn components(&self) -> Components { Components::from(self) } pub fn head(&self) -> Component { self.components().next().expect("`RefStr` cannot be empty") } #[cfg(feature = "percent-encoding")] pub fn percent_encode(&self) -> PercentEncode { /// https://url.spec.whatwg.org/#fragment-percent-encode-set const FRAGMENT_PERCENT_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS .add(b' ') .add(b'"') .add(b'<') .add(b'>') .add(b'`'); /// https://url.spec.whatwg.org/#path-percent-encode-set const PATH_PERCENT_ENCODE_SET: &percent_encoding::AsciiSet = &FRAGMENT_PERCENT_ENCODE_SET .add(b'#') .add(b'?') .add(b'{') .add(b'}'); percent_encoding::utf8_percent_encode(self.as_str(), PATH_PERCENT_ENCODE_SET) } #[cfg(feature = "bstr")] #[inline] pub fn as_bstr(&self) -> &bstr::BStr { self.as_ref() } pub(crate) const fn from_str(s: &str) -> &RefStr { unsafe { &*(s as *const str as *const RefStr) } } } impl Deref for RefStr { type Target = str; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for RefStr { #[inline] fn as_ref(&self) -> &str { self } } #[cfg(feature = "bstr")] impl AsRef for RefStr { #[inline] fn as_ref(&self) -> &bstr::BStr { use bstr::ByteSlice as _; self.as_str().as_bytes().as_bstr() } } impl<'a> AsRef for &'a RefStr { #[inline] fn as_ref(&self) -> &RefStr { self } } impl<'a> TryFrom<&'a str> for &'a RefStr { type Error = check::Error; #[inline] fn try_from(s: &'a str) -> Result { check::ref_format(CHECK_OPTS, s).map(|()| RefStr::from_str(s)) } } impl<'a> From<&'a RefStr> for Cow<'a, RefStr> { #[inline] fn from(rs: &'a RefStr) -> Cow<'a, RefStr> { Cow::Borrowed(rs) } } impl Display for RefStr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self) } } #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct RefString(String); impl RefString { #[inline] pub fn as_refstr(&self) -> &RefStr { self } /// Join `other` onto `self` in place. /// /// This is a consuming version of [`RefString::push`] which can be chained. /// Prefer this over chaining calls to [`RefStr::join`] if the /// intermediate values are not needed. pub fn and(self, other: R) -> Self where R: AsRef, { self._and(other.as_ref()) } fn _and(mut self, other: &RefStr) -> Self { self.push(other); self } pub fn push(&mut self, other: R) where R: AsRef, { self.0.push('/'); self.0.push_str(other.as_ref().as_str()); } #[inline] pub fn pop(&mut self) -> bool { match self.0.rfind('/') { None => false, Some(idx) => { self.0.truncate(idx); true }, } } /// Append a [`PatternStr`], turning self into a new [`PatternString`]. pub fn with_pattern

(self, pattern: P) -> PatternString where P: AsRef, { self._with_pattern(pattern.as_ref()) } fn _with_pattern(self, pattern: &PatternStr) -> PatternString { let mut buf = self.0; buf.push('/'); buf.push_str(pattern.as_str()); PatternString(buf) } #[inline] pub fn into_qualified<'a>(self) -> Option> { Qualified::from_refstr(self) } #[inline] pub fn reserve(&mut self, additional: usize) { self.0.reserve(additional) } #[inline] pub fn shrink_to_fit(&mut self) { self.0.shrink_to_fit() } #[cfg(feature = "bstr")] #[inline] pub fn into_bstring(self) -> bstr::BString { self.into() } #[cfg(feature = "bstr")] #[inline] pub fn as_bstr(&self) -> &bstr::BStr { self.as_ref() } } impl Deref for RefString { type Target = RefStr; #[inline] fn deref(&self) -> &Self::Target { self.borrow() } } impl AsRef for RefString { #[inline] fn as_ref(&self) -> &RefStr { self } } impl AsRef for RefString { #[inline] fn as_ref(&self) -> &str { self.0.as_str() } } #[cfg(feature = "bstr")] impl AsRef for RefString { #[inline] fn as_ref(&self) -> &bstr::BStr { use bstr::ByteSlice as _; self.as_str().as_bytes().as_bstr() } } impl Borrow for RefString { #[inline] fn borrow(&self) -> &RefStr { RefStr::from_str(self.0.as_str()) } } impl ToOwned for RefStr { type Owned = RefString; #[inline] fn to_owned(&self) -> Self::Owned { RefString(self.0.to_owned()) } } impl TryFrom<&str> for RefString { type Error = check::Error; #[inline] fn try_from(s: &str) -> Result { RefStr::try_from_str(s).map(ToOwned::to_owned) } } impl TryFrom for RefString { type Error = check::Error; #[inline] fn try_from(s: String) -> Result { check::ref_format(CHECK_OPTS, s.as_str()).map(|()| RefString(s)) } } impl<'a> From<&'a RefString> for Cow<'a, RefStr> { #[inline] fn from(rs: &'a RefString) -> Cow<'a, RefStr> { Cow::Borrowed(rs.as_refstr()) } } impl<'a> From for Cow<'a, RefStr> { #[inline] fn from(rs: RefString) -> Cow<'a, RefStr> { Cow::Owned(rs) } } impl From for String { #[inline] fn from(rs: RefString) -> Self { rs.0 } } #[cfg(feature = "bstr")] impl From for bstr::BString { #[inline] fn from(rs: RefString) -> Self { bstr::BString::from(rs.0.into_bytes()) } } impl Display for RefString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.0) } } impl FromIterator for RefString where A: AsRef, { fn from_iter(iter: T) -> Self where T: IntoIterator, { let mut buf = String::new(); for c in iter { buf.push_str(c.as_ref().as_str()); buf.push('/'); } assert!(!buf.is_empty(), "empty iterator"); buf.truncate(buf.len() - 1); Self(buf) } } impl Extend for RefString where A: AsRef, { fn extend(&mut self, iter: T) where T: IntoIterator, { for x in iter { self.push(x) } } }