radicle_git_ref_format/
name.rs1use std::{
2 borrow::{Borrow, Cow},
3 convert::TryFrom,
4 fmt::{self, Display},
5 iter::{Extend, FromIterator},
6 ops::Deref,
7};
8
9use crate::{
10 Namespaced, Qualified, check,
11 refspec::{PatternStr, PatternString},
12};
13
14mod iter;
15pub use iter::{Component, Components, Iter, component};
16
17#[cfg(feature = "percent-encoding")]
18pub use percent_encoding::PercentEncode;
19
20pub const HEADS: &RefStr = RefStr::from_str(str::HEADS);
21pub const MAIN: &RefStr = RefStr::from_str(str::MAIN);
22pub const MASTER: &RefStr = RefStr::from_str(str::MASTER);
23pub const NAMESPACES: &RefStr = RefStr::from_str(str::NAMESPACES);
24pub const NOTES: &RefStr = RefStr::from_str(str::NOTES);
25pub const ORIGIN: &RefStr = RefStr::from_str(str::ORIGIN);
26pub const REFS: &RefStr = RefStr::from_str(str::REFS);
27pub const REMOTES: &RefStr = RefStr::from_str(str::REMOTES);
28pub const TAGS: &RefStr = RefStr::from_str(str::TAGS);
29
30pub const REFS_HEADS_MAIN: &RefStr = RefStr::from_str(str::REFS_HEADS_MAIN);
31pub const REFS_HEADS_MASTER: &RefStr = RefStr::from_str(str::REFS_HEADS_MASTER);
32
33pub mod str {
34 pub const HEADS: &str = "heads";
35 pub const MAIN: &str = "main";
36 pub const MASTER: &str = "master";
37 pub const NAMESPACES: &str = "namespaces";
38 pub const NOTES: &str = "notes";
39 pub const ORIGIN: &str = "origin";
40 pub const REFS: &str = "refs";
41 pub const REMOTES: &str = "remotes";
42 pub const TAGS: &str = "tags";
43
44 pub const REFS_HEADS_MAIN: &str = "refs/heads/main";
45 pub const REFS_HEADS_MASTER: &str = "refs/heads/master";
46}
47
48pub mod bytes {
49 use super::str;
50
51 pub const HEADS: &[u8] = str::HEADS.as_bytes();
52 pub const MAIN: &[u8] = str::MAIN.as_bytes();
53 pub const MASTER: &[u8] = str::MASTER.as_bytes();
54 pub const NAMESPACES: &[u8] = str::NAMESPACES.as_bytes();
55 pub const NOTES: &[u8] = str::NOTES.as_bytes();
56 pub const ORIGIN: &[u8] = str::ORIGIN.as_bytes();
57 pub const REFS: &[u8] = str::REFS.as_bytes();
58 pub const REMOTES: &[u8] = str::REMOTES.as_bytes();
59 pub const TAGS: &[u8] = str::TAGS.as_bytes();
60
61 pub const REFS_HEADS_MAIN: &[u8] = str::REFS_HEADS_MAIN.as_bytes();
62 pub const REFS_HEADS_MASTER: &[u8] = str::REFS_HEADS_MASTER.as_bytes();
63}
64
65const CHECK_OPTS: check::Options = check::Options {
66 allow_pattern: false,
67 allow_onelevel: true,
68};
69
70#[repr(transparent)]
71#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
72pub struct RefStr(str);
73
74impl RefStr {
75 pub fn try_from_str(s: &str) -> Result<&RefStr, check::Error> {
76 TryFrom::try_from(s)
77 }
78
79 #[inline]
80 pub fn as_str(&self) -> &str {
81 self
82 }
83
84 #[inline]
85 pub fn to_ref_string(&self) -> RefString {
86 self.to_owned()
87 }
88
89 pub fn strip_prefix<P>(&self, base: P) -> Option<&RefStr>
90 where
91 P: AsRef<RefStr>,
92 {
93 self._strip_prefix(base.as_ref())
94 }
95
96 fn _strip_prefix(&self, base: &RefStr) -> Option<&RefStr> {
97 self.0
98 .strip_prefix(base.as_str())
99 .and_then(|s| s.strip_prefix('/'))
100 .map(Self::from_str)
101 }
102
103 pub fn join<R>(&self, other: R) -> RefString
108 where
109 R: AsRef<RefStr>,
110 {
111 self._join(other.as_ref())
112 }
113
114 fn _join(&self, other: &RefStr) -> RefString {
115 let mut buf = self.to_ref_string();
116 buf.push(other);
117 buf
118 }
119
120 pub fn to_pattern<P>(&self, pattern: P) -> PatternString
121 where
122 P: AsRef<PatternStr>,
123 {
124 self._to_pattern(pattern.as_ref())
125 }
126
127 fn _to_pattern(&self, pattern: &PatternStr) -> PatternString {
128 self.to_owned().with_pattern(pattern)
129 }
130
131 #[inline]
132 pub fn qualified<'a>(&'a self) -> Option<Qualified<'a>> {
133 Qualified::from_refstr(self)
134 }
135
136 #[inline]
137 pub fn to_namespaced<'a>(&'a self) -> Option<Namespaced<'a>> {
138 self.into()
139 }
140
141 pub fn iter<'a>(&'a self) -> Iter<'a> {
142 self.0.split('/')
143 }
144
145 pub fn components<'a>(&'a self) -> Components<'a> {
146 Components::from(self)
147 }
148
149 pub fn head<'a>(&'a self) -> Component<'a> {
150 self.components().next().expect("`RefStr` cannot be empty")
151 }
152
153 #[cfg(feature = "percent-encoding")]
154 pub fn percent_encode(&self) -> PercentEncode<'_> {
155 const FRAGMENT_PERCENT_ENCODE_SET: &percent_encoding::AsciiSet =
157 &percent_encoding::CONTROLS
158 .add(b' ')
159 .add(b'"')
160 .add(b'<')
161 .add(b'>')
162 .add(b'`');
163
164 const PATH_PERCENT_ENCODE_SET: &percent_encoding::AsciiSet = &FRAGMENT_PERCENT_ENCODE_SET
166 .add(b'#')
167 .add(b'?')
168 .add(b'{')
169 .add(b'}');
170
171 percent_encoding::utf8_percent_encode(self.as_str(), PATH_PERCENT_ENCODE_SET)
172 }
173
174 #[cfg(feature = "bstr")]
175 #[inline]
176 pub fn as_bstr(&self) -> &bstr::BStr {
177 self.as_ref()
178 }
179
180 pub(crate) const fn from_str(s: &str) -> &RefStr {
181 unsafe { &*(s as *const str as *const RefStr) }
182 }
183}
184
185impl Deref for RefStr {
186 type Target = str;
187
188 #[inline]
189 fn deref(&self) -> &Self::Target {
190 &self.0
191 }
192}
193
194impl AsRef<str> for RefStr {
195 #[inline]
196 fn as_ref(&self) -> &str {
197 self
198 }
199}
200
201#[cfg(feature = "bstr")]
202impl AsRef<bstr::BStr> for RefStr {
203 #[inline]
204 fn as_ref(&self) -> &bstr::BStr {
205 use bstr::ByteSlice as _;
206 self.as_str().as_bytes().as_bstr()
207 }
208}
209
210impl AsRef<RefStr> for &RefStr {
211 #[inline]
212 fn as_ref(&self) -> &RefStr {
213 self
214 }
215}
216
217impl<'a> TryFrom<&'a str> for &'a RefStr {
218 type Error = check::Error;
219
220 #[inline]
221 fn try_from(s: &'a str) -> Result<Self, Self::Error> {
222 check::ref_format(CHECK_OPTS, s).map(|()| RefStr::from_str(s))
223 }
224}
225
226impl<'a> From<&'a RefStr> for Cow<'a, RefStr> {
227 #[inline]
228 fn from(rs: &'a RefStr) -> Cow<'a, RefStr> {
229 Cow::Borrowed(rs)
230 }
231}
232
233impl Display for RefStr {
234 #[inline]
235 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
236 f.write_str(self)
237 }
238}
239
240#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
241pub struct RefString(String);
242
243impl RefString {
244 #[inline]
245 pub fn as_refstr(&self) -> &RefStr {
246 self
247 }
248
249 pub fn and<R>(self, other: R) -> Self
255 where
256 R: AsRef<RefStr>,
257 {
258 self._and(other.as_ref())
259 }
260
261 fn _and(mut self, other: &RefStr) -> Self {
262 self.push(other);
263 self
264 }
265
266 pub fn push<R>(&mut self, other: R)
267 where
268 R: AsRef<RefStr>,
269 {
270 self.0.push('/');
271 self.0.push_str(other.as_ref().as_str());
272 }
273
274 #[inline]
275 pub fn pop(&mut self) -> bool {
276 match self.0.rfind('/') {
277 None => false,
278 Some(idx) => {
279 self.0.truncate(idx);
280 true
281 }
282 }
283 }
284
285 pub fn with_pattern<P>(self, pattern: P) -> PatternString
287 where
288 P: AsRef<PatternStr>,
289 {
290 self._with_pattern(pattern.as_ref())
291 }
292
293 fn _with_pattern(self, pattern: &PatternStr) -> PatternString {
294 let mut buf = self.0;
295 buf.push('/');
296 buf.push_str(pattern.as_str());
297
298 PatternString(buf)
299 }
300
301 #[inline]
302 pub fn into_qualified<'a>(self) -> Option<Qualified<'a>> {
303 Qualified::from_refstr(self)
304 }
305
306 #[inline]
307 pub fn reserve(&mut self, additional: usize) {
308 self.0.reserve(additional)
309 }
310
311 #[inline]
312 pub fn shrink_to_fit(&mut self) {
313 self.0.shrink_to_fit()
314 }
315
316 #[cfg(feature = "bstr")]
317 #[inline]
318 pub fn into_bstring(self) -> bstr::BString {
319 self.into()
320 }
321
322 #[cfg(feature = "bstr")]
323 #[inline]
324 pub fn as_bstr(&self) -> &bstr::BStr {
325 self.as_ref()
326 }
327}
328
329impl Deref for RefString {
330 type Target = RefStr;
331
332 #[inline]
333 fn deref(&self) -> &Self::Target {
334 self.borrow()
335 }
336}
337
338impl AsRef<RefStr> for RefString {
339 #[inline]
340 fn as_ref(&self) -> &RefStr {
341 self
342 }
343}
344
345impl AsRef<str> for RefString {
346 #[inline]
347 fn as_ref(&self) -> &str {
348 self.0.as_str()
349 }
350}
351
352#[cfg(feature = "bstr")]
353impl AsRef<bstr::BStr> for RefString {
354 #[inline]
355 fn as_ref(&self) -> &bstr::BStr {
356 use bstr::ByteSlice as _;
357 self.as_str().as_bytes().as_bstr()
358 }
359}
360
361impl Borrow<RefStr> for RefString {
362 #[inline]
363 fn borrow(&self) -> &RefStr {
364 RefStr::from_str(self.0.as_str())
365 }
366}
367
368impl ToOwned for RefStr {
369 type Owned = RefString;
370
371 #[inline]
372 fn to_owned(&self) -> Self::Owned {
373 RefString(self.0.to_owned())
374 }
375}
376
377impl TryFrom<&str> for RefString {
378 type Error = check::Error;
379
380 #[inline]
381 fn try_from(s: &str) -> Result<Self, Self::Error> {
382 RefStr::try_from_str(s).map(ToOwned::to_owned)
383 }
384}
385
386impl TryFrom<String> for RefString {
387 type Error = check::Error;
388
389 #[inline]
390 fn try_from(s: String) -> Result<Self, Self::Error> {
391 check::ref_format(CHECK_OPTS, s.as_str()).map(|()| RefString(s))
392 }
393}
394
395impl<'a> From<&'a RefString> for Cow<'a, RefStr> {
396 #[inline]
397 fn from(rs: &'a RefString) -> Cow<'a, RefStr> {
398 Cow::Borrowed(rs.as_refstr())
399 }
400}
401
402impl<'a> From<RefString> for Cow<'a, RefStr> {
403 #[inline]
404 fn from(rs: RefString) -> Cow<'a, RefStr> {
405 Cow::Owned(rs)
406 }
407}
408
409impl From<RefString> for String {
410 #[inline]
411 fn from(rs: RefString) -> Self {
412 rs.0
413 }
414}
415
416#[cfg(feature = "bstr")]
417impl From<RefString> for bstr::BString {
418 #[inline]
419 fn from(rs: RefString) -> Self {
420 bstr::BString::from(rs.0.into_bytes())
421 }
422}
423
424impl Display for RefString {
425 #[inline]
426 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
427 f.write_str(&self.0)
428 }
429}
430
431impl<A> FromIterator<A> for RefString
432where
433 A: AsRef<RefStr>,
434{
435 fn from_iter<T>(iter: T) -> Self
436 where
437 T: IntoIterator<Item = A>,
438 {
439 let mut buf = String::new();
440 for c in iter {
441 buf.push_str(c.as_ref().as_str());
442 buf.push('/');
443 }
444 assert!(!buf.is_empty(), "empty iterator");
445 buf.truncate(buf.len() - 1);
446
447 Self(buf)
448 }
449}
450
451impl<A> Extend<A> for RefString
452where
453 A: AsRef<RefStr>,
454{
455 fn extend<T>(&mut self, iter: T)
456 where
457 T: IntoIterator<Item = A>,
458 {
459 for x in iter {
460 self.push(x)
461 }
462 }
463}