git_ref_format_core/
name.rs

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