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.
- checksums.yaml +4 -4
- data/Cargo.lock +19 -37
- data/Cargo.toml +5 -6
- data/Gemfile +3 -7
- data/README.md +94 -30
- data/Rakefile +11 -2
- data/faster_path.gemspec +6 -3
- data/lib/faster_path/version.rb +1 -1
- data/lib/faster_path.rb +4 -2
- data/src/basename.rs +166 -69
- data/src/chop_basename.rs +84 -21
- data/src/cleanpath_aggressive.rs +87 -71
- data/src/cleanpath_conservative.rs +175 -79
- data/src/dirname.rs +96 -32
- data/src/extname.rs +150 -20
- data/src/lib.rs +54 -104
- data/src/path_parsing.rs +282 -21
- data/src/pathname.rs +296 -256
- data/src/plus.rs +90 -74
- data/src/prepend_prefix.rs +11 -13
- data/src/relative_path_from.rs +72 -48
- data/src/ruby.rs +530 -0
- metadata +17 -18
- data/src/debug.rs +0 -29
- data/src/helpers.rs +0 -51
- data/src/memrnchr.rs +0 -70
- data/src/pathname_sys.rs +0 -41
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
|
|
6
|
-
use
|
|
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
|
-
|
|
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<&
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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.
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
match chop_basename(prefix1) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
#[
|
|
76
|
-
fn
|
|
77
|
-
|
|
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!("/" ,
|
|
83
|
-
assert_eq!("a/b" ,
|
|
84
|
-
assert_eq!("a" ,
|
|
85
|
-
assert_eq!("b" ,
|
|
86
|
-
assert_eq!("." ,
|
|
87
|
-
assert_eq!("/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!("/" ,
|
|
90
|
-
assert_eq!("////" ,
|
|
91
|
-
assert_eq!("." ,
|
|
92
|
-
assert_eq!("a" ,
|
|
93
|
-
assert_eq!("../.." ,
|
|
94
|
-
assert_eq!("/c" ,
|
|
95
|
-
assert_eq!("c" ,
|
|
96
|
-
assert_eq!("a/c" ,
|
|
97
|
-
assert_eq!("../../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",
|
|
99
|
+
assert_eq!("a//b/d//e", plus_str("a//b/c", "../d//e"));
|
|
100
100
|
|
|
101
|
-
assert_eq!("//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
|
}
|
data/src/prepend_prefix.rs
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
9
|
-
} else if contains_sep(prefix
|
|
10
|
-
let
|
|
11
|
-
|
|
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
|
-
|
|
15
|
+
Cow::Owned([prefix, relpath].concat())
|
|
18
16
|
}
|
|
19
17
|
}
|
data/src/relative_path_from.rs
CHANGED
|
@@ -1,70 +1,94 @@
|
|
|
1
|
-
use
|
|
2
|
-
use
|
|
3
|
-
use path_parsing::
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
let
|
|
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
|
|
17
|
-
let (base_prefix, mut base_names) = to_names(base_directory
|
|
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 !
|
|
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
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
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(
|
|
38
|
+
Ok(b".".to_vec())
|
|
49
39
|
} else {
|
|
50
|
-
|
|
51
|
-
|
|
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: &
|
|
57
|
-
let mut result: 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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
}
|