1use crate::Error;
2#[cfg(feature = "alloc")]
3use alloc::string::String;
4#[cfg(feature = "alloc")]
5use alloc::vec::Vec;
6use core::fmt::{Debug, Formatter};
7use core::hash::Hasher;
8
9use crate::platform::NULL_BYTE;
10use crate::string::strlen::strlen;
11
12#[cfg(feature = "alloc")]
13#[repr(transparent)]
14#[derive(Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
15pub struct UnixString(pub(crate) Vec<u8>);
16
17#[cfg(feature = "alloc")]
18impl Debug for UnixString {
19 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
20 let slice = unsafe { core::slice::from_raw_parts(self.0.as_ptr(), self.0.len()) };
21 match core::str::from_utf8(slice) {
22 Ok(raw) => f.write_fmt(format_args!("UnixString({raw})")),
23 Err(_e) => f.write_fmt(format_args!("UnixString({slice:?})")),
24 }
25 }
26}
27
28#[cfg(feature = "alloc")]
29impl UnixString {
30 #[inline]
31 #[must_use]
32 pub fn as_ptr(&self) -> *const u8 {
33 self.0.as_ptr()
34 }
35
36 #[inline]
40 pub fn try_from_string(s: String) -> Result<Self, Error> {
41 Self::try_from_vec(s.into_bytes())
42 }
43
44 #[inline]
48 pub fn try_from_vec(mut s: Vec<u8>) -> Result<Self, Error> {
49 let len = s.len();
50 for (ind, byte) in s.iter().enumerate() {
51 if *byte == NULL_BYTE {
52 return if ind == len - 1 {
53 unsafe { Ok(core::mem::transmute::<Vec<u8>, Self>(s)) }
54 } else {
55 Err(Error::no_code("Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place"))
56 };
57 }
58 }
59 s.push(0);
60 Ok(Self(s))
61 }
62
63 pub fn try_from_bytes(s: &[u8]) -> Result<Self, Error> {
68 let len = s.len();
69 for (ind, byte) in s.iter().enumerate() {
70 if *byte == NULL_BYTE {
71 return if ind == len - 1 {
72 unsafe { Ok(core::mem::transmute::<Vec<u8>, Self>(s.to_vec())) }
73 } else {
74 Err(Error::no_code("Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place"))
75 };
76 }
77 }
78 let mut new = s.to_vec();
79 new.push(0);
80 Ok(Self(new))
81 }
82
83 #[inline]
88 pub fn try_from_str(s: &str) -> Result<Self, Error> {
89 Self::try_from_bytes(s.as_bytes())
90 }
91
92 #[must_use]
109 pub fn from_format(args: core::fmt::Arguments<'_>) -> Self {
110 let mut fmt_str_buf = alloc::fmt::format(args).into_bytes();
111 if !matches!(fmt_str_buf.last(), Some(&NULL_BYTE)) {
112 fmt_str_buf.push(NULL_BYTE);
113 }
114 UnixString(fmt_str_buf)
115 }
116}
117
118#[cfg(feature = "alloc")]
119impl core::ops::Deref for UnixString {
120 type Target = UnixStr;
121
122 fn deref(&self) -> &Self::Target {
123 unsafe { &*(core::ptr::from_ref::<[u8]>(self.0.as_slice()) as *const UnixStr) }
124 }
125}
126
127#[cfg(feature = "alloc")]
128impl AsRef<UnixStr> for UnixString {
129 #[inline]
130 fn as_ref(&self) -> &UnixStr {
131 unsafe { UnixStr::from_bytes_unchecked(self.0.as_slice()) }
132 }
133}
134
135#[repr(transparent)]
136#[derive(Eq, PartialEq, Ord, PartialOrd)]
137pub struct UnixStr(pub(crate) [u8]);
138
139impl UnixStr {
140 pub const EMPTY: &'static Self = UnixStr::from_str_checked("\0");
141
142 #[inline]
145 #[must_use]
146 pub const unsafe fn from_str_unchecked(s: &str) -> &Self {
147 core::mem::transmute(s)
148 }
149
150 #[inline]
153 #[must_use]
154 pub const unsafe fn from_bytes_unchecked(s: &[u8]) -> &Self {
155 core::mem::transmute(s)
156 }
157
158 #[must_use]
164 pub const fn from_str_checked(s: &str) -> &Self {
165 const_null_term_validate(s.as_bytes());
166 unsafe { core::mem::transmute(s) }
167 }
168
169 #[inline]
173 pub fn try_from_str(s: &str) -> Result<&Self, Error> {
174 Self::try_from_bytes(s.as_bytes())
175 }
176
177 #[inline]
181 pub fn try_from_bytes(s: &[u8]) -> Result<&Self, Error> {
182 let len = s.len();
183 for (ind, byte) in s.iter().enumerate() {
184 if *byte == NULL_BYTE {
185 return if ind == len - 1 {
186 unsafe { Ok(&*(core::ptr::from_ref::<[u8]>(s) as *const UnixStr)) }
187 } else {
188 Err(Error::no_code("Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place"))
189 };
190 }
191 }
192 Err(Error::no_code(
193 "Tried to instantiate UnixStr from an invalid &str, not null terminated",
194 ))
195 }
196
197 #[must_use]
200 pub unsafe fn from_ptr<'a>(s: *const u8) -> &'a Self {
201 let non_null_len = strlen(s);
202 let slice = core::slice::from_raw_parts(s, non_null_len + 1);
203 &*(core::ptr::from_ref::<[u8]>(slice) as *const Self)
204 }
205
206 pub fn as_str(&self) -> Result<&str, Error> {
210 let slice = unsafe { core::slice::from_raw_parts(self.0.as_ptr(), self.0.len() - 1) };
211 Ok(core::str::from_utf8(slice)?)
212 }
213
214 #[inline]
216 #[must_use]
217 pub const fn as_slice(&self) -> &[u8] {
218 &self.0
219 }
220
221 #[inline]
223 #[must_use]
224 #[expect(clippy::len_without_is_empty)]
225 pub fn len(&self) -> usize {
226 self.0.len()
227 }
228
229 #[must_use]
231 #[inline]
232 pub fn as_ptr(&self) -> *const u8 {
233 self.0.as_ptr()
234 }
235
236 #[must_use]
237 pub fn match_up_to(&self, other: &UnixStr) -> usize {
238 let mut it = 0;
239 let slf_ptr = self.as_ptr();
240 let other_ptr = other.as_ptr();
241 loop {
242 unsafe {
243 let a_val = slf_ptr.add(it).read();
244 let b_val = other_ptr.add(it).read();
245 if a_val != b_val || a_val == NULL_BYTE {
246 return it;
248 }
249 it += 1;
251 }
252 }
253 }
254
255 #[must_use]
256 pub fn match_up_to_str(&self, other: &str) -> usize {
257 let mut it = 0;
258 let slf_ptr = self.as_ptr();
259 let other_ptr = other.as_ptr();
260 let other_len = other.len();
261 loop {
262 unsafe {
263 let a_val = slf_ptr.add(it).read();
264 let b_val = other_ptr.add(it).read();
265 if a_val != b_val || a_val == NULL_BYTE {
266 return it;
268 }
269 it += 1;
271 }
272 if it == other_len {
273 return it;
274 }
275 }
276 }
277
278 #[must_use]
279 pub fn find(&self, other: &Self) -> Option<usize> {
280 if other.len() > self.len() {
281 return None;
282 }
283 let this_buf = &self.0;
284 let other_buf = &other.0[..other.0.len() - 2];
285 buf_find(this_buf, other_buf)
286 }
287
288 #[must_use]
289 pub fn find_buf(&self, other: &[u8]) -> Option<usize> {
290 if other.len() > self.len() {
291 return None;
292 }
293 let this_buf = &self.0;
294 buf_find(this_buf, other)
295 }
296
297 #[must_use]
298 pub fn ends_with(&self, other: &Self) -> bool {
299 if other.len() > self.len() {
300 return false;
301 }
302 let mut ind = 0;
303 while let (Some(this), Some(that)) = (
304 self.0.get(self.0.len() - 1 - ind),
305 other.0.get(other.0.len() - 1 - ind),
306 ) {
307 if this != that {
308 return false;
309 }
310 if other.0.len() - 1 - ind == 0 {
311 return true;
312 }
313 ind += 1;
314 }
315 true
316 }
317
318 #[must_use]
334 pub fn path_file_name(&self) -> Option<&UnixStr> {
335 for (ind, byte) in self.0.iter().enumerate().rev() {
336 if *byte == b'/' {
337 return if ind + 2 < self.len() {
338 unsafe {
339 Some(&*(core::ptr::from_ref::<[u8]>(&self.0[ind + 1..]) as *const Self))
340 }
341 } else {
342 None
343 };
344 }
345 }
346 None
347 }
348
349 #[must_use]
371 #[cfg(feature = "alloc")]
372 pub fn path_join(&self, ext: &Self) -> UnixString {
373 let mut as_string = self.0.to_vec();
374 as_string.pop();
375 let Some(last) = as_string.last().copied() else {
376 return UnixString::from(ext);
377 };
378 if ext.len() == 1 {
379 return UnixString::from(self);
380 }
381 as_string.reserve(ext.len());
382 let buf = if last == b'/' {
383 unsafe {
384 if ext.0.get_unchecked(0) == &b'/' {
385 as_string.extend_from_slice(ext.0.get_unchecked(1..));
386 } else {
387 as_string.extend_from_slice(&ext.0);
388 }
389 }
390 as_string
391 } else if unsafe { ext.0.get_unchecked(0) == &b'/' } {
392 as_string.extend_from_slice(&ext.0);
393 as_string
394 } else {
395 as_string.push(b'/');
396 as_string.extend_from_slice(&ext.0);
397 as_string
398 };
399 UnixString(buf)
400 }
401
402 #[must_use]
415 #[cfg(feature = "alloc")]
416 pub fn path_join_fmt(&self, args: core::fmt::Arguments<'_>) -> UnixString {
417 let container = alloc::fmt::format(args);
418 if container.is_empty() {
419 return UnixString::from(self);
420 }
421 let mut container_vec = container.into_bytes();
422 let mut as_string = self.0.to_vec();
423 as_string.pop();
424 let Some(last) = as_string.last().copied() else {
425 if !matches!(container_vec.last().copied(), Some(NULL_BYTE)) {
426 container_vec.push(NULL_BYTE);
427 }
428 return UnixString(container_vec);
429 };
430 if last == b'/' {
431 as_string.reserve(container_vec.len() + 1);
432 let start_from = if let Some(b'/') = container_vec.first().copied() {
433 1
434 } else {
435 0
436 };
437 if let Some(add_slice) = container_vec.get(start_from..) {
438 as_string.extend_from_slice(add_slice);
439 }
440 if !matches!(as_string.last().copied(), Some(NULL_BYTE)) {
441 as_string.push(NULL_BYTE);
442 }
443 } else if let Some(b'/') = container_vec.first().copied() {
444 as_string.extend(container_vec);
445 if !matches!(as_string.last().copied(), Some(NULL_BYTE)) {
446 as_string.push(NULL_BYTE);
447 }
448 } else {
449 as_string.push(b'/');
450 as_string.extend(container_vec);
451 if !matches!(as_string.last().copied(), Some(NULL_BYTE)) {
452 as_string.push(NULL_BYTE);
453 }
454 }
455 UnixString(as_string)
456 }
457
458 #[must_use]
477 #[cfg(feature = "alloc")]
478 pub fn parent_path(&self) -> Option<UnixString> {
479 let len = self.0.len();
480 if len < 3 {
482 return None;
483 }
484 let last = self.0.len() - 2;
485 let mut next_slash_back = last;
486 while let Some(byte) = self.0.get(next_slash_back).copied() {
487 if byte == b'/' {
488 if next_slash_back != 0 {
489 if let Some(b'/') = self.0.get(next_slash_back - 1) {
490 return None;
491 }
492 }
493 break;
494 }
495 if next_slash_back == 0 {
496 return None;
497 }
498 next_slash_back -= 1;
499 }
500 if next_slash_back == 0 {
502 next_slash_back += 1;
503 }
504 unsafe {
505 Some(UnixString(
506 self.0.get_unchecked(..=next_slash_back).to_vec(),
507 ))
508 }
509 }
510}
511
512#[inline]
513#[expect(clippy::needless_range_loop)]
514fn buf_find(this_buf: &[u8], other_buf: &[u8]) -> Option<usize> {
515 for i in 0..this_buf.len() {
516 if this_buf[i] == other_buf[0] {
517 let mut no_match = false;
518 for j in 1..other_buf.len() {
519 if let Some(this) = this_buf.get(i + j) {
520 if *this != other_buf[j] {
521 no_match = true;
522 break;
523 }
524 } else {
525 return None;
526 }
527 }
528 if !no_match {
529 return Some(i);
530 }
531 }
532 }
533 None
534}
535
536impl Debug for &UnixStr {
537 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
538 let slice = unsafe { core::slice::from_raw_parts(self.0.as_ptr(), self.0.len()) };
539 match core::str::from_utf8(slice) {
540 Ok(inner) => f.write_fmt(format_args!("UnixStr({inner})")),
541 Err(_e) => f.write_fmt(format_args!("UnixStr({slice:?})")),
542 }
543 }
544}
545
546impl core::hash::Hash for &UnixStr {
547 #[inline]
548 fn hash<H: Hasher>(&self, state: &mut H) {
549 self.0.hash(state);
550 }
551}
552
553#[cfg(feature = "alloc")]
554impl From<&UnixStr> for UnixString {
555 #[inline]
556 fn from(s: &UnixStr) -> Self {
557 UnixString(s.0.to_vec())
558 }
559}
560
561#[cfg(feature = "alloc")]
562impl core::str::FromStr for UnixString {
563 type Err = Error;
564
565 #[inline]
566 fn from_str(s: &str) -> Result<Self, Self::Err> {
567 Self::try_from_str(s)
568 }
569}
570
571#[inline]
572const fn const_null_term_validate(s: &[u8]) {
573 assert!(
574 !s.is_empty(),
575 "Tried to instantiate UnixStr from an invalid &str, not null terminated"
576 );
577 let len = s.len() - 1;
578 let mut i = len;
579 assert!(
580 s[i] == b'\0',
581 "Tried to instantiate UnixStr from an invalid &str, not null terminated"
582 );
583 while i > 0 {
584 i -= 1;
585 assert!(s[i] != b'\0', "Tried to instantiate UnixStr from an invalid &str, a null byte was found but out of place");
586 }
587}
588
589#[cfg(test)]
590mod tests {
591 use super::*;
592 #[test]
593 fn can_match_up_to() {
594 let haystack = UnixStr::try_from_str("haystack\0").unwrap();
595 let needle = UnixStr::EMPTY;
596 assert_eq!(0, haystack.match_up_to(needle));
597 let needle = UnixStr::try_from_str("h\0").unwrap();
598 assert_eq!(1, haystack.match_up_to(needle));
599 let needle = UnixStr::try_from_str("haystac\0").unwrap();
600 assert_eq!(7, haystack.match_up_to(needle));
601 let needle = UnixStr::try_from_str("haystack\0").unwrap();
602 assert_eq!(8, haystack.match_up_to(needle));
603 let needle = UnixStr::try_from_str("haystack2\0").unwrap();
604 assert_eq!(8, haystack.match_up_to(needle));
605 }
606
607 #[test]
608 fn can_create_unix_str() {
609 const CONST_CORRECT: &UnixStr = UnixStr::from_str_checked("abc\0");
610
611 let correct1 = UnixStr::try_from_str("abc\0").unwrap();
612 let correct2 = UnixStr::try_from_bytes(b"abc\0").unwrap();
613 assert_eq!(CONST_CORRECT, correct1);
614 assert_eq!(correct1, correct2);
615 }
616
617 #[test]
618 fn can_create_unix_str_sad() {
619 let unacceptable = UnixStr::try_from_str("a\0bc");
620 assert!(unacceptable.is_err());
621 let unacceptable_vec = UnixStr::try_from_bytes(b"a\0bc");
622 assert!(unacceptable_vec.is_err());
623 let unacceptable_not_null_term = UnixStr::try_from_str("abc");
624 assert!(unacceptable_not_null_term.is_err());
625 }
626
627 #[test]
628 fn can_cmp_unix_str_and_str() {
629 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
630 const MY_CMP_STR: &str = "my-nice-str";
631 assert_eq!(MY_CMP_STR.len(), UNIX_STR.match_up_to_str(MY_CMP_STR));
632 }
633
634 #[test]
635 fn can_check_ends_with() {
636 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
637 assert!(UNIX_STR.ends_with(UnixStr::from_str_checked("-str\0")));
638 }
639
640 #[test]
641 fn can_check_ends_with_self() {
642 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
643 assert!(UNIX_STR.ends_with(UNIX_STR));
644 }
645
646 #[test]
647 fn can_check_ends_with_empty() {
648 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
649 assert!(UNIX_STR.ends_with(UnixStr::EMPTY));
650 }
651
652 #[test]
653 fn can_check_ends_with_no() {
654 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
655 assert!(!UNIX_STR.ends_with(UnixStr::from_str_checked("nice-\0")));
656 }
657
658 #[test]
659 fn can_check_ends_with_no_too_long() {
660 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
661 assert!(!UNIX_STR.ends_with(UnixStr::from_str_checked("other-my-nice-str\0")));
662 }
663
664 #[test]
665 fn can_find_at_end() {
666 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
667 let found_at = UNIX_STR.find(UnixStr::from_str_checked("-str\0")).unwrap();
668 assert_eq!(7, found_at);
669 }
670
671 #[test]
672 fn can_find_finds_first() {
673 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("str-str-str\0");
674 let found_at = UNIX_STR.find(UnixStr::from_str_checked("-str\0")).unwrap();
675 assert_eq!(3, found_at);
676 let found_at = UNIX_STR.find_buf("-str".as_bytes()).unwrap();
677 assert_eq!(3, found_at);
678 }
679
680 #[test]
681 fn can_find_at_middle() {
682 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
683 let found_at = UNIX_STR.find(UnixStr::from_str_checked("-nice\0")).unwrap();
684 assert_eq!(2, found_at);
685 let found_at = UNIX_STR.find_buf("-nice".as_bytes()).unwrap();
686 assert_eq!(2, found_at);
687 }
688
689 #[test]
690 fn can_find_at_start() {
691 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
692 let found_at = UNIX_STR.find(UnixStr::from_str_checked("my\0")).unwrap();
693 assert_eq!(0, found_at);
694 let found_at = UNIX_STR.find_buf("my".as_bytes()).unwrap();
695 assert_eq!(0, found_at);
696 }
697
698 #[test]
699 fn can_find_no_match() {
700 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("my-nice-str\0");
701 let found_at = UNIX_STR.find(UnixStr::from_str_checked("cake\0"));
702 assert!(found_at.is_none());
703 let found_at = UNIX_STR.find_buf("cake".as_bytes());
704 assert!(found_at.is_none());
705 }
706
707 #[test]
708 fn can_find_too_long() {
709 const UNIX_STR: &UnixStr = UnixStr::from_str_checked("str\0");
710 let found_at = UNIX_STR.find(UnixStr::from_str_checked("sstr\0"));
711 assert!(found_at.is_none());
712
713 let found_at = UNIX_STR.find_buf("sstr".as_bytes());
714 assert!(found_at.is_none());
715 }
716
717 #[cfg(feature = "alloc")]
718 mod alloc_tests {
719 use super::*;
720 use alloc::string::ToString;
721 #[test]
722 #[cfg(feature = "alloc")]
723 fn can_create_unix_string_sad() {
724 let acceptable = UnixString::try_from_str("abc").unwrap();
725 let correct = UnixString::try_from_str("abc\0").unwrap();
726 assert_eq!(correct, acceptable);
727 let unacceptable = UnixString::try_from_str("a\0bc");
728 assert!(unacceptable.is_err());
729 let unacceptable_vec = UnixString::try_from_vec(alloc::vec![b'a', b'\0', b'b', b'c']);
730 assert!(unacceptable_vec.is_err());
731 }
732 #[test]
733 fn can_path_join() {
734 let a = UnixStr::from_str_checked("hello\0");
735 let b = UnixStr::from_str_checked("there\0");
736 let new = a.path_join(b);
737 assert_eq!("hello/there", new.as_str().unwrap());
738 }
739
740 #[test]
741 fn can_path_join_fmt() {
742 let a = UnixStr::from_str_checked("hello\0");
743 let new = a.path_join_fmt(format_args!("there"));
744 assert_eq!("hello/there", new.as_str().unwrap());
745 }
746
747 #[test]
748 fn can_path_join_with_trailing_slash() {
749 let a = UnixStr::from_str_checked("hello/\0");
750 let b = UnixStr::from_str_checked("there\0");
751 let new = a.path_join(b);
752 assert_eq!("hello/there", new.as_str().unwrap());
753 }
754
755 #[test]
756 fn can_path_join_fmt_with_trailing_slash() {
757 let a = UnixStr::from_str_checked("hello/\0");
758 let new = a.path_join_fmt(format_args!("there"));
759 assert_eq!("hello/there", new.as_str().unwrap());
760 }
761 #[test]
762 fn can_path_join_with_leading_slash() {
763 let a = UnixStr::from_str_checked("hello\0");
764 let b = UnixStr::from_str_checked("/there\0");
765 let new = a.path_join(b);
766 assert_eq!("hello/there", new.as_str().unwrap());
767 }
768
769 #[test]
770 fn can_path_join_fmt_with_leading_slash() {
771 let a = UnixStr::from_str_checked("hello\0");
772 let new = a.path_join_fmt(format_args!("/there"));
773 assert_eq!("hello/there", new.as_str().unwrap());
774 }
775
776 #[test]
777 fn can_path_join_empty() {
778 let a = UnixStr::from_str_checked("\0");
779 let b = UnixStr::from_str_checked("/there\0");
780 let new = a.path_join(b);
781 assert_eq!("/there", new.as_str().unwrap());
782 let new = b.path_join(a);
783 assert_eq!("/there", new.as_str().unwrap());
784 }
785
786 #[test]
787 fn can_path_join_fmt_empty() {
788 let a = UnixStr::from_str_checked("\0");
789 let b = UnixStr::from_str_checked("/there\0");
790 let new = a.path_join_fmt(format_args!("/there"));
791 assert_eq!("/there", new.as_str().unwrap());
792 let new = b.path_join_fmt(format_args!(""));
793 assert_eq!("/there", new.as_str().unwrap());
794 }
795
796 #[test]
797 fn can_path_join_truncates_slashes() {
798 let a = UnixStr::from_str_checked("hello/\0");
799 let b = UnixStr::from_str_checked("/there\0");
800 let new = a.path_join(b);
801 assert_eq!("hello/there", new.as_str().unwrap());
802 }
803
804 #[test]
805 fn can_get_last_path_happy() {
806 let base = unix_lit!("a/b/c");
807 let res = base.path_file_name().unwrap();
808 let expect = unix_lit!("c");
809 assert_eq!(expect, res);
810 }
811
812 #[test]
813 fn can_get_last_path_root_gives_none() {
814 let base = unix_lit!("/");
815 assert!(base.path_file_name().is_none());
816 }
817
818 #[test]
819 fn can_get_last_path_empty_gives_none() {
820 assert!(UnixStr::EMPTY.path_file_name().is_none());
821 }
822
823 #[test]
824 fn find_parent_path_happy() {
825 let a = UnixStr::from_str_checked("hello/there/friend\0");
826 let parent = a.parent_path().unwrap();
827 assert_eq!("hello/there", parent.as_str().unwrap());
828 let b = UnixStr::from_str_checked("/home/gramar/code/rust/tiny-std\0");
829 let b_first_parent = b.parent_path().unwrap();
830 assert_eq!("/home/gramar/code/rust", b_first_parent.as_str().unwrap());
831 let b_second_parent = b_first_parent.parent_path().unwrap();
832 assert_eq!("/home/gramar/code", b_second_parent.as_str().unwrap());
833 let b_third_parent = b_second_parent.parent_path().unwrap();
834 assert_eq!("/home/gramar", b_third_parent.as_str().unwrap());
835 let b_fourth_parent = b_third_parent.parent_path().unwrap();
836 assert_eq!("/home", b_fourth_parent.as_str().unwrap());
837 let root = b_fourth_parent.parent_path().unwrap();
838 assert_eq!("/", root.as_str().unwrap());
839 }
840
841 #[test]
842 fn find_parent_path_empty_no_parent() {
843 let a = UnixStr::EMPTY;
844 let parent = a.parent_path();
845 assert!(parent.is_none());
846 }
847
848 #[test]
849 fn find_parent_path_short_no_parent() {
850 let a = UnixStr::from_str_checked("/\0");
851 let parent = a.parent_path();
852 assert!(parent.is_none());
853 let b = UnixStr::from_str_checked("a\0");
854 let parent = b.parent_path();
855 assert!(parent.is_none());
856 }
857
858 #[test]
859 fn find_parent_path_short_has_parent() {
860 let a = UnixStr::from_str_checked("/a\0");
861 let parent = a.parent_path().unwrap();
862 assert_eq!("/", parent.as_str().unwrap());
863 }
864
865 #[test]
866 fn find_parent_path_double_slash_invalid() {
867 let a = UnixStr::from_str_checked("//\0");
868 let parent = a.parent_path();
869 assert!(parent.is_none());
870 let a = UnixStr::from_str_checked("hello//\0");
871 let parent = a.parent_path();
872 assert!(parent.is_none());
873 }
874
875 #[test]
876 fn can_create_unix_string_happy() {
877 let correct = UnixString::try_from_str("abc\0").unwrap();
878 let correct2 = UnixString::try_from_bytes(b"abc\0").unwrap();
879 let correct3 = UnixString::try_from_string("abc\0".to_string()).unwrap();
880 let correct4 = UnixString::try_from_vec(b"abc\0".to_vec()).unwrap();
881 let correct5 = UnixString::try_from_str("abc").unwrap();
882 let correct6 = UnixString::try_from_string(String::from("abc")).unwrap();
883 assert_eq!(correct, correct2);
884 assert_eq!(correct2, correct3);
885 assert_eq!(correct3, correct4);
886 assert_eq!(correct4, correct5);
887 assert_eq!(correct5, correct6);
888 let compare = [b'a', b'b', b'c', 0];
889 assert_eq!(correct4.as_slice(), compare);
890 assert_ne!(correct.as_ptr(), correct2.as_ptr());
891 assert_eq!(UnixStr::try_from_str("abc\0").unwrap(), correct.as_ref());
892 assert_eq!(UnixStr::try_from_str("abc\0").unwrap(), &*correct);
893 }
894 }
895}