faster_path 0.3.10 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/src/plus.rs CHANGED
@@ -1,102 +1,118 @@
1
1
  use std::borrow::Cow;
2
- use std::str;
3
- use std::path::MAIN_SEPARATOR;
4
2
 
5
- use chop_basename::chop_basename;
6
- use path_parsing::SEP;
3
+ use crate::basename::basename;
4
+ use crate::chop_basename::chop_basename;
5
+ use crate::dirname::dirname;
6
+ use crate::path_parsing::Rules;
7
7
 
8
- pub fn plus_paths<'a>(path1: &'a str, path2: &str) -> Cow<'a, str> {
8
+ // Pathname's `plus`, which `Pathname#+` and `Pathname#join` use.
9
+ pub fn plus_paths<'a>(rules: Rules, path1: &'a [u8], path2: &[u8]) -> Cow<'a, [u8]> {
10
+ // The names in path2, and where each starts
9
11
  let mut prefix2 = path2;
10
12
  let mut index_list2: Vec<usize> = vec![];
11
- let mut basename_list2: Vec<&str> = vec![];
12
- loop {
13
- match chop_basename(prefix2) {
14
- None => { break; }
15
- Some((pfx2, basename2)) => {
16
- prefix2 = pfx2;
17
- index_list2.push(pfx2.len());
18
- basename_list2.push(basename2);
19
- }
20
- }
13
+ let mut basename_list2: Vec<&[u8]> = vec![];
14
+ while let Some((prefix, basename)) = chop_basename(rules, prefix2) {
15
+ prefix2 = prefix;
16
+ index_list2.push(prefix.len());
17
+ basename_list2.push(basename);
21
18
  }
22
19
  if !prefix2.is_empty() {
23
- return path2.to_string().into();
24
- };
20
+ return Cow::Owned(path2.to_vec());
21
+ }
22
+ index_list2.reverse();
23
+ basename_list2.reverse();
24
+ // The names before `first` have been shifted off
25
+ let mut first = 0;
25
26
 
26
- let result_prefix: Cow<str>;
27
27
  let mut prefix1 = path1;
28
28
  loop {
29
- let mut new_len = basename_list2.len() - count_trailing(".", &basename_list2);
30
- index_list2.truncate(new_len);
31
- basename_list2.truncate(new_len);
32
- match chop_basename(prefix1) {
33
- None => {
34
- result_prefix = prefix1.into();
35
- break;
36
- }
37
- Some((pfx1, basename1)) => {
38
- prefix1 = pfx1;
39
- if basename1 == "." { continue; };
40
- if basename1 == ".." || basename_list2.last() != Some(&"..") {
41
- result_prefix = [prefix1, basename1].concat().into();
42
- break;
43
- }
44
- }
29
+ while basename_list2.get(first) == Some(&&b"."[..]) {
30
+ first += 1;
31
+ }
32
+ let (prefix, basename1) = match chop_basename(rules, prefix1) {
33
+ Some(chopped) => chopped,
34
+ None => break,
35
+ };
36
+ prefix1 = prefix;
37
+ if basename1 == b"." {
38
+ continue;
45
39
  }
46
- if new_len > 0 {
47
- new_len -= 1;
48
- index_list2.truncate(new_len);
49
- basename_list2.truncate(new_len);
40
+ if basename1 == b".." || basename_list2.get(first) != Some(&&b".."[..]) {
41
+ // `prefix1 + basename1`; the basename follows its prefix
42
+ prefix1 = &path1[..prefix1.len() + basename1.len()];
43
+ break;
50
44
  }
45
+ first += 1;
51
46
  }
52
47
 
53
- if !result_prefix.is_empty() && result_prefix.as_bytes().iter().cloned().all(|b| b == SEP) {
54
- let new_len = basename_list2.len() - count_trailing("..", &basename_list2);
55
- index_list2.truncate(new_len);
56
- basename_list2.truncate(new_len);
57
- }
58
- if let Some(last_index2) = index_list2.last() {
59
- let suffix = &path2[*last_index2..];
60
- match (result_prefix.as_bytes().last(), suffix.as_bytes().first()) {
61
- (Some(&SEP), Some(&SEP)) => [&result_prefix, &suffix[1..]].concat().into(),
62
- (Some(&SEP), Some(_)) | (Some(_), Some(&SEP)) => [&result_prefix, suffix].concat().into(),
63
- (None, Some(_)) => suffix.to_string().into(),
64
- _ => format!("{}{}{}", result_prefix.as_ref(), MAIN_SEPARATOR, suffix).into(),
48
+ let mut prefix1_has_name = chop_basename(rules, prefix1).is_some();
49
+ if !prefix1_has_name && rules.contains_sep(basename(rules, prefix1, b"")) {
50
+ // Nothing goes above the root
51
+ prefix1_has_name = true;
52
+ while basename_list2.get(first) == Some(&&b".."[..]) {
53
+ first += 1;
65
54
  }
66
- } else {
67
- if result_prefix.is_empty() {
68
- ".".into()
55
+ }
56
+ if first < basename_list2.len() {
57
+ let suffix2 = &path2[index_list2[first]..];
58
+ if prefix1_has_name {
59
+ Cow::Owned(rules.join(prefix1, suffix2))
69
60
  } else {
70
- result_prefix
61
+ Cow::Owned([prefix1, suffix2].concat())
71
62
  }
63
+ } else if prefix1_has_name {
64
+ Cow::Borrowed(prefix1)
65
+ } else {
66
+ dirname(rules, prefix1)
72
67
  }
73
68
  }
74
69
 
75
- #[inline(always)]
76
- fn count_trailing(x: &str, xs: &Vec<&str>) -> usize {
77
- xs.iter().rev().take_while(|&c| c == &x).count()
70
+ #[cfg(test)]
71
+ fn plus_str(path1: &str, path2: &str) -> String {
72
+ String::from_utf8(plus_paths(Rules::UNIX, path1.as_bytes(), path2.as_bytes()).into_owned()).unwrap()
73
+ }
74
+
75
+ #[cfg(test)]
76
+ fn windows_plus(path1: &str, path2: &str) -> String {
77
+ String::from_utf8(plus_paths(Rules::WINDOWS, path1.as_bytes(), path2.as_bytes()).into_owned()).unwrap()
78
78
  }
79
79
 
80
80
  #[test]
81
81
  fn it_will_plus_same_as_ruby() {
82
- assert_eq!("/" , plus_paths("/" , "/"));
83
- assert_eq!("a/b" , plus_paths("a" , "b"));
84
- assert_eq!("a" , plus_paths("a" , "."));
85
- assert_eq!("b" , plus_paths("." , "b"));
86
- assert_eq!("." , plus_paths("." , "."));
87
- assert_eq!("/b" , plus_paths("a" , "/b"));
82
+ assert_eq!("/" , plus_str("/" , "/"));
83
+ assert_eq!("a/b" , plus_str("a" , "b"));
84
+ assert_eq!("a" , plus_str("a" , "."));
85
+ assert_eq!("b" , plus_str("." , "b"));
86
+ assert_eq!("." , plus_str("." , "."));
87
+ assert_eq!("/b" , plus_str("a" , "/b"));
88
88
 
89
- assert_eq!("/" , plus_paths("/" , ".."));
90
- assert_eq!("////" , plus_paths("////", ""));
91
- assert_eq!("." , plus_paths("a" , ".."));
92
- assert_eq!("a" , plus_paths("a/b", ".."));
93
- assert_eq!("../.." , plus_paths(".." , ".."));
94
- assert_eq!("/c" , plus_paths("/" , "../c"));
95
- assert_eq!("c" , plus_paths("a" , "../c"));
96
- assert_eq!("a/c" , plus_paths("a/b", "../c"));
97
- assert_eq!("../../c", plus_paths(".." , "../c"));
89
+ assert_eq!("/" , plus_str("/" , ".."));
90
+ assert_eq!("////" , plus_str("////", ""));
91
+ assert_eq!("." , plus_str("a" , ".."));
92
+ assert_eq!("a" , plus_str("a/b", ".."));
93
+ assert_eq!("../.." , plus_str(".." , ".."));
94
+ assert_eq!("/c" , plus_str("/" , "../c"));
95
+ assert_eq!("c" , plus_str("a" , "../c"));
96
+ assert_eq!("a/c" , plus_str("a/b", "../c"));
97
+ assert_eq!("../../c", plus_str(".." , "../c"));
98
98
 
99
- assert_eq!("a//b/d//e", plus_paths("a//b/c", "../d//e"));
99
+ assert_eq!("a//b/d//e", plus_str("a//b/c", "../d//e"));
100
100
 
101
- assert_eq!("//foo/var/bar", plus_paths("//foo/var", "bar"));
101
+ assert_eq!("//foo/var/bar", plus_str("//foo/var", "bar"));
102
+ }
103
+
104
+ #[test]
105
+ fn it_will_plus_same_as_ruby_on_windows() {
106
+ assert_eq!("a/b", windows_plus("a", "b"));
107
+ assert_eq!("a/b", windows_plus("a\\", "b"));
108
+ assert_eq!("/b", windows_plus("a", "/b"));
109
+ assert_eq!("\\b", windows_plus("a", "\\b"));
110
+ assert_eq!("C:/b", windows_plus("a", "C:/b"));
111
+ assert_eq!("C:/a/b", windows_plus("C:/a", "b"));
112
+ assert_eq!("C:/b", windows_plus("C:/a", "../b"));
113
+ assert_eq!("C:/b", windows_plus("C:/", "../b"));
114
+ assert_eq!("C:\\a/b", windows_plus("C:\\a", "b"));
115
+ assert_eq!("a", windows_plus("a\\b", ".."));
116
+ assert_eq!("//a/b/c", windows_plus("//a/b", "c"));
117
+ assert_eq!("//a/b/c", windows_plus("//a/b", "../c"));
102
118
  }
@@ -1,19 +1,17 @@
1
1
  use std::borrow::Cow;
2
- use dirname::dirname;
3
- use path_parsing::{SEP, contains_sep};
4
- use std::path::MAIN_SEPARATOR;
5
2
 
6
- pub fn prepend_prefix<'a>(prefix: &'a str, relpath: &str) -> Cow<'a, str> {
3
+ use crate::cleanpath_conservative::add_trailing_separator;
4
+ use crate::dirname::dirname;
5
+ use crate::path_parsing::Rules;
6
+
7
+ // Pathname's `prepend_prefix`
8
+ pub fn prepend_prefix<'a>(rules: Rules, prefix: &'a [u8], relpath: &[u8]) -> Cow<'a, [u8]> {
7
9
  if relpath.is_empty() {
8
- dirname(prefix).into()
9
- } else if contains_sep(prefix.as_bytes()) {
10
- let prefix_dirname = dirname(prefix);
11
- match prefix_dirname.as_bytes().last() {
12
- None => relpath.to_string().into(),
13
- Some(&SEP) => format!("{}{}", prefix_dirname, relpath).into(),
14
- _ => format!("{}{}{}", prefix_dirname, MAIN_SEPARATOR, relpath).into()
15
- }
10
+ dirname(rules, prefix)
11
+ } else if rules.contains_sep(prefix) {
12
+ let prefix = add_trailing_separator(rules, dirname(rules, prefix));
13
+ Cow::Owned([&prefix[..], relpath].concat())
16
14
  } else {
17
- format!("{}{}", prefix, relpath).into()
15
+ Cow::Owned([prefix, relpath].concat())
18
16
  }
19
17
  }
@@ -1,70 +1,94 @@
1
- use std::iter;
2
- use helpers::{is_same_path, to_str};
3
- use path_parsing::SEP_STR;
4
- use cleanpath_aggressive::cleanpath_aggressive;
5
- use chop_basename::chop_basename;
6
- use pathname::Pathname;
7
- use ruru;
8
- use ruru::{Exception as Exc, AnyException as Exception};
1
+ use crate::chop_basename::chop_basename;
2
+ use crate::cleanpath_aggressive::cleanpath_aggressive;
3
+ use crate::path_parsing::{Rules, SEP_BYTES};
9
4
 
10
- type MaybeString = Result<ruru::RString, ruru::result::Error>;
5
+ #[derive(Debug, PartialEq)]
6
+ pub enum RelativePathError {
7
+ // Holds the destination prefix and the cleaned base directory.
8
+ DifferentPrefix(Vec<u8>, Vec<u8>),
9
+ // Holds the cleaned base directory.
10
+ BaseDirectoryHasDotDot(Vec<u8>),
11
+ }
11
12
 
12
- pub fn relative_path_from(itself: MaybeString, base_directory: MaybeString) -> Result<Pathname, Exception> {
13
- let dest_directory = cleanpath_aggressive(to_str(&itself));
14
- let base_directory = cleanpath_aggressive(to_str(&base_directory));
13
+ // Pathname's `relative_path_from`
14
+ pub fn relative_path_from(rules: Rules, dest: &[u8], base: &[u8]) -> Result<Vec<u8>, RelativePathError> {
15
+ let dest_directory = cleanpath_aggressive(rules, dest);
16
+ let base_directory = cleanpath_aggressive(rules, base);
15
17
 
16
- let (dest_prefix, mut dest_names) = to_names(dest_directory.as_ref());
17
- let (base_prefix, mut base_names) = to_names(base_directory.as_ref());
18
+ let (dest_prefix, mut dest_names) = to_names(rules, &dest_directory);
19
+ let (base_prefix, mut base_names) = to_names(rules, &base_directory);
18
20
 
19
- if !is_same_path(&dest_prefix, &base_prefix) {
20
- return Err(
21
- Exception::new(
22
- "ArgumentError",
23
- Some(&format!("different prefix: {} and {}", dest_prefix, base_prefix)),
24
- )
25
- );
21
+ if !rules.same_path(dest_prefix, base_prefix) {
22
+ return Err(RelativePathError::DifferentPrefix(dest_prefix.to_vec(), base_directory.to_vec()));
26
23
  }
27
24
 
28
- // Remove shared tail
25
+ // Remove the shared leading names (stored last, as the names are collected in reverse)
29
26
  {
30
27
  let num_same = dest_names.iter().rev().zip(base_names.iter().rev()).
31
- take_while(|&(dest, base)| dest == base).count();
32
- let num_dest_names = dest_names.len();
33
- dest_names.truncate(num_dest_names - num_same);
34
- let num_base_names = base_names.len();
35
- base_names.truncate(num_base_names - num_same);
28
+ take_while(|&(dest, base)| rules.same_path(dest, base)).count();
29
+ dest_names.truncate(dest_names.len() - num_same);
30
+ base_names.truncate(base_names.len() - num_same);
36
31
  };
37
32
 
38
- if base_names.contains(&"..") {
39
- return Err(
40
- Exception::new(
41
- "ArgumentError",
42
- Some(&format!("base_directory has ..: {}", base_directory)),
43
- )
44
- );
33
+ if base_names.contains(&&b".."[..]) {
34
+ return Err(RelativePathError::BaseDirectoryHasDotDot(base_directory.to_vec()));
45
35
  }
46
36
 
47
37
  if base_names.is_empty() && dest_names.is_empty() {
48
- Ok(Pathname::new("."))
38
+ Ok(b".".to_vec())
49
39
  } else {
50
- Ok(Pathname::new(&iter::repeat("..").take(base_names.len()).chain(dest_names.into_iter().rev()).
51
- collect::<Vec<&str>>().join(&SEP_STR)))
40
+ let names: Vec<&[u8]> = std::iter::repeat(&b".."[..]).take(base_names.len()).
41
+ chain(dest_names.into_iter().rev()).collect();
42
+ Ok(names.join(SEP_BYTES))
52
43
  }
53
44
  }
54
45
 
55
46
  #[inline(always)]
56
- fn to_names(path: &str) -> (&str, Vec<&str>) {
57
- let mut result: Vec<&str> = vec![];
47
+ fn to_names(rules: Rules, path: &[u8]) -> (&[u8], Vec<&[u8]>) {
48
+ let mut result: Vec<&[u8]> = vec![];
58
49
  let mut prefix = path;
59
- loop {
60
- match chop_basename(&prefix) {
61
- Some((ref dest, ref basename)) => {
62
- prefix = dest;
63
- if basename != &"." {
64
- result.push(basename);
65
- }
66
- }
67
- None => return (prefix, result),
50
+ while let Some((dest, basename)) = chop_basename(rules, prefix) {
51
+ prefix = dest;
52
+ if basename != b"." {
53
+ result.push(basename);
68
54
  }
69
55
  }
56
+ (prefix, result)
57
+ }
58
+
59
+ #[cfg(test)]
60
+ fn relative_str(rules: Rules, dest: &str, base: &str) -> Option<String> {
61
+ relative_path_from(rules, dest.as_bytes(), base.as_bytes()).ok().map(|path| String::from_utf8(path).unwrap())
62
+ }
63
+
64
+ #[test]
65
+ fn it_finds_relative_paths() {
66
+ let u = Rules::UNIX;
67
+ assert_eq!(relative_str(u, "a", "b").unwrap(), "../a");
68
+ assert_eq!(relative_str(u, "/a/b/c/d", "/a/b").unwrap(), "c/d");
69
+ assert_eq!(relative_str(u, "/a/b", "/a/b/c/d").unwrap(), "../..");
70
+ assert_eq!(relative_str(u, ".", ".").unwrap(), ".");
71
+ assert_eq!(relative_str(u, "a/b/c", "a/d").unwrap(), "../b/c");
72
+ }
73
+
74
+ #[test]
75
+ fn it_rejects_incompatible_paths() {
76
+ assert_eq!(
77
+ relative_path_from(Rules::UNIX, b"/", b"."),
78
+ Err(RelativePathError::DifferentPrefix(b"/".to_vec(), b".".to_vec()))
79
+ );
80
+ assert_eq!(
81
+ relative_path_from(Rules::UNIX, b"a", b".."),
82
+ Err(RelativePathError::BaseDirectoryHasDotDot(b"..".to_vec()))
83
+ );
84
+ }
85
+
86
+ #[test]
87
+ fn it_finds_relative_paths_on_windows() {
88
+ let w = Rules::WINDOWS;
89
+ assert_eq!(relative_str(w, "C:\\a\\b", "c:/a").unwrap(), "b");
90
+ assert_eq!(relative_str(w, "C:/A/b", "c:/a/c").unwrap(), "../b");
91
+ assert_eq!(relative_str(w, "//a/b/c/d", "//a/b/c").unwrap(), "d");
92
+ assert_eq!(relative_str(w, "C:/a", "D:/a"), None);
93
+ assert_eq!(relative_str(Rules::UNIX, "a/B", "a/b").unwrap(), "../B");
70
94
  }