Skip to main content

radicle_git_ref_format/
name.rs

1use 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    /// Join `other` onto `self`, yielding a new [`RefString`].
104    ///
105    /// Consider to use [`RefString::and`] when chaining multiple fragments
106    /// together, and the intermediate values are not needed.
107    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        /// https://url.spec.whatwg.org/#fragment-percent-encode-set
156        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        /// https://url.spec.whatwg.org/#path-percent-encode-set
165        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    /// Join `other` onto `self` in place.
250    ///
251    /// This is a consuming version of [`RefString::push`] which can be chained.
252    /// Prefer this over chaining calls to [`RefStr::join`] if the
253    /// intermediate values are not needed.
254    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    /// Append a [`PatternStr`], turning self into a new [`PatternString`].
286    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}