radicle_surf/
namespace.rs1use std::{
19 convert::TryFrom,
20 fmt,
21 str::{self, FromStr},
22};
23
24use git_ext::ref_format::{
25 self,
26 refspec::{NamespacedPattern, PatternString, QualifiedPattern},
27 Component, Namespaced, Qualified, RefStr, RefString,
28};
29use nonempty::NonEmpty;
30use thiserror::Error;
31
32#[derive(Debug, Error)]
33pub enum Error {
34 #[error("namespaces must not be empty")]
37 EmptyNamespace,
38 #[error(transparent)]
39 RefFormat(#[from] ref_format::Error),
40 #[error(transparent)]
41 Utf8(#[from] str::Utf8Error),
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
54pub struct Namespace {
55 pub(super) namespaces: RefString,
58}
59
60impl Namespace {
61 pub(crate) fn to_namespaced<'a>(&self, name: &Qualified<'a>) -> Namespaced<'a> {
75 let mut components = self.namespaces.components().rev();
76 let mut namespaced = name.with_namespace(
77 components
78 .next()
79 .expect("BUG: 'namespaces' cannot be empty"),
80 );
81 for ns in components {
82 let qualified = namespaced.into_qualified();
83 namespaced = qualified.with_namespace(ns);
84 }
85 namespaced
86 }
87
88 pub(crate) fn to_namespaced_pattern<'a>(
102 &self,
103 pat: &QualifiedPattern<'a>,
104 ) -> NamespacedPattern<'a> {
105 let pattern = PatternString::from(self.namespaces.clone());
106 let mut components = pattern.components().rev();
107 let mut namespaced = pat
108 .with_namespace(
109 components
110 .next()
111 .expect("BUG: 'namespaces' cannot be empty"),
112 )
113 .expect("BUG: 'namespace' cannot have globs");
114 for ns in components {
115 let qualified = namespaced.into_qualified();
116 namespaced = qualified
117 .with_namespace(ns)
118 .expect("BUG: 'namespaces' cannot have globs");
119 }
120 namespaced
121 }
122}
123
124impl fmt::Display for Namespace {
125 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126 write!(f, "{}", self.namespaces)
127 }
128}
129
130impl<'a> From<NonEmpty<Component<'a>>> for Namespace {
131 fn from(cs: NonEmpty<Component<'a>>) -> Self {
132 Self {
133 namespaces: cs.into_iter().collect::<RefString>(),
134 }
135 }
136}
137
138impl TryFrom<&str> for Namespace {
139 type Error = Error;
140
141 fn try_from(name: &str) -> Result<Self, Self::Error> {
142 Self::from_str(name)
143 }
144}
145
146impl TryFrom<&[u8]> for Namespace {
147 type Error = Error;
148
149 fn try_from(namespace: &[u8]) -> Result<Self, Self::Error> {
150 str::from_utf8(namespace)
151 .map_err(Error::from)
152 .and_then(Self::from_str)
153 }
154}
155
156impl FromStr for Namespace {
157 type Err = Error;
158
159 fn from_str(name: &str) -> Result<Self, Self::Err> {
160 let namespaces = RefStr::try_from_str(name)?.to_ref_string();
161 Ok(Self { namespaces })
162 }
163}
164
165impl From<Namespaced<'_>> for Namespace {
166 fn from(namespaced: Namespaced<'_>) -> Self {
167 let mut namespaces = namespaced.namespace().to_ref_string();
168 let mut qualified = namespaced.strip_namespace();
169 while let Some(namespaced) = qualified.to_namespaced() {
170 namespaces.push(namespaced.namespace());
171 qualified = namespaced.strip_namespace();
172 }
173 Self { namespaces }
174 }
175}
176
177impl TryFrom<&git2::Reference<'_>> for Namespace {
178 type Error = Error;
179
180 fn try_from(reference: &git2::Reference) -> Result<Self, Self::Error> {
181 let name = RefStr::try_from_str(str::from_utf8(reference.name_bytes())?)?;
182 name.to_namespaced()
183 .ok_or(Error::EmptyNamespace)
184 .map(Self::from)
185 }
186}