radicle-git init

Port over all git relevant parts of the radicle-link repository[0].
The packages that are kept are:
* git-ext
* git-ref-format
* git-trailers
* link-git
* macros
* std-ext
* test -- only the portions that test the above

The development and testing scripts are also added for local and CI
testing.

[0]: https://github.com/radicle-dev/radicle-link/
Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
This commit is contained in:
Fintan Halpenny
2022-09-08 12:07:51 +01:00
parent c43420956c
commit 70b571b67b
112 changed files with 10341 additions and 7 deletions
+117
View File
@@ -0,0 +1,117 @@
// 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::convert::TryFrom;
use minicbor::{
decode,
encode::{self, Write},
Decode,
Decoder,
Encode,
Encoder,
};
use crate::{
refspec::{PatternStr, PatternString},
Namespaced,
Qualified,
RefStr,
RefString,
};
impl<'de: 'a, 'a> Decode<'de> for &'a RefStr {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
d.str()
.and_then(|s| Self::try_from(s).map_err(|e| decode::Error::Custom(Box::new(e))))
}
}
impl<'a> Encode for &'a RefStr {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
e.str(self.as_str())?;
Ok(())
}
}
impl<'de> Decode<'de> for RefString {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
Decode::decode(d).map(|s: &RefStr| s.to_owned())
}
}
impl Encode for RefString {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
self.as_refstr().encode(e)
}
}
impl<'de: 'a, 'a> Decode<'de> for &'a PatternStr {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
d.str()
.and_then(|s| Self::try_from(s).map_err(|e| decode::Error::Custom(Box::new(e))))
}
}
impl<'a> Encode for &'a PatternStr {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
e.str(self.as_str())?;
Ok(())
}
}
impl<'de> Decode<'de> for PatternString {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
Decode::decode(d).map(|s: &PatternStr| s.to_owned())
}
}
impl Encode for PatternString {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
self.as_pattern_str().encode(e)
}
}
impl<'de: 'a, 'a> Decode<'de> for Qualified<'a> {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
Decode::decode(d).and_then(|s: &RefStr| {
s.qualified()
.ok_or(decode::Error::Message("not a qualified ref"))
})
}
}
impl<'a> Encode for Qualified<'a> {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
self.as_str().encode(e)
}
}
impl<'de: 'a, 'a> Decode<'de> for Namespaced<'a> {
#[inline]
fn decode(d: &mut Decoder<'de>) -> Result<Self, decode::Error> {
Decode::decode(d).and_then(|s: &RefStr| {
s.namespaced()
.ok_or(decode::Error::Message("not a namespaced ref"))
})
}
}
impl<'a> Encode for Namespaced<'a> {
#[inline]
fn encode<W: Write>(&self, e: &mut Encoder<W>) -> Result<(), encode::Error<W::Error>> {
self.as_str().encode(e)
}
}
+106
View File
@@ -0,0 +1,106 @@
// 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 thiserror::Error;
pub struct Options {
/// If `false`, the refname must contain at least one `/`.
pub allow_onelevel: bool,
/// If `true`, the refname may contain exactly one `*` character.
pub allow_pattern: bool,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
#[error("empty input")]
Empty,
#[error("lone '@' character")]
LoneAt,
#[error("consecutive or trailing slash")]
Slash,
#[error("ends with '.lock'")]
DotLock,
#[error("consecutive dots ('..')")]
DotDot,
#[error("at-open-brace ('@{{')")]
AtOpenBrace,
#[error("invalid character {0:?}")]
InvalidChar(char),
#[error("component starts with '.'")]
StartsDot,
#[error("component ends with '.'")]
EndsDot,
#[error("control character")]
Control,
#[error("whitespace")]
Space,
#[error("must contain at most one '*'")]
Pattern,
#[error("must contain at least one '/'")]
OneLevel,
}
/// Validate that a string slice is a valid refname.
pub fn ref_format(opts: Options, s: &str) -> Result<(), Error> {
match s {
"" => Err(Error::Empty),
"@" => Err(Error::LoneAt),
"." => Err(Error::StartsDot),
_ => {
let mut globs = 0usize;
let mut parts = 0usize;
for x in s.split('/') {
if x.is_empty() {
return Err(Error::Slash);
}
parts += 1;
if x.ends_with(".lock") {
return Err(Error::DotLock);
}
let last_char = x.chars().count() - 1;
for (i, y) in x.chars().zip(x.chars().cycle().skip(1)).enumerate() {
match y {
('.', '.') => return Err(Error::DotDot),
('@', '{') => return Err(Error::AtOpenBrace),
('\0', _) => return Err(Error::InvalidChar('\0')),
('\\', _) => return Err(Error::InvalidChar('\\')),
('~', _) => return Err(Error::InvalidChar('~')),
('^', _) => return Err(Error::InvalidChar('^')),
(':', _) => return Err(Error::InvalidChar(':')),
('?', _) => return Err(Error::InvalidChar('?')),
('[', _) => return Err(Error::InvalidChar('[')),
('*', _) => globs += 1,
('.', _) if i == 0 => return Err(Error::StartsDot),
('.', _) if i == last_char => return Err(Error::EndsDot),
(' ', _) => return Err(Error::Space),
(z, _) if z.is_ascii_control() => return Err(Error::Control),
_ => continue,
}
}
}
if parts < 2 && !opts.allow_onelevel {
Err(Error::OneLevel)
} else if globs > 1 && opts.allow_pattern {
Err(Error::Pattern)
} else if globs > 0 && !opts.allow_pattern {
Err(Error::InvalidChar('*'))
} else {
Ok(())
}
},
}
}
+437
View File
@@ -0,0 +1,437 @@
// 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::Cow,
fmt::{self, Display},
ops::Deref,
};
use crate::{lit, name, Component, RefStr, RefString};
/// A fully-qualified refname.
///
/// A refname is qualified _iff_ it starts with "refs/" and has at least three
/// components. This implies that a [`Qualified`] ref has a category, such as
/// "refs/heads/main".
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Qualified<'a>(pub(crate) Cow<'a, RefStr>);
impl<'a> Qualified<'a> {
/// Infallibly create a [`Qualified`] from components.
///
/// Note that the "refs/" prefix is implicitly added, so `a` is the second
/// [`Component`]. Mirroring [`Self::non_empty_components`], providing
/// two [`Component`]s guarantees well-formedness of the [`Qualified`].
/// `tail` may be empty.
///
/// # Example
///
/// ```no_run
/// use git_ref_format::{component, Qualified};
///
/// assert_eq!(
/// "refs/heads/main",
/// Qualified::from_components(component::HEADS, component::MAIN, None).as_str()
/// )
/// ```
pub fn from_components<'b, 'c, 'd, A, B, C>(a: A, b: B, tail: C) -> Self
where
A: Into<Component<'b>>,
B: Into<Component<'c>>,
C: IntoIterator<Item = Component<'d>>,
{
let mut inner = name::REFS.join(a.into()).and(b.into());
inner.extend(tail);
Self(inner.into())
}
pub fn from_refstr(r: impl Into<Cow<'a, RefStr>>) -> Option<Self> {
Self::_from_refstr(r.into())
}
fn _from_refstr(r: Cow<'a, RefStr>) -> Option<Self> {
let mut iter = r.iter();
match (iter.next()?, iter.next()?, iter.next()?) {
("refs", _, _) => Some(Qualified(r)),
_ => None,
}
}
#[inline]
pub fn as_str(&self) -> &str {
self.as_ref()
}
#[inline]
pub fn join<'b, R>(&self, other: R) -> Qualified<'b>
where
R: AsRef<RefStr>,
{
Qualified(self.0.join(other).into())
}
#[inline]
pub fn namespaced(&self) -> Option<Namespaced> {
self.0.as_ref().into()
}
/// Add a namespace.
///
/// Creates a new [`Namespaced`] by prefxing `self` with
/// "refs/namespaces/<ns>".
pub fn add_namespace<'b>(&self, ns: Component<'b>) -> Namespaced<'a> {
Namespaced(Cow::Owned(
IntoIterator::into_iter([lit::Refs.into(), lit::Namespaces.into(), ns])
.chain(self.0.components())
.collect(),
))
}
/// Like [`Self::non_empty_components`], but with string slices.
pub fn non_empty_iter(&self) -> (&str, &str, &str, name::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, name::Components) {
let mut cs = self.components();
(
cs.next().unwrap(),
cs.next().unwrap(),
cs.next().unwrap(),
cs,
)
}
#[inline]
pub fn to_owned<'b>(&self) -> Qualified<'b> {
Qualified(Cow::Owned(self.0.clone().into_owned()))
}
#[inline]
pub fn into_owned<'b>(self) -> Qualified<'b> {
Qualified(Cow::Owned(self.0.into_owned()))
}
#[inline]
pub fn into_refstring(self) -> RefString {
self.into()
}
}
impl Deref for Qualified<'_> {
type Target = RefStr;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<RefStr> for Qualified<'_> {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl AsRef<str> for Qualified<'_> {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl AsRef<Self> for Qualified<'_> {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
impl<'a> From<Qualified<'a>> for Cow<'a, RefStr> {
#[inline]
fn from(q: Qualified<'a>) -> Self {
q.0
}
}
impl From<Qualified<'_>> for RefString {
#[inline]
fn from(q: Qualified) -> Self {
q.0.into_owned()
}
}
impl<T, U> From<(lit::Refs, T, U)> for Qualified<'_>
where
T: AsRef<RefStr>,
U: AsRef<RefStr>,
{
#[inline]
fn from((refs, cat, name): (lit::Refs, T, U)) -> Self {
let refs: &RefStr = refs.into();
Self(Cow::Owned(refs.join(cat).and(name)))
}
}
impl<T> From<lit::RefsHeads<T>> for Qualified<'_>
where
T: AsRef<RefStr>,
{
#[inline]
fn from((refs, heads, name): lit::RefsHeads<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), heads.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl<T> From<lit::RefsTags<T>> for Qualified<'_>
where
T: AsRef<RefStr>,
{
#[inline]
fn from((refs, tags, name): lit::RefsTags<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), tags.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl<T> From<lit::RefsNotes<T>> for Qualified<'_>
where
T: AsRef<RefStr>,
{
#[inline]
fn from((refs, notes, name): lit::RefsNotes<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), notes.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl<T> From<lit::RefsRemotes<T>> for Qualified<'_>
where
T: AsRef<RefStr>,
{
#[inline]
fn from((refs, remotes, name): lit::RefsRemotes<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), remotes.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl Display for Qualified<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
#[cfg(feature = "link-literals")]
mod link {
use super::*;
impl From<lit::RefsRadId> for Qualified<'_> {
#[inline]
fn from((refs, rad, id): lit::RefsRadId) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), rad.into(), id.into()]).collect(),
))
}
}
impl From<lit::RefsRadSelf> for Qualified<'_> {
#[inline]
fn from((refs, rad, selv): lit::RefsRadSelf) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), rad.into(), selv.into()]).collect(),
))
}
}
impl From<lit::RefsRadSignedRefs> for Qualified<'_> {
#[inline]
fn from((refs, rad, sig): lit::RefsRadSignedRefs) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), rad.into(), sig.into()]).collect(),
))
}
}
impl<'a, T: Into<Component<'a>>> From<lit::RefsRadIds<T>> for Qualified<'_> {
#[inline]
fn from((refs, rad, ids, id): lit::RefsRadIds<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), rad.into(), ids.into(), id.into()])
.collect(),
))
}
}
impl<'a, T: Into<Component<'a>>, I: Into<Component<'a>>> From<lit::RefsCobs<T, I>>
for Qualified<'_>
{
#[inline]
fn from((refs, cobs, ty, id): lit::RefsCobs<T, I>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([Component::from(refs), cobs.into(), ty.into(), id.into()])
.collect(),
))
}
}
}
/// A [`Qualified`] 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
/// [`Namespaced::strip_namespace`] may be convertible to a [`Namespaced`]
/// again. For example:
///
/// ```no_run
/// let full = refname!("refs/namespaces/a/refs/namespaces/b/refs/heads/main");
/// let namespaced = full.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/main", strip_first.as_str());
/// assert_eq!("refs/heads/main", strip_second.as_str());
/// ```
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Namespaced<'a>(Cow<'a, RefStr>);
impl<'a> Namespaced<'a> {
pub fn namespace(&self) -> Component {
self.components().nth(2).unwrap()
}
pub fn strip_namespace<'b>(&self) -> Qualified<'b> {
const REFS_NAMESPACES: &RefStr = RefStr::from_str("refs/namespaces");
Qualified(Cow::Owned(
self.strip_prefix(REFS_NAMESPACES)
.unwrap()
.components()
.skip(1)
.collect(),
))
}
pub fn strip_namespace_recursive<'b>(&self) -> Qualified<'b> {
let mut strip = self.strip_namespace();
while let Some(ns) = strip.namespaced() {
strip = ns.strip_namespace();
}
strip
}
#[inline]
pub fn to_owned<'b>(&self) -> Namespaced<'b> {
Namespaced(Cow::Owned(self.0.clone().into_owned()))
}
#[inline]
pub fn into_owned<'b>(self) -> Namespaced<'b> {
Namespaced(Cow::Owned(self.0.into_owned()))
}
#[inline]
pub fn into_qualified(self) -> Qualified<'a> {
self.into()
}
}
impl Deref for Namespaced<'_> {
type Target = RefStr;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<RefStr> for Namespaced<'_> {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl AsRef<str> for Namespaced<'_> {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl<'a> From<Namespaced<'a>> for Qualified<'a> {
#[inline]
fn from(ns: Namespaced<'a>) -> Self {
Self(ns.0)
}
}
impl<'a> From<&'a RefStr> for Option<Namespaced<'a>> {
fn from(rs: &'a RefStr) -> Self {
let mut cs = rs.iter();
match (cs.next()?, cs.next()?, cs.next()?, cs.next()?) {
("refs", "namespaces", _, "refs") => Some(Namespaced(Cow::from(rs))),
_ => None,
}
}
}
impl<'a, T> From<lit::RefsNamespaces<'_, T>> for Namespaced<'static>
where
T: Into<Component<'a>>,
{
#[inline]
fn from((refs, namespaces, namespace, name): lit::RefsNamespaces<T>) -> Self {
Self(Cow::Owned(
IntoIterator::into_iter([refs.into(), namespaces.into(), namespace.into()])
.collect::<RefString>()
.and(name),
))
}
}
impl Display for Namespaced<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
+25
View File
@@ -0,0 +1,25 @@
// 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.
mod check;
pub use check::{ref_format as check_ref_format, Error, Options};
mod deriv;
pub use deriv::{Namespaced, Qualified};
pub mod lit;
pub mod name;
#[cfg(feature = "percent-encoding")]
pub use name::PercentEncode;
pub use name::{Component, RefStr, RefString};
pub mod refspec;
pub use refspec::DuplicateGlob;
#[cfg(feature = "minicbor")]
mod cbor;
#[cfg(feature = "serde")]
mod serde;
+313
View File
@@ -0,0 +1,313 @@
// 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 crate::{name, Qualified, RefStr};
/// A literal [`RefStr`].
///
/// Types implementing [`Lit`] must be [`name::Component`]s, and provide a
/// conversion from a component _iff_ the component's [`RefStr`] representation
/// is equal to [`Lit::NAME`]. Because these morphisms can only be guaranteed
/// axiomatically, the trait can not currently be implemented by types outside
/// of this crate.
///
/// [`Lit`] types are useful for efficiently creating known-valid [`Qualified`]
/// refs, and sometimes for pattern matching.
pub trait Lit: Sized + sealed::Sealed {
const SELF: Self;
const NAME: &'static RefStr;
#[inline]
fn from_component(c: &name::Component) -> Option<Self> {
(c.as_ref() == Self::NAME).then(|| Self::SELF)
}
}
impl<T: Lit> From<T> for &'static RefStr {
#[inline]
fn from(_: T) -> Self {
T::NAME
}
}
mod sealed {
pub trait Sealed {}
}
/// All known literal [`RefStr`]s.
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub enum KnownLit {
Refs,
Heads,
Namespaces,
Remotes,
Tags,
Notes,
#[cfg(feature = "link-literals")]
Rad,
#[cfg(feature = "link-literals")]
Id,
#[cfg(feature = "link-literals")]
Ids,
#[cfg(feature = "link-literals")]
Selv,
#[cfg(feature = "link-literals")]
SignedRefs,
#[cfg(feature = "link-literals")]
Cobs,
}
impl KnownLit {
#[inline]
pub fn from_component(c: &name::Component) -> Option<Self> {
let c: &RefStr = c.as_ref();
if c == Refs::NAME {
Some(Self::Refs)
} else if c == Heads::NAME {
Some(Self::Heads)
} else if c == Namespaces::NAME {
Some(Self::Namespaces)
} else if c == Remotes::NAME {
Some(Self::Remotes)
} else if c == Tags::NAME {
Some(Self::Tags)
} else if c == Notes::NAME {
Some(Self::Notes)
} else {
#[cfg(feature = "link-literals")]
{
if c == Rad::NAME {
Some(Self::Rad)
} else if c == Id::NAME {
Some(Self::Id)
} else if c == Ids::NAME {
Some(Self::Ids)
} else if c == Selv::NAME {
Some(Self::Selv)
} else if c == SignedRefs::NAME {
Some(Self::SignedRefs)
} else if c == Cobs::NAME {
Some(Self::Cobs)
} else {
None
}
}
#[cfg(not(feature = "link-literals"))]
None
}
}
}
impl From<KnownLit> for name::Component<'_> {
#[inline]
fn from(k: KnownLit) -> Self {
match k {
KnownLit::Refs => Refs.into(),
KnownLit::Heads => Heads.into(),
KnownLit::Namespaces => Namespaces.into(),
KnownLit::Remotes => Remotes.into(),
KnownLit::Tags => Tags.into(),
KnownLit::Notes => Notes.into(),
#[cfg(feature = "link-literals")]
KnownLit::Rad => Rad.into(),
#[cfg(feature = "link-literals")]
KnownLit::Id => Id.into(),
#[cfg(feature = "link-literals")]
KnownLit::Ids => Ids.into(),
#[cfg(feature = "link-literals")]
KnownLit::Selv => Selv.into(),
#[cfg(feature = "link-literals")]
KnownLit::SignedRefs => SignedRefs.into(),
#[cfg(feature = "link-literals")]
KnownLit::Cobs => Cobs.into(),
}
}
}
/// Either a [`KnownLit`] or a [`name::Component`]
pub enum SomeLit<'a> {
Known(KnownLit),
Any(name::Component<'a>),
}
impl SomeLit<'_> {
pub fn known(self) -> Option<KnownLit> {
match self {
Self::Known(k) => Some(k),
_ => None,
}
}
}
impl<'a> From<name::Component<'a>> for SomeLit<'a> {
#[inline]
fn from(c: name::Component<'a>) -> Self {
match KnownLit::from_component(&c) {
Some(k) => Self::Known(k),
None => Self::Any(c),
}
}
}
pub type RefsHeads<T> = (Refs, Heads, T);
pub type RefsTags<T> = (Refs, Tags, T);
pub type RefsNotes<T> = (Refs, Notes, T);
pub type RefsRemotes<T> = (Refs, Remotes, T);
pub type RefsNamespaces<'a, T> = (Refs, Namespaces, T, Qualified<'a>);
#[inline]
pub fn refs_heads<T: AsRef<RefStr>>(name: T) -> RefsHeads<T> {
(Refs, Heads, name)
}
#[inline]
pub fn refs_tags<T: AsRef<RefStr>>(name: T) -> RefsTags<T> {
(Refs, Tags, name)
}
#[inline]
pub fn refs_notes<T: AsRef<RefStr>>(name: T) -> RefsNotes<T> {
(Refs, Notes, name)
}
#[inline]
pub fn refs_remotes<T: AsRef<RefStr>>(name: T) -> RefsRemotes<T> {
(Refs, Remotes, name)
}
#[inline]
pub fn refs_namespaces<'a, 'b, T>(namespace: T, name: Qualified<'b>) -> RefsNamespaces<'b, T>
where
T: Into<name::Component<'a>>,
{
(Refs, Namespaces, namespace, name)
}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Refs;
impl Lit for Refs {
const SELF: Self = Self;
const NAME: &'static RefStr = name::REFS;
}
impl sealed::Sealed for Refs {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Heads;
impl Lit for Heads {
const SELF: Self = Self;
const NAME: &'static RefStr = name::HEADS;
}
impl sealed::Sealed for Heads {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Namespaces;
impl Lit for Namespaces {
const SELF: Self = Self;
const NAME: &'static RefStr = name::NAMESPACES;
}
impl sealed::Sealed for Namespaces {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Remotes;
impl Lit for Remotes {
const SELF: Self = Self;
const NAME: &'static RefStr = name::REMOTES;
}
impl sealed::Sealed for Remotes {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Tags;
impl Lit for Tags {
const SELF: Self = Self;
const NAME: &'static RefStr = name::TAGS;
}
impl sealed::Sealed for Tags {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Notes;
impl Lit for Notes {
const SELF: Self = Self;
const NAME: &'static RefStr = name::NOTES;
}
impl sealed::Sealed for Notes {}
#[cfg(feature = "link-literals")]
mod link {
use super::*;
pub type RefsRadId = (Refs, Rad, Id);
pub type RefsRadSelf = (Refs, Rad, Selv);
pub type RefsRadSignedRefs = (Refs, Rad, SignedRefs);
pub type RefsRadIds<T> = (Refs, Rad, Ids, T);
pub type RefsCobs<T, I> = (Refs, Cobs, T, I);
pub const REFS_RAD_ID: (Refs, Rad, Id) = (Refs, Rad, Id);
pub const REFS_RAD_SELF: (Refs, Rad, Selv) = (Refs, Rad, Selv);
pub const REFS_RAD_SIGNED_REFS: (Refs, Rad, SignedRefs) = (Refs, Rad, SignedRefs);
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Rad;
impl Lit for Rad {
const SELF: Self = Self;
const NAME: &'static RefStr = RefStr::from_str("rad");
}
impl sealed::Sealed for Rad {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Id;
impl Lit for Id {
const SELF: Self = Self;
const NAME: &'static RefStr = RefStr::from_str("id");
}
impl sealed::Sealed for Id {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Ids;
impl Lit for Ids {
const SELF: Self = Self;
const NAME: &'static RefStr = RefStr::from_str("ids");
}
impl sealed::Sealed for Ids {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Selv;
impl Lit for Selv {
const SELF: Self = Self;
const NAME: &'static RefStr = RefStr::from_str("self");
}
impl sealed::Sealed for Selv {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct SignedRefs;
impl Lit for SignedRefs {
const SELF: Self = Self;
const NAME: &'static RefStr = RefStr::from_str("signed_refs");
}
impl sealed::Sealed for SignedRefs {}
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Cobs;
impl Lit for Cobs {
const SELF: Self = Self;
const NAME: &'static RefStr = RefStr::from_str("cobs");
}
impl sealed::Sealed for Cobs {}
}
#[cfg(feature = "link-literals")]
pub use link::*;
+522
View File
@@ -0,0 +1,522 @@
// 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";
#[cfg(feature = "link-literals")]
mod link {
pub const RAD: &str = "rad";
pub const ID: &str = "id";
pub const IDS: &str = "ids";
pub const SELF: &str = "self";
pub const SIGNED_REFS: &str = "signed_refs";
pub const COBS: &str = "cobs";
pub const REFS_RAD_ID: &str = "refs/rad/id";
pub const REFS_RAD_SELF: &str = "refs/rad/self";
pub const REFS_RAD_SIGNED_REFS: &str = "refs/rad/signed_refs";
}
#[cfg(feature = "link-literals")]
pub use link::*;
}
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();
#[cfg(feature = "link-literals")]
mod link {
use super::str;
pub const RAD: &[u8] = str::RAD.as_bytes();
pub const ID: &[u8] = str::ID.as_bytes();
pub const IDS: &[u8] = str::IDS.as_bytes();
pub const SELF: &[u8] = str::SELF.as_bytes();
pub const SIGNED_REFS: &[u8] = str::SIGNED_REFS.as_bytes();
pub const COBS: &[u8] = str::COBS.as_bytes();
pub const REFS_RAD_ID: &[u8] = str::REFS_RAD_ID.as_bytes();
pub const REFS_RAD_SELF: &[u8] = str::REFS_RAD_SELF.as_bytes();
pub const REFS_RAD_SIGNED_REFS: &[u8] = str::REFS_RAD_SIGNED_REFS.as_bytes();
}
#[cfg(feature = "link-literals")]
pub use link::*;
}
#[cfg(feature = "link-literals")]
mod link {
use super::{str, RefStr};
pub const RAD: &RefStr = RefStr::from_str(str::RAD);
pub const ID: &RefStr = RefStr::from_str(str::ID);
pub const IDS: &RefStr = RefStr::from_str(str::IDS);
pub const SELF: &RefStr = RefStr::from_str(str::SELF);
pub const SIGNED_REFS: &RefStr = RefStr::from_str(str::SIGNED_REFS);
pub const COBS: &RefStr = RefStr::from_str(str::COBS);
pub const REFS_RAD_ID: &RefStr = RefStr::from_str(str::REFS_RAD_ID);
pub const REFS_RAD_SELF: &RefStr = RefStr::from_str(str::REFS_RAD_SELF);
pub const REFS_RAD_SIGNED_REFS: &RefStr = RefStr::from_str(str::REFS_RAD_SIGNED_REFS);
}
#[cfg(feature = "link-literals")]
pub use link::*;
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<P>(&self, base: P) -> Option<&RefStr>
where
P: AsRef<RefStr>,
{
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<R>(&self, other: R) -> RefString
where
R: AsRef<RefStr>,
{
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<P>(&self, pattern: P) -> PatternString
where
P: AsRef<PatternStr>,
{
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> {
Qualified::from_refstr(self)
}
#[inline]
pub fn namespaced(&self) -> Option<Namespaced> {
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<str> for RefStr {
#[inline]
fn as_ref(&self) -> &str {
self
}
}
#[cfg(feature = "bstr")]
impl AsRef<bstr::BStr> for RefStr {
#[inline]
fn as_ref(&self) -> &bstr::BStr {
use bstr::ByteSlice as _;
self.as_str().as_bytes().as_bstr()
}
}
impl<'a> AsRef<RefStr> 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<Self, Self::Error> {
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 neede.
pub fn and<R>(self, other: R) -> Self
where
R: AsRef<RefStr>,
{
self._and(other.as_ref())
}
fn _and(mut self, other: &RefStr) -> Self {
self.push(other);
self
}
pub fn push<R>(&mut self, other: R)
where
R: AsRef<RefStr>,
{
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<P>(self, pattern: P) -> PatternString
where
P: AsRef<PatternStr>,
{
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<'a>> {
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<RefStr> for RefString {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl AsRef<str> for RefString {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
#[cfg(feature = "bstr")]
impl AsRef<bstr::BStr> for RefString {
#[inline]
fn as_ref(&self) -> &bstr::BStr {
use bstr::ByteSlice as _;
self.as_str().as_bytes().as_bstr()
}
}
impl Borrow<RefStr> 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<Self, Self::Error> {
RefStr::try_from_str(s).map(ToOwned::to_owned)
}
}
impl TryFrom<String> for RefString {
type Error = check::Error;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> {
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<RefString> for Cow<'a, RefStr> {
#[inline]
fn from(rs: RefString) -> Cow<'a, RefStr> {
Cow::Owned(rs)
}
}
impl From<RefString> for String {
#[inline]
fn from(rs: RefString) -> Self {
rs.0
}
}
#[cfg(feature = "bstr")]
impl From<RefString> 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<A> FromIterator<A> for RefString
where
A: AsRef<RefStr>,
{
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = A>,
{
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<A> Extend<A> for RefString
where
A: AsRef<RefStr>,
{
fn extend<T>(&mut self, iter: T)
where
T: IntoIterator<Item = A>,
{
for x in iter {
self.push(x)
}
}
}
+162
View File
@@ -0,0 +1,162 @@
// 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::Cow,
fmt::{self, Display},
ops::Deref,
};
use super::{RefStr, RefString};
use crate::lit;
pub type Iter<'a> = std::str::Split<'a, char>;
/// Iterator created by the [`RefStr::iter`] method.
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Clone)]
pub struct Components<'a> {
inner: std::str::Split<'a, char>,
}
impl<'a> Iterator for Components<'a> {
type Item = Component<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(RefStr::from_str)
.map(Cow::from)
.map(Component)
}
}
impl<'a> From<&'a RefStr> for Components<'a> {
#[inline]
fn from(rs: &'a RefStr) -> Self {
Self {
inner: rs.as_str().split('/'),
}
}
}
/// A path component of a [`RefStr`].
///
/// A [`Component`] is a valid [`RefStr`] which does not contain any '/'
/// separators.
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Component<'a>(Cow<'a, RefStr>);
impl<'a> Component<'a> {
#[inline]
pub fn from_refstring(r: RefString) -> Option<Self> {
if !r.contains('/') {
Some(Self(Cow::Owned(r)))
} else {
None
}
}
#[inline]
pub fn as_lit<T: lit::Lit>(&self) -> Option<T> {
T::from_component(self)
}
#[inline]
pub fn into_inner(self) -> Cow<'a, RefStr> {
self.0
}
}
impl<'a> Deref for Component<'a> {
type Target = RefStr;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<RefStr> for Component<'_> {
#[inline]
fn as_ref(&self) -> &RefStr {
self
}
}
impl<'a> From<&'a RefStr> for Option<Component<'a>> {
#[inline]
fn from(r: &'a RefStr) -> Self {
if !r.contains('/') {
Some(Component(Cow::from(r)))
} else {
None
}
}
}
impl<'a> From<Component<'a>> for Cow<'a, RefStr> {
#[inline]
fn from(c: Component<'a>) -> Self {
c.0
}
}
impl<T: lit::Lit> From<T> for Component<'static> {
#[inline]
fn from(_: T) -> Self {
Component(Cow::from(T::NAME))
}
}
impl<'a> From<lit::SomeLit<'a>> for Component<'a> {
#[inline]
fn from(s: lit::SomeLit<'a>) -> Self {
use lit::SomeLit::*;
match s {
Known(k) => k.into(),
Any(c) => c,
}
}
}
impl Display for Component<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.0.as_str())
}
}
pub mod component {
use super::Component;
use crate::name;
use std::borrow::Cow;
pub const HEADS: Component = Component(Cow::Borrowed(name::HEADS));
pub const MAIN: Component = Component(Cow::Borrowed(name::MAIN));
pub const MASTER: Component = Component(Cow::Borrowed(name::MASTER));
pub const NAMESPACES: Component = Component(Cow::Borrowed(name::NAMESPACES));
pub const NOTES: Component = Component(Cow::Borrowed(name::NOTES));
pub const ORIGIN: Component = Component(Cow::Borrowed(name::ORIGIN));
pub const REFS: Component = Component(Cow::Borrowed(name::REFS));
pub const REMOTES: Component = Component(Cow::Borrowed(name::REMOTES));
pub const TAGS: Component = Component(Cow::Borrowed(name::TAGS));
#[cfg(feature = "link-literals")]
mod link {
use super::*;
pub const RAD: Component = Component(Cow::Borrowed(name::RAD));
pub const ID: Component = Component(Cow::Borrowed(name::ID));
pub const IDS: Component = Component(Cow::Borrowed(name::IDS));
pub const SELF: Component = Component(Cow::Borrowed(name::SELF));
pub const SIGNED_REFS: Component = Component(Cow::Borrowed(name::SIGNED_REFS));
pub const COBS: Component = Component(Cow::Borrowed(name::COBS));
}
#[cfg(feature = "link-literals")]
pub use link::*;
}
+294
View File
@@ -0,0 +1,294 @@
// 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, RefStr, RefString};
mod iter;
pub use iter::{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<R>(&self, other: R) -> PatternString
where
R: AsRef<RefStr>,
{
self._join(other.as_ref())
}
fn _join(&self, other: &RefStr) -> PatternString {
let mut buf = self.to_owned();
buf.push(other);
buf
}
#[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<str> for PatternStr {
#[inline]
fn as_ref(&self) -> &str {
self
}
}
impl AsRef<Self> 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<Self, Self::Error> {
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<Self, DuplicateGlob>
where
T: IntoIterator<Item = Component<'a>>,
{
iter.into_iter().collect()
}
#[inline]
pub fn and<R>(mut self, other: R) -> Self
where
R: AsRef<RefStr>,
{
self._push(other.as_ref());
self
}
#[inline]
pub fn push<R>(&mut self, other: R)
where
R: AsRef<RefStr>,
{
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<PatternStr> for PatternString {
#[inline]
fn as_ref(&self) -> &PatternStr {
self
}
}
impl AsRef<str> for PatternString {
#[inline]
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl Borrow<PatternStr> 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<RefString> 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<PatternString> 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<Self, Self::Error> {
PatternStr::try_from_str(s).map(ToOwned::to_owned)
}
}
impl TryFrom<String> for PatternString {
type Error = check::Error;
#[inline]
fn try_from(s: String) -> Result<Self, Self::Error> {
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<Component<'a>> for Result<PatternString, DuplicateGlob> {
fn from_iter<T>(iter: T) -> Self
where
T: IntoIterator<Item = Component<'a>>,
{
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)
}
}
+69
View File
@@ -0,0 +1,69 @@
// 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::fmt::{self, Display};
use super::PatternStr;
use crate::RefStr;
pub type Iter<'a> = std::str::Split<'a, char>;
pub enum Component<'a> {
Glob(Option<&'a PatternStr>),
Normal(&'a RefStr),
}
impl Component<'_> {
#[inline]
pub fn as_str(&self) -> &str {
self.as_ref()
}
}
impl AsRef<str> for Component<'_> {
#[inline]
fn as_ref(&self) -> &str {
match self {
Self::Glob(None) => "*",
Self::Glob(Some(x)) => x.as_str(),
Self::Normal(x) => x.as_str(),
}
}
}
impl Display for Component<'_> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[derive(Clone)]
pub struct Components<'a> {
inner: Iter<'a>,
}
impl<'a> Iterator for Components<'a> {
type Item = Component<'a>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|next| match next {
"*" => Component::Glob(None),
x if x.contains('*') => Component::Glob(Some(PatternStr::from_str(x))),
x => Component::Normal(RefStr::from_str(x)),
})
}
}
impl<'a> From<&'a PatternStr> for Components<'a> {
#[inline]
fn from(p: &'a PatternStr) -> Self {
Self {
inner: p.as_str().split('/'),
}
}
}
+144
View File
@@ -0,0 +1,144 @@
// 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::convert::TryFrom;
use ::serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use crate::{
refspec::{PatternStr, PatternString},
Namespaced,
Qualified,
RefStr,
RefString,
};
impl<'de: 'a, 'a> Deserialize<'de> for &'a RefStr {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
.and_then(|s: &str| Self::try_from(s).map_err(de::Error::custom))
}
}
impl<'a> Serialize for &'a RefStr {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for RefString {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).map(|x: &RefStr| x.to_owned())
}
}
impl Serialize for RefString {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de: 'a, 'a> Deserialize<'de> for &'a PatternStr {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer)
.and_then(|s: &str| Self::try_from(s).map_err(de::Error::custom))
}
}
impl<'a> Serialize for &'a PatternStr {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for PatternString {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).map(|x: &PatternStr| x.to_owned())
}
}
impl Serialize for PatternString {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de: 'a, 'a> Deserialize<'de> for Qualified<'a> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).and_then(|s: &RefStr| {
s.qualified()
.ok_or_else(|| de::Error::custom("not a qualified ref"))
})
}
}
impl Serialize for Qualified<'_> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de: 'a, 'a> Deserialize<'de> for Namespaced<'a> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Deserialize::deserialize(deserializer).and_then(|s: &RefStr| {
s.namespaced()
.ok_or_else(|| de::Error::custom("not a namespaced ref"))
})
}
}
impl Serialize for Namespaced<'_> {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.as_str())
}
}