faster_path 0.3.9 → 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 +18 -43
- data/Cargo.toml +5 -7
- data/Gemfile +4 -7
- data/README.md +106 -33
- data/Rakefile +11 -2
- data/faster_path.gemspec +6 -3
- data/lib/faster_path/version.rb +1 -1
- data/lib/faster_path.rb +10 -3
- 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 +297 -296
- data/src/plus.rs +93 -79
- data/src/prepend_prefix.rs +11 -13
- data/src/relative_path_from.rs +72 -48
- data/src/ruby.rs +530 -0
- metadata +19 -20
- data/src/debug.rs +0 -29
- data/src/helpers.rs +0 -51
- data/src/memrnchr.rs +0 -70
- data/src/pathname_sys.rs +0 -55
data/src/plus.rs
CHANGED
|
@@ -1,104 +1,118 @@
|
|
|
1
|
-
|
|
2
|
-
use std::path::Path;
|
|
3
|
-
use std::str;
|
|
4
|
-
use chop_basename::chop_basename;
|
|
5
|
-
use basename::basename;
|
|
6
|
-
use dirname::dirname;
|
|
7
|
-
use self::array_tool::vec::Shift;
|
|
8
|
-
use std::ops::Index;
|
|
1
|
+
use std::borrow::Cow;
|
|
9
2
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
use crate::basename::basename;
|
|
4
|
+
use crate::chop_basename::chop_basename;
|
|
5
|
+
use crate::dirname::dirname;
|
|
6
|
+
use crate::path_parsing::Rules;
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
11
|
+
let mut prefix2 = path2;
|
|
12
|
+
let mut index_list2: Vec<usize> = vec![];
|
|
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);
|
|
24
18
|
}
|
|
25
19
|
if !prefix2.is_empty() {
|
|
26
|
-
return path2.
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
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;
|
|
30
26
|
|
|
27
|
+
let mut prefix1 = path1;
|
|
31
28
|
loop {
|
|
32
|
-
while
|
|
33
|
-
|
|
34
|
-
|
|
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;
|
|
35
39
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if basename1 == "." { continue };
|
|
41
|
-
if basename1 == ".." || basename_list2.is_empty() || basename_list2.first().unwrap() != ".." {
|
|
42
|
-
prefix1.push_str(&basename1);
|
|
43
|
-
break
|
|
44
|
-
}
|
|
45
|
-
}
|
|
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;
|
|
46
44
|
}
|
|
47
|
-
|
|
48
|
-
basename_list2.shift();
|
|
45
|
+
first += 1;
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if r1 {
|
|
58
|
-
while !basename_list2.is_empty() && basename_list2.first().unwrap() == ".." {
|
|
59
|
-
index_list2.shift();
|
|
60
|
-
basename_list2.shift();
|
|
61
|
-
}
|
|
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;
|
|
62
54
|
}
|
|
63
55
|
}
|
|
64
|
-
if
|
|
65
|
-
let suffix2 = path2
|
|
66
|
-
if
|
|
67
|
-
|
|
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))
|
|
68
60
|
} else {
|
|
69
|
-
prefix1.
|
|
70
|
-
result = prefix1.to_string();
|
|
61
|
+
Cow::Owned([prefix1, suffix2].concat())
|
|
71
62
|
}
|
|
63
|
+
} else if prefix1_has_name {
|
|
64
|
+
Cow::Borrowed(prefix1)
|
|
72
65
|
} else {
|
|
73
|
-
|
|
74
|
-
result = prefix1.to_string();
|
|
75
|
-
} else {
|
|
76
|
-
result = dirname(&prefix1[..]).to_string();
|
|
77
|
-
}
|
|
66
|
+
dirname(rules, prefix1)
|
|
78
67
|
}
|
|
68
|
+
}
|
|
69
|
+
|
|
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
|
+
}
|
|
79
74
|
|
|
80
|
-
|
|
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()
|
|
81
78
|
}
|
|
82
79
|
|
|
83
80
|
#[test]
|
|
84
81
|
fn it_will_plus_same_as_ruby() {
|
|
85
|
-
assert_eq!("/" ,
|
|
86
|
-
assert_eq!("a/b" ,
|
|
87
|
-
assert_eq!("a" ,
|
|
88
|
-
assert_eq!("b" ,
|
|
89
|
-
assert_eq!("." ,
|
|
90
|
-
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
|
+
|
|
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"));
|
|
91
98
|
|
|
92
|
-
assert_eq!("/"
|
|
93
|
-
assert_eq!("." , plus_paths("a" , ".."));
|
|
94
|
-
assert_eq!("a" , plus_paths("a/b", ".."));
|
|
95
|
-
assert_eq!("../.." , plus_paths(".." , ".."));
|
|
96
|
-
assert_eq!("/c" , plus_paths("/" , "../c"));
|
|
97
|
-
assert_eq!("c" , plus_paths("a" , "../c"));
|
|
98
|
-
assert_eq!("a/c" , plus_paths("a/b", "../c"));
|
|
99
|
-
assert_eq!("../../c", plus_paths(".." , "../c"));
|
|
99
|
+
assert_eq!("a//b/d//e", plus_str("a//b/c", "../d//e"));
|
|
100
100
|
|
|
101
|
-
assert_eq!("
|
|
101
|
+
assert_eq!("//foo/var/bar", plus_str("//foo/var", "bar"));
|
|
102
|
+
}
|
|
102
103
|
|
|
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"));
|
|
104
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
|
}
|