From 215beebfd11d072458421f0aed8d430cd33f19ee Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 17 Mar 2025 13:31:06 +1100 Subject: [PATCH 01/13] ~ ordering --- src/lib.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8d61730..895362b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -420,13 +420,13 @@ pub mod libpath { #[test] fn TEST_char_is_path_name_separator__1() { - assert!(char_is_path_name_separator_('/')); assert!(!char_is_path_name_separator_('\\')); + assert!(char_is_path_name_separator_('/')); - assert!(!char_is_path_name_separator_('a')); + assert!(!char_is_path_name_separator_('-')); assert!(!char_is_path_name_separator_(':')); assert!(!char_is_path_name_separator_(';')); - assert!(!char_is_path_name_separator_('-')); + assert!(!char_is_path_name_separator_('a')); } #[test] @@ -871,16 +871,16 @@ pub mod libpath { #[test] fn TEST_char_is_drive_letter__1() { - assert!(char_is_drive_letter_('a')); assert!(char_is_drive_letter_('A')); - assert!(char_is_drive_letter_('c')); assert!(char_is_drive_letter_('C')); - assert!(char_is_drive_letter_('z')); assert!(char_is_drive_letter_('Z')); + assert!(char_is_drive_letter_('a')); + assert!(char_is_drive_letter_('c')); + assert!(char_is_drive_letter_('z')); - assert!(!char_is_drive_letter_(':')); - assert!(!char_is_drive_letter_('/')); assert!(!char_is_drive_letter_('.')); + assert!(!char_is_drive_letter_('/')); + assert!(!char_is_drive_letter_(':')); } #[test] @@ -888,10 +888,10 @@ pub mod libpath { assert!(char_is_path_name_separator_('/')); assert!(char_is_path_name_separator_('\\')); - assert!(!char_is_path_name_separator_('a')); + assert!(!char_is_path_name_separator_('-')); assert!(!char_is_path_name_separator_(':')); assert!(!char_is_path_name_separator_(';')); - assert!(!char_is_path_name_separator_('-')); + assert!(!char_is_path_name_separator_('a')); } #[test] @@ -2838,3 +2838,4 @@ mod tests { /* ///////////////////////////// end of file //////////////////////////// */ + From 5cd0bd23e560e5e3cf6fa7cf301432be763a7284 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 17 Mar 2025 15:18:52 +1100 Subject: [PATCH 02/13] ~ preparatory refactoring --- src/lib.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 895362b..fcfe8c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -267,7 +267,7 @@ pub mod libpath { for c in path.chars() { ix += 1; - if char_is_bad_in_all_contexts_(c) { + if char_is_invalid_in_path_(c) { return ( // argument list: Classification::InvalidChars, @@ -341,8 +341,32 @@ pub mod libpath { ) } - fn char_is_bad_in_all_contexts_(c : char) -> bool { + /// Evaluates whether a character is invalid in a path entry + /// name. + /* + fn char_is_invalid_in_entryname_(c : char) -> bool { match c { + // On all Unix, by definition, the character '/' is invalid. + '/' => true, + // On macOS (through observation), the additional character + // values in the range 128-255 is invalid. + '\u{0080}'..='\u{00ff}' => true, + + _ => false, + } + } + */ + + /// Evaluates whether a character is invalid in a path. + fn char_is_invalid_in_path_(c : char) -> bool { + match c { + // On macOS (through observation), the additional character + // values in the range 128-255 is invalid. + '\u{0080}'..='\u{00ff}' => true, + '*' => true, + '<' => true, + '>' => true, + '?' => true, '|' => true, _ => false, @@ -418,10 +442,18 @@ pub mod libpath { fn TEST_char_is_drive_letter__1() { } + #[test] + fn TEST_char_is_invalid_in_entryname__1() { + } + + #[test] + fn TEST_char_is_invalid_in_path__1() { + } + #[test] fn TEST_char_is_path_name_separator__1() { - assert!(!char_is_path_name_separator_('\\')); assert!(char_is_path_name_separator_('/')); + assert!(!char_is_path_name_separator_('\\')); assert!(!char_is_path_name_separator_('-')); assert!(!char_is_path_name_separator_(':')); @@ -663,6 +695,16 @@ pub mod libpath { _ => (), } + if char_is_invalid_in_path_(c) { + return ( + // argument list: + Classification::InvalidChars, + PoSl::empty(), + PoSl::empty(), + Some(ix as usize), + ); + } + if 0 == ix { if '/' == c { return ( @@ -771,6 +813,41 @@ pub mod libpath { ) } + /// Evaluates whether a character is invalid in a path entry + /// name. + /* + fn char_is_invalid_in_entryname_(c : char) -> bool { + match c { + '\u{0001}'..='\u{001f}' => true, + '"' => true, + '*' => true, + '/' => true, + '<' => true, + '>' => true, + '?' => true, + '\\' => true, + '|' => true, + + _ => false, + } + } + */ + + /// Evaluates whether a character is invalid in a path. + fn char_is_invalid_in_path_(c : char) -> bool { + match c { + '\u{0001}'..='\u{001f}' => true, + '"' => true, + '*' => true, + '<' => true, + '>' => true, + '?' => true, + '|' => true, + + _ => false, + } + } + fn char_is_path_name_separator_(c : char) -> bool { match c { '/' => true, @@ -883,6 +960,14 @@ pub mod libpath { assert!(!char_is_drive_letter_(':')); } + #[test] + fn TEST_char_is_invalid_in_entryname__1() { + } + + #[test] + fn TEST_char_is_invalid_in_path__1() { + } + #[test] fn TEST_char_is_path_name_separator__1() { assert!(char_is_path_name_separator_('/')); @@ -930,8 +1015,8 @@ pub mod libpath { ("~a/", 0, Classification::Relative, PoSl::new(0, 0), PoSl::new(0, 3), None), ("~a/", classification_flags::RECOGNISE_TILDE_HOME, Classification::Relative, PoSl::new(0, 0), PoSl::new(0, 3), None), - // ("|a", 0, Classification::InvalidChars, PoSl::empty(), PoSl::empty(), Some(0)), - ("a|", 0, Classification::Relative, PoSl::empty(), PoSl::new(0, 2), None), + ("|a", 0, Classification::InvalidChars, PoSl::empty(), PoSl::empty(), Some(0)), + ("a|", 0, Classification::InvalidChars, PoSl::empty(), PoSl::empty(), Some(1)), ]; for ( From 3dff129914bc5effe72c7f5f677eff86f1c26cbb Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 17 Mar 2025 15:20:16 +1100 Subject: [PATCH 03/13] ~ layout --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index fcfe8c9..8809298 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -429,6 +429,7 @@ pub mod libpath { (number_of_parts, number_of_dots_parts) } + #[cfg(test)] mod tests { #![allow(non_snake_case)] @@ -1100,6 +1101,7 @@ pub mod libpath { } } + #[cfg(test)] #[allow(non_snake_case)] mod tests { From eae4ca3483e2303f4bf5b5e15f1d6fb8f23f9282 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 17 Mar 2025 15:29:26 +1100 Subject: [PATCH 04/13] + more tests --- src/lib.rs | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8809298..ec47b88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -439,16 +439,18 @@ pub mod libpath { use fastparse::fastparse::types::PositionalSlice as PoSl; - #[test] - fn TEST_char_is_drive_letter__1() { - } - - #[test] - fn TEST_char_is_invalid_in_entryname__1() { - } - #[test] fn TEST_char_is_invalid_in_path__1() { + assert!(char_is_invalid_in_path_('<')); + assert!(char_is_invalid_in_path_('>')); + assert!(char_is_invalid_in_path_('|')); + + assert!(!char_is_invalid_in_path_('-')); + assert!(!char_is_invalid_in_path_('/')); + assert!(!char_is_invalid_in_path_(':')); + assert!(!char_is_invalid_in_path_(';')); + assert!(!char_is_invalid_in_path_('\\')); + assert!(!char_is_invalid_in_path_('a')); } #[test] @@ -961,12 +963,18 @@ pub mod libpath { assert!(!char_is_drive_letter_(':')); } - #[test] - fn TEST_char_is_invalid_in_entryname__1() { - } - #[test] fn TEST_char_is_invalid_in_path__1() { + assert!(char_is_invalid_in_path_('<')); + assert!(char_is_invalid_in_path_('>')); + assert!(char_is_invalid_in_path_('|')); + + assert!(!char_is_invalid_in_path_('-')); + assert!(!char_is_invalid_in_path_('/')); + assert!(!char_is_invalid_in_path_(':')); + assert!(!char_is_invalid_in_path_(';')); + assert!(!char_is_invalid_in_path_('\\')); + assert!(!char_is_invalid_in_path_('a')); } #[test] From 79e553ea440e7ccc9663db1c48e319c27adb7641 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Mon, 17 Mar 2025 19:21:34 +1100 Subject: [PATCH 05/13] + added helper function `libpath::util::windows::unc_split_()` --- src/lib.rs | 271 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ec47b88..d09feaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -657,6 +657,64 @@ pub mod libpath { (cl, cr) } + fn unc_split_<'a>( + path : &'a str, + parse_flags : i32, + ) -> Vec<&'a str> + { + let mut v = Vec::with_capacity(1 + path.len() / 10); + + let path_bytes = path.as_bytes(); + + let mut prev = b'|'; // anything other than a slash + let mut from = 0; + for ix in 0..path.len() { + let c = path_bytes[ix]; + + match c { + b'/' | b'\\' => { + + if c == prev { + // continue the slash run + + } else { + // push slash run slice + + if from != ix { + + v.push(&path[from..ix]); + from = ix; + } + } + }, + _ => { + + match prev { + b'/' | b'\\' => { + // push non-slash run slice + + v.push(&path[from..ix]); + from = ix; + }, + _ => { + // continue the non-slash run + + }, + } + } + } + + prev = c; + } + + if from != path.len() { + + v.push(&path[from..]); + } + + v + } + /// Examines the path to the degree necessary to be able to /// classify it. /// @@ -1104,6 +1162,219 @@ pub mod libpath { #[test] fn TEST_find_last_slash__1() { } + + #[test] + fn TEST_unc_split_() { + { + let input = ""; + let parse_flags = 0; + let expected : Vec<&str> = vec![]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = "COM1"; + let parse_flags = 0; + let expected = vec![ + // element list: + "COM1", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\.\COM1"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"COM1", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"COM1\"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"COM1", + r"\", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"Volume{b75e2c83-0000-0000-0000-602f00000000}", + r"\", + r"Test", + r"\", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}/Test/Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"Volume{b75e2c83-0000-0000-0000-602f00000000}", + r"/", + r"Test", + r"/", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\?\C:/Test/Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"?", + r"\", + r"C:", + r"/", + r"Test", + r"/", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\.\UNC\LOCALHOST\c$\temp\test-file.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"UNC", + r"\", + r"LOCALHOST", + r"\", + r"c$", + r"\", + r"temp", + r"\", + r"test-file.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\.\UNC\127.0.0.1\c$\temp\test-file.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"UNC", + r"\", + r"127.0.0.1", + r"\", + r"c$", + r"\", + r"temp", + r"\", + r"test-file.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\127.0.0.1\c$\dir\stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"127.0.0.1", + r"\", + r"c$", + r"\", + r"dir", + r"\", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\127.0.0.1\c$\dir\stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"127.0.0.1", + r"\", + r"c$", + r"\", + r"dir", + r"\", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\?\server1\utilities\\filecomparer\"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"?", + r"\", + r"server1", + r"\", + r"utilities", + r"\\", + r"filecomparer", + r"\", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + } } } } From 1dd0b6acde4aabfda05af1e735e4dce5be61ed84 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 18 Mar 2025 11:52:06 +1100 Subject: [PATCH 06/13] ~ enhanced `libpath::util::windows::unc_split_()` --- src/lib.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d09feaf..0ff049e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -551,6 +551,7 @@ pub mod libpath { use super::common::ClassificationResult; use fastparse::fastparse::types::PositionalSlice as PoSl; + pub mod classification_flags { /// T.B.C. @@ -657,6 +658,11 @@ pub mod libpath { (cl, cr) } + // Splits the given path into slices. + // + // # Note: + // The splitting is done by byte not by character, because all + // significant characters are ASCII. fn unc_split_<'a>( path : &'a str, parse_flags : i32, @@ -678,11 +684,25 @@ pub mod libpath { // continue the slash run } else { - // push slash run slice + // push non-slash run slice if from != ix { - v.push(&path[from..ix]); + let slice0 = &path[from..ix]; + + // check for drive-relative strings (e.g. "C:dir") and split them + if slice0.len() > 2 && str_begins_with_drive_spec_(slice0) { + + let slice1 = &slice0[0..2]; + let slice2 = &slice0[2..]; + + v.push(slice1); + v.push(slice2); + } else { + + v.push(slice0); + } + from = ix; } } @@ -691,7 +711,7 @@ pub mod libpath { match prev { b'/' | b'\\' => { - // push non-slash run slice + // push slash run slice v.push(&path[from..ix]); from = ix; @@ -997,6 +1017,33 @@ pub mod libpath { } } + fn str_begins_with_drive_spec_(s : &str) -> bool { + if s.len() < 2 { + return false; + } + + let c0 = s.as_bytes()[0] as char; + let c1 = s.as_bytes()[1] as char; + + if c1 != ':' { + return false; + } + + if !char_is_drive_letter_(c0) { + return false; + } + + true + } + + fn str_is_drive_spec_(s : &str) -> bool { + if 2 != s.len() { + return false; + } + + str_begins_with_drive_spec_(s) + } + #[cfg(test)] mod tests { @@ -1174,6 +1221,53 @@ pub mod libpath { assert_eq!(expected, actual); } + { + let input = r"C:\Test\Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"\", + r"Test", + r"\", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"C:/Test/Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"/", + r"Test", + r"/", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"C:Test\Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"Test", + r"\", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + { let input = "COM1"; let parse_flags = 0; From 09a92c10548c0f76113634ca6f552474b556a036 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 18 Mar 2025 12:02:08 +1100 Subject: [PATCH 07/13] **TODO.md** : + more things to do --- TODO.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 037559b..51dac4a 100644 --- a/TODO.md +++ b/TODO.md @@ -9,7 +9,22 @@ ## Functional improvements -* [ ] Full UNC support (in `libpath::util::windows`); +* [ ] Full path support (in `libpath::util::windows`): + * [ ] Drive-rooted paths; + * [ ] Drive-relative paths; + * [ ] Home-rooted paths; + * [ ] Slash-rooted paths; + * [ ] relative paths; + * [ ] UNC-rooted paths; + * [ ] UNC-incompleted paths; + * [ ] (some of) above with: + * [ ] Local device prefix `"\\.\"`; + * [ ] Root local device prefix `"\\?\"`; + * [ ] Local device prefix and UNC designator `"\\.\UNC\"`; + * [ ] Root local device prefix and UNC designator `"\\?\UNC\"`; + * [ ] NT path prefix `"\??\"`; + * [ ] Device names (such as `"COM1"`); + * [ ] Support (sadly) full flexibility in Windows paths for mixed use of `'\'` and `'/'` (though not for runs); * [ ] Rename `ClassificationResult` to `PathDescriptor`; * [x] Rename `ClassificationResult#Entry` to `#EntryName`; * [ ] Remove `ClassificationResult#FirstInvalid` and use in function calls; @@ -20,6 +35,7 @@ * [ ] Implement `IGNORE_INVALID_CHARS`; * [ ] Implement `RECOGNISE_TILDE_HOME`; * [ ] Implement `IGNORE_INVALID_CHARS_IN_LONG_PATH`; +* [ ] Implement path normalisation (Unix and Windows) : `to_os_normal()`, `to_asbtract_normal()`; ## Performance improvements From 60211a1c0e0d0d32d1097fe287cbdbc8e52697c3 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 18 Mar 2025 12:14:34 +1100 Subject: [PATCH 08/13] **TODO.md** : + more things to do --- TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO.md b/TODO.md index 51dac4a..670a797 100644 --- a/TODO.md +++ b/TODO.md @@ -25,6 +25,7 @@ * [ ] NT path prefix `"\??\"`; * [ ] Device names (such as `"COM1"`); * [ ] Support (sadly) full flexibility in Windows paths for mixed use of `'\'` and `'/'` (though not for runs); + * [ ] Detection of trailing space as invalid character(s); * [ ] Rename `ClassificationResult` to `PathDescriptor`; * [x] Rename `ClassificationResult#Entry` to `#EntryName`; * [ ] Remove `ClassificationResult#FirstInvalid` and use in function calls; From 1d1c884dd9d4537e9e39ecc5556f6f33e93b1b0a Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 18 Mar 2025 12:53:46 +1100 Subject: [PATCH 09/13] + more tests for `libpath::util::windows::unc_split_()` --- src/lib.rs | 829 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 624 insertions(+), 205 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0ff049e..6f25552 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -668,6 +668,10 @@ pub mod libpath { parse_flags : i32, ) -> Vec<&'a str> { + { + let _ = parse_flags; + } + let mut v = Vec::with_capacity(1 + path.len() / 10); let path_bytes = path.as_bytes(); @@ -1212,8 +1216,9 @@ pub mod libpath { #[test] fn TEST_unc_split_() { + // empty { - let input = ""; + let input = r""; let parse_flags = 0; let expected : Vec<&str> = vec![]; let actual = unc_split_(input, parse_flags); @@ -1221,252 +1226,666 @@ pub mod libpath { assert_eq!(expected, actual); } + // relative { - let input = r"C:\Test\Foo.txt"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"C:", - r"\", - r"Test", - r"\", - r"Foo.txt", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"abc"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"abc", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); - } + { + let input = r"abc.def"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"abc.def", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - { - let input = r"C:/Test/Foo.txt"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"C:", - r"/", - r"Test", - r"/", - r"Foo.txt", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"directory\abc.def"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"directory", + r"\", + r"abc.def", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); - } + { + let input = r"directory/abc.def"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"directory", + r"/", + r"abc.def", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - { - let input = r"C:Test\Foo.txt"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"C:", - r"Test", - r"\", - r"Foo.txt", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"directory\\abc.def"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"directory", + r"\\", + r"abc.def", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); + { + let input = r"directory//abc.def"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"directory", + r"//", + r"abc.def", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } } + // slash-rooted { - let input = "COM1"; - let parse_flags = 0; - let expected = vec![ - // element list: - "COM1", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"\"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"\", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); - } + { + let input = r"/"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"/", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - { - let input = r"\\.\COM1"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r".", - r"\", - r"COM1", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"\abc"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"\", + r"abc", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); + { + let input = r"/abc"; + let parse_flags = 0; + let expected : Vec<&str> = vec![ + // element list: + r"/", + r"abc", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } } + // drive-rooted { - let input = r"COM1\"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"COM1", - r"\", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"C:\Test\Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"\", + r"Test", + r"\", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); - } + { + let input = r"C:/Test/Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"/", + r"Test", + r"/", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - { - let input = r"\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r".", - r"\", - r"Volume{b75e2c83-0000-0000-0000-602f00000000}", - r"\", - r"Test", - r"\", - r"Foo.txt", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"C:\\Test\\Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"\\", + r"Test", + r"\\", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); + { + let input = r"C://Test//Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"//", + r"Test", + r"//", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } } + // drive-relative { - let input = r"\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}/Test/Foo.txt"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r".", - r"\", - r"Volume{b75e2c83-0000-0000-0000-602f00000000}", - r"/", - r"Test", - r"/", - r"Foo.txt", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"C:Test\Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"Test", + r"\", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); - } + { + let input = r"C:Test/Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"Test", + r"/", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - { - let input = r"\\?\C:/Test/Foo.txt"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r"?", - r"\", - r"C:", - r"/", - r"Test", - r"/", - r"Foo.txt", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"C:Test\\Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"Test", + r"\\", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); + { + let input = r"C:Test//Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"Test", + r"//", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"C:..\File.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"C:", + r"..", + r"\", + r"File.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } } + // local device names { - let input = r"\\.\UNC\LOCALHOST\c$\temp\test-file.txt"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r".", - r"\", - r"UNC", - r"\", - r"LOCALHOST", - r"\", - r"c$", - r"\", - r"temp", - r"\", - r"test-file.txt", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"\\.\COM1"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"COM1", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); - } + { + let input = r"\\.\C:"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"C:", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - { - let input = r"\\.\UNC\127.0.0.1\c$\temp\test-file.txt"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r".", - r"\", - r"UNC", - r"\", - r"127.0.0.1", - r"\", - r"c$", - r"\", - r"temp", - r"\", - r"test-file.txt", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\Test\Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"Volume{b75e2c83-0000-0000-0000-602f00000000}", + r"\", + r"Test", + r"\", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); + { + let input = r"\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}/Test/Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"Volume{b75e2c83-0000-0000-0000-602f00000000}", + r"/", + r"Test", + r"/", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } } + // UNC-rooted { - let input = r"\\127.0.0.1\c$\dir\stem.ext"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r"127.0.0.1", - r"\", - r"c$", - r"\", - r"dir", - r"\", - r"stem.ext", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"\\server\share\directory\stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"server", + r"\", + r"share", + r"\", + r"directory", + r"\", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); + { + let input = r"//server/share/directory/stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"//", + r"server", + r"/", + r"share", + r"/", + r"directory", + r"/", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\0:0:0::1\c$\Windows\System32\notepad.exe"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"0:0:0::1", + r"\", + r"c$", + r"\", + r"Windows", + r"\", + r"System32", + r"\", + r"notepad.exe", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } } + // home-rooted { - let input = r"\\127.0.0.1\c$\dir\stem.ext"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r"127.0.0.1", - r"\", - r"c$", - r"\", - r"dir", - r"\", - r"stem.ext", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"~"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"~", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"~\"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"~", + r"\", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"~/"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"~", + r"/", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"~\server\share\directory\stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"~", + r"\", + r"server", + r"\", + r"share", + r"\", + r"directory", + r"\", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"~/server/share/directory/stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"~", + r"/", + r"server", + r"/", + r"share", + r"/", + r"directory", + r"/", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"~\\server\\share\\directory\\stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"~", + r"\\", + r"server", + r"\\", + r"share", + r"\\", + r"directory", + r"\\", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"~//server//share//directory//stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"~", + r"//", + r"server", + r"//", + r"share", + r"//", + r"directory", + r"//", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); } + // misc { - let input = r"\\?\server1\utilities\\filecomparer\"; - let parse_flags = 0; - let expected = vec![ - // element list: - r"\\", - r"?", - r"\", - r"server1", - r"\", - r"utilities", - r"\\", - r"filecomparer", - r"\", - ]; - let actual = unc_split_(input, parse_flags); + { + let input = r"\\?\C:/Test/Foo.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"?", + r"\", + r"C:", + r"/", + r"Test", + r"/", + r"Foo.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } - assert_eq!(expected, actual); + { + let input = r"\\.\UNC\LOCALHOST\c$\temp\test-file.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"UNC", + r"\", + r"LOCALHOST", + r"\", + r"c$", + r"\", + r"temp", + r"\", + r"test-file.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\.\UNC\127.0.0.1\c$\temp\test-file.txt"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r".", + r"\", + r"UNC", + r"\", + r"127.0.0.1", + r"\", + r"c$", + r"\", + r"temp", + r"\", + r"test-file.txt", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\127.0.0.1\c$\dir\stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"127.0.0.1", + r"\", + r"c$", + r"\", + r"dir", + r"\", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\::1\c$\dir\stem.ext"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"::1", + r"\", + r"c$", + r"\", + r"dir", + r"\", + r"stem.ext", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } + + { + let input = r"\\?\server1\share1\\directory1\"; + let parse_flags = 0; + let expected = vec![ + // element list: + r"\\", + r"?", + r"\", + r"server1", + r"\", + r"share1", + r"\\", + r"directory1", + r"\", + ]; + let actual = unc_split_(input, parse_flags); + + assert_eq!(expected, actual); + } } } } From ea45dfe027fd5cba66de7d367e8b8ec249ffe566 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 8 Apr 2025 07:53:31 +1000 Subject: [PATCH 10/13] ~ renamed `str_begins_with_drive_spec_()` => `slice_begins_with_drive_spec_()` ; ~ renamed `str_is_drive_spec_()` => `slice_is_drive_spec_()` ; --- src/lib.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6f25552..7cb743b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -695,7 +695,7 @@ pub mod libpath { let slice0 = &path[from..ix]; // check for drive-relative strings (e.g. "C:dir") and split them - if slice0.len() > 2 && str_begins_with_drive_spec_(slice0) { + if slice0.len() > 2 && slice_begins_with_drive_spec_(slice0) { let slice1 = &slice0[0..2]; let slice2 = &slice0[2..]; @@ -1013,6 +1013,7 @@ pub mod libpath { (number_of_parts, number_of_dots_parts) } + /// Indicates whether the given character is a drive letter. fn char_is_drive_letter_(c : char) -> bool { match c { 'A'..='Z' => true, @@ -1021,7 +1022,9 @@ pub mod libpath { } } - fn str_begins_with_drive_spec_(s : &str) -> bool { + /// Indicates whether the slice begins with a drive + /// specification, e.g. `"C:\dir-1\dir-2\stem.ext"`. + fn slice_begins_with_drive_spec_(s : &str) -> bool { if s.len() < 2 { return false; } @@ -1040,12 +1043,14 @@ pub mod libpath { true } - fn str_is_drive_spec_(s : &str) -> bool { + /// Indicates whether the slice is a drive specification, e.g. + /// `"C:"`. + fn slice_is_drive_spec_(s : &str) -> bool { if 2 != s.len() { return false; } - str_begins_with_drive_spec_(s) + slice_begins_with_drive_spec_(s) } @@ -1214,6 +1219,50 @@ pub mod libpath { fn TEST_find_last_slash__1() { } + #[test] + fn TEST_slice_begins_with_drive_spec__1() { + assert!(slice_begins_with_drive_spec_("A:")); + assert!(slice_begins_with_drive_spec_("C:")); + assert!(slice_begins_with_drive_spec_("Z:")); + assert!(slice_begins_with_drive_spec_("a:")); + assert!(slice_begins_with_drive_spec_("c:")); + assert!(slice_begins_with_drive_spec_("z:")); + + assert!(slice_begins_with_drive_spec_("c:\\dir-1\\dir-2\\stem.ext")); + assert!(slice_begins_with_drive_spec_("z:/dir-1/dir-2/stem.ext")); + assert!(slice_begins_with_drive_spec_("A:\\")); + assert!(slice_begins_with_drive_spec_("C:/")); + assert!(!slice_begins_with_drive_spec_("Z")); + assert!(!slice_begins_with_drive_spec_("a")); + assert!(!slice_begins_with_drive_spec_("")); + + assert!(!slice_begins_with_drive_spec_(".:")); + assert!(!slice_begins_with_drive_spec_("/:")); + assert!(!slice_begins_with_drive_spec_("::")); + } + + #[test] + fn TEST_slice_is_drive_spec__1() { + assert!(slice_is_drive_spec_("A:")); + assert!(slice_is_drive_spec_("C:")); + assert!(slice_is_drive_spec_("Z:")); + assert!(slice_is_drive_spec_("a:")); + assert!(slice_is_drive_spec_("c:")); + assert!(slice_is_drive_spec_("z:")); + + assert!(!slice_is_drive_spec_("c:\\dir-1\\dir-2\\stem.ext")); + assert!(!slice_is_drive_spec_("z:/dir-1/dir-2/stem.ext")); + assert!(!slice_is_drive_spec_("A:\\")); + assert!(!slice_is_drive_spec_("C:/")); + assert!(!slice_is_drive_spec_("Z")); + assert!(!slice_is_drive_spec_("a")); + assert!(!slice_is_drive_spec_("")); + + assert!(!slice_is_drive_spec_(".:")); + assert!(!slice_is_drive_spec_("/:")); + assert!(!slice_is_drive_spec_("::")); + } + #[test] fn TEST_unc_split_() { // empty From 2a9450988f00555d0df995fbe5c4e202945c36b8 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 8 Apr 2025 08:07:08 +1000 Subject: [PATCH 11/13] + unit-tests around `find_last_slash_()` --- src/lib.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7cb743b..5c131b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -541,6 +541,15 @@ pub mod libpath { #[test] fn TEST_find_last_slash__1() { + assert_eq!(None, find_last_slash_("")); + assert_eq!(None, find_last_slash_("abc")); + assert_eq!(None, find_last_slash_("C:")); + + assert_eq!(Some(12), find_last_slash_("/dir-1/dir-2/stem.ext")); + assert_eq!(None, find_last_slash_("dir\\")); + assert_eq!(Some(3), find_last_slash_("dir/")); + assert_eq!(Some(0), find_last_slash_("/")); + assert_eq!(None, find_last_slash_("\\")); } } } @@ -1217,6 +1226,16 @@ pub mod libpath { #[test] fn TEST_find_last_slash__1() { + assert_eq!(None, find_last_slash_("")); + assert_eq!(None, find_last_slash_("abc")); + assert_eq!(None, find_last_slash_("C:")); + + assert_eq!(Some(14), find_last_slash_("c:\\dir-1\\dir-2\\stem.ext")); + assert_eq!(Some(12), find_last_slash_("/dir-1/dir-2/stem.ext")); + assert_eq!(Some(2), find_last_slash_("C:\\")); + assert_eq!(Some(2), find_last_slash_("C:/")); + assert_eq!(Some(0), find_last_slash_("/")); + assert_eq!(Some(0), find_last_slash_("\\")); } #[test] From 176fd7fc3168073232a24e49b7146c95a2a6c148 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 8 Apr 2025 08:12:59 +1000 Subject: [PATCH 12/13] + added documentation --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 5c131b8..4ff4ee4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -373,10 +373,12 @@ pub mod libpath { } } + /// Evaluates whether a character is a path-name-separator. fn char_is_path_name_separator_(c : char) -> bool { c == '/' } + /// Looks for the last slash in the slice. fn find_last_slash_(s : &str) -> Option { s.rfind('/') } @@ -942,6 +944,7 @@ pub mod libpath { } } + /// Evaluates whether a character is a path-name-separator. fn char_is_path_name_separator_(c : char) -> bool { match c { '/' => true, @@ -950,6 +953,7 @@ pub mod libpath { } } + /// Looks for the last slash (forward or backward) in the slice. fn find_last_slash_(s : &str) -> Option { // TODO: consider rfind(&['/', '\\'][..]) From a9f02c292052c9c29b4118afdeacf7f3091d6e6b Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Tue, 8 Apr 2025 08:14:38 +1000 Subject: [PATCH 13/13] ~ renamed `count_parts_()` => `count_directory_parts_()` --- src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4ff4ee4..6824fd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -198,7 +198,7 @@ pub mod libpath { cr.Directory = PoSl::new(root.len(), dir_len); - let (num_parts, num_dir_parts) = count_parts_(cr.Directory.substring_of(path), parse_flags); + let (num_parts, num_dir_parts) = count_directory_parts_(cr.Directory.substring_of(path), parse_flags); cr.NumDirectoryParts = num_parts; cr.NumDotsDirectoryParts = num_dir_parts; @@ -383,7 +383,7 @@ pub mod libpath { s.rfind('/') } - fn count_parts_( + fn count_directory_parts_( s : &str, parse_flags : i32, ) -> ( @@ -534,7 +534,7 @@ pub mod libpath { ]; for (s, parse_flags, expected_number_of_parts, expected_number_of_dots_parts) in test_criteria { - let (number_of_parts, number_of_dots_parts) = count_parts_(*s, *parse_flags); + let (number_of_parts, number_of_dots_parts) = count_directory_parts_(*s, *parse_flags); assert_eq!(*expected_number_of_parts, number_of_parts, "wrong number of parts {number_of_parts} ({expected_number_of_parts} expected) when parsing '{s}'"); assert_eq!(*expected_number_of_dots_parts, number_of_dots_parts, "wrong number of dots parts {number_of_dots_parts} ({expected_number_of_dots_parts} expected) when parsing '{s}'"); @@ -630,7 +630,7 @@ pub mod libpath { cr.Directory = PoSl::new(root.len(), dir_len); - let (num_parts, num_dir_parts) = count_parts_(cr.Directory.substring_of(path), parse_flags); + let (num_parts, num_dir_parts) = count_directory_parts_(cr.Directory.substring_of(path), parse_flags); cr.NumDirectoryParts = num_parts; cr.NumDotsDirectoryParts = num_dir_parts; @@ -978,7 +978,7 @@ pub mod libpath { } } - fn count_parts_( + fn count_directory_parts_( s : &str, parse_flags : i32, ) -> ( @@ -1221,7 +1221,7 @@ pub mod libpath { ]; for (s, parse_flags, expected_number_of_parts, expected_number_of_dots_parts) in test_criteria { - let (number_of_parts, number_of_dots_parts) = count_parts_(*s, *parse_flags); + let (number_of_parts, number_of_dots_parts) = count_directory_parts_(*s, *parse_flags); assert_eq!(*expected_number_of_parts, number_of_parts, "wrong number of parts {number_of_parts} ({expected_number_of_parts} expected) when parsing '{s}'"); assert_eq!(*expected_number_of_dots_parts, number_of_dots_parts, "wrong number of dots parts {number_of_dots_parts} ({expected_number_of_dots_parts} expected) when parsing '{s}'");