git_ref_format_core/
name.rs1use 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 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 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 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 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 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}