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