// Copyright © 2021 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::FromIterator, ops::Deref, }; use thiserror::Error; use crate::{check, lit, RefStr, RefString}; mod iter; pub use iter::{component, Component, Components, Iter}; pub const STAR: &PatternStr = PatternStr::from_str("*"); const CHECK_OPTS: check::Options = check::Options { allow_onelevel: true, allow_pattern: true, }; #[repr(transparent)] #[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct PatternStr(str); impl PatternStr { #[inline] pub fn try_from_str(s: &str) -> Result<&Self, check::Error> { TryFrom::try_from(s) } #[inline] pub fn as_str(&self) -> &str { self } pub fn join(&self, other: R) -> PatternString where R: AsRef, { self._join(other.as_ref()) } fn _join(&self, other: &RefStr) -> PatternString { let mut buf = self.to_owned(); buf.push(other); buf } #[inline] pub fn qualified(&self) -> Option { QualifiedPattern::from_patternstr(self) } #[inline] pub fn to_namespaced(&self) -> Option { self.into() } #[inline] pub fn iter(&self) -> Iter { self.0.split('/') } #[inline] pub fn components(&self) -> Components { Components::from(self) } pub(crate) const fn from_str(s: &str) -> &PatternStr { unsafe { &*(s as *const str as *const PatternStr) } } } impl Deref for PatternStr { type Target = str; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for PatternStr { #[inline] fn as_ref(&self) -> &str { self } } impl AsRef for PatternStr { #[inline] fn as_ref(&self) -> &Self { self } } impl<'a> TryFrom<&'a str> for &'a PatternStr { type Error = check::Error; #[inline] fn try_from(s: &'a str) -> Result { check::ref_format(CHECK_OPTS, s).map(|()| PatternStr::from_str(s)) } } impl<'a> From<&'a RefStr> for &'a PatternStr { #[inline] fn from(rs: &'a RefStr) -> Self { PatternStr::from_str(rs.as_str()) } } impl<'a> From<&'a PatternStr> for Cow<'a, PatternStr> { #[inline] fn from(p: &'a PatternStr) -> Cow<'a, PatternStr> { Cow::Borrowed(p) } } impl Display for PatternStr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self) } } #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct PatternString(pub(crate) String); impl PatternString { #[inline] pub fn as_str(&self) -> &str { self.as_ref() } #[inline] pub fn as_pattern_str(&self) -> &PatternStr { self.as_ref() } #[inline] pub fn from_components<'a, T>(iter: T) -> Result where T: IntoIterator>, { iter.into_iter().collect() } #[inline] pub fn and(mut self, other: R) -> Self where R: AsRef, { self._push(other.as_ref()); self } #[inline] pub fn push(&mut self, other: R) where R: AsRef, { self._push(other.as_ref()) } fn _push(&mut self, other: &RefStr) { self.0.push('/'); self.0.push_str(other.as_str()); } #[inline] pub fn pop(&mut self) -> bool { match self.0.rfind('/') { None => false, Some(idx) => { self.0.truncate(idx); true }, } } } impl Deref for PatternString { type Target = PatternStr; #[inline] fn deref(&self) -> &Self::Target { self.borrow() } } impl AsRef for PatternString { #[inline] fn as_ref(&self) -> &PatternStr { self } } impl AsRef for PatternString { #[inline] fn as_ref(&self) -> &str { self.0.as_str() } } impl Borrow for PatternString { #[inline] fn borrow(&self) -> &PatternStr { PatternStr::from_str(self.0.as_str()) } } impl ToOwned for PatternStr { type Owned = PatternString; #[inline] fn to_owned(&self) -> Self::Owned { PatternString(self.0.to_owned()) } } impl From for PatternString { #[inline] fn from(rs: RefString) -> Self { Self(rs.into()) } } impl<'a> From<&'a PatternString> for Cow<'a, PatternStr> { #[inline] fn from(p: &'a PatternString) -> Cow<'a, PatternStr> { Cow::Borrowed(p.as_ref()) } } impl From for String { #[inline] fn from(p: PatternString) -> Self { p.0 } } impl TryFrom<&str> for PatternString { type Error = check::Error; #[inline] fn try_from(s: &str) -> Result { PatternStr::try_from_str(s).map(ToOwned::to_owned) } } impl TryFrom for PatternString { type Error = check::Error; #[inline] fn try_from(s: String) -> Result { check::ref_format(CHECK_OPTS, s.as_str()).map(|()| PatternString(s)) } } #[derive(Debug, Error)] #[error("more than one '*' encountered")] pub struct DuplicateGlob; impl<'a> FromIterator> for Result { fn from_iter(iter: T) -> Self where T: IntoIterator>, { use Component::*; let mut buf = String::new(); let mut seen_glob = false; for c in iter { if let Glob(_) = c { if seen_glob { return Err(DuplicateGlob); } seen_glob = true; } buf.push_str(c.as_str()); buf.push('/'); } buf.truncate(buf.len() - 1); Ok(PatternString(buf)) } } impl Display for PatternString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.0) } } /// A fully-qualified refspec. /// /// A refspec is qualified _iff_ it starts with "refs/" and has at least three /// components. This implies that a [`QualifiedPattern`] ref has a category, /// such as "refs/heads/*". #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct QualifiedPattern<'a>(pub(crate) Cow<'a, PatternStr>); impl<'a> QualifiedPattern<'a> { pub fn from_patternstr(r: impl Into>) -> Option { Self::_from_patternstr(r.into()) } fn _from_patternstr(r: Cow<'a, PatternStr>) -> Option { let mut iter = r.iter(); match (iter.next()?, iter.next()?, iter.next()?) { ("refs", _, _) => Some(QualifiedPattern(r)), _ => None, } } #[inline] pub fn as_str(&self) -> &str { self.as_ref() } #[inline] pub fn join<'b, R>(&self, other: R) -> QualifiedPattern<'b> where R: AsRef, { QualifiedPattern(Cow::Owned(self.0.join(other))) } #[inline] pub fn to_namespaced(&self) -> Option { self.0.as_ref().into() } /// Add a namespace. /// /// Creates a new [`NamespacedPattern`] by prefxing `self` with /// "refs/namespaces/". pub fn with_namespace<'b>( &self, ns: Component<'b>, ) -> Result, DuplicateGlob> { PatternString::from_components( IntoIterator::into_iter([lit::Refs.into(), lit::Namespaces.into(), ns]) .chain(self.components()), ) .map(|pat| NamespacedPattern(Cow::Owned(pat))) } /// Like [`Self::non_empty_components`], but with string slices. pub fn non_empty_iter(&self) -> (&str, &str, &str, Iter) { let mut iter = self.iter(); ( iter.next().unwrap(), iter.next().unwrap(), iter.next().unwrap(), iter, ) } /// Return the first three [`Component`]s, and a possibly empty iterator /// over the remaining ones. /// /// A qualified ref is guaranteed to have at least three components, which /// this method provides a witness of. This is useful eg. for pattern /// matching on the prefix. pub fn non_empty_components(&self) -> (Component, Component, Component, Components) { let mut cs = self.components(); ( cs.next().unwrap(), cs.next().unwrap(), cs.next().unwrap(), cs, ) } #[inline] pub fn to_owned<'b>(&self) -> QualifiedPattern<'b> { QualifiedPattern(Cow::Owned(self.0.clone().into_owned())) } #[inline] pub fn into_owned<'b>(self) -> QualifiedPattern<'b> { QualifiedPattern(Cow::Owned(self.0.into_owned())) } #[inline] pub fn into_patternstring(self) -> PatternString { self.into() } } impl Deref for QualifiedPattern<'_> { type Target = PatternStr; #[inline] fn deref(&self) -> &Self::Target { &self.0 } } impl AsRef for QualifiedPattern<'_> { #[inline] fn as_ref(&self) -> &PatternStr { self } } impl AsRef for QualifiedPattern<'_> { #[inline] fn as_ref(&self) -> &str { self.0.as_str() } } impl AsRef for QualifiedPattern<'_> { #[inline] fn as_ref(&self) -> &Self { self } } impl<'a> From> for Cow<'a, PatternStr> { #[inline] fn from(q: QualifiedPattern<'a>) -> Self { q.0 } } impl From> for PatternString { #[inline] fn from(q: QualifiedPattern) -> Self { q.0.into_owned() } } /// A [`PatternString`] ref under a git namespace. /// /// A ref is namespaced if it starts with "refs/namespaces/", another path /// component, and "refs/". Eg. /// /// refs/namespaces/xyz/refs/heads/main /// /// Note that namespaces can be nested, so the result of /// [`NamespacedPattern::strip_namespace`] may be convertible to a /// [`NamespacedPattern`] again. For example: /// /// ```no_run /// let full = pattern!("refs/namespaces/a/refs/namespaces/b/refs/heads/*"); /// let namespaced = full.to_namespaced().unwrap(); /// let strip_first = namespaced.strip_namespace(); /// let nested = strip_first.namespaced().unwrap(); /// let strip_second = nested.strip_namespace(); /// /// assert_eq!("a", namespaced.namespace().as_str()); /// assert_eq!("b", nested.namespace().as_str()); /// assert_eq!("refs/namespaces/b/refs/heads/*", strip_first.as_str()); /// assert_eq!("refs/heads/*", strip_second.as_str()); /// ``` #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] pub struct NamespacedPattern<'a>(Cow<'a, PatternStr>); impl<'a> NamespacedPattern<'a> { pub fn namespace(&self) -> Component { self.components().nth(2).unwrap() } pub fn strip_namespace(&self) -> PatternString { PatternString::from_components(self.components().skip(3)) .expect("BUG: NamespacedPattern was constructed with a duplicate glob") } pub fn strip_namespace_recursive(&self) -> PatternString { let mut strip = self.strip_namespace(); while let Some(ns) = strip.to_namespaced() { strip = ns.strip_namespace(); } strip } #[inline] pub fn to_owned<'b>(&self) -> NamespacedPattern<'b> { NamespacedPattern(Cow::Owned(self.0.clone().into_owned())) } #[inline] pub fn into_owned<'b>(self) -> NamespacedPattern<'b> { NamespacedPattern(Cow::Owned(self.0.into_owned())) } #[inline] pub fn into_qualified(self) -> QualifiedPattern<'a> { self.into() } } impl Deref for NamespacedPattern<'_> { type Target = PatternStr; #[inline] fn deref(&self) -> &Self::Target { self.borrow() } } impl AsRef for NamespacedPattern<'_> { #[inline] fn as_ref(&self) -> &PatternStr { self.0.as_ref() } } impl AsRef for NamespacedPattern<'_> { #[inline] fn as_ref(&self) -> &str { self.0.as_str() } } impl Borrow for NamespacedPattern<'_> { #[inline] fn borrow(&self) -> &PatternStr { PatternStr::from_str(self.0.as_str()) } } impl<'a> From> for QualifiedPattern<'a> { #[inline] fn from(ns: NamespacedPattern<'a>) -> Self { Self(ns.0) } } impl<'a> From<&'a PatternStr> for Option> { fn from(rs: &'a PatternStr) -> Self { let mut cs = rs.iter(); match (cs.next()?, cs.next()?, cs.next()?, cs.next()?) { ("refs", "namespaces", _, "refs") => Some(NamespacedPattern(Cow::from(rs))), _ => None, } } } impl Display for NamespacedPattern<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.0.fmt(f) } }