Skip to content

Commit 664a4ea

Browse files
authored
Merge pull request #137 from danielpclark/cleanpath_conservative
Cleanpath conservative
2 parents 6e0f241 + 7663a78 commit 664a4ea

9 files changed

+218
-2
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ Current methods implemented:
143143
| `FasterPath.children` | `Pathname#children` | 13.2% |
144144
| `FasterPath.chop_basename` | `Pathname#chop_basename` | 54.5% |
145145
| `FasterPath.cleanpath_aggressive` | `Pathname#cleanpath_aggressive` | 73.8% |
146+
| `FasterPath.cleanpath_conservative` | `Pathname#cleanpath_conservative` | 70.7% |
146147
| `FasterPath.directory?` | `Pathname#directory?` | 11.3% |
147148
| `FasterPath.entries` | `Pathname#entries` | 8.4% |
148149
| `FasterPath.has_trailing_separator?` | `Pathname#has_trailing_separator` | 67.6% |

lib/faster_path/optional/monkeypatches.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ def cleanpath_aggressive
5252
end
5353
private :cleanpath_aggressive
5454

55+
def cleanpath_conservative
56+
Pathname.new(FasterPath.cleanpath_conservative(@path))
57+
end
58+
private :cleanpath_conservative
59+
5560
def directory?
5661
FasterPath.directory?(@path)
5762
end

lib/faster_path/optional/refinements.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ def cleanpath_aggressive
4848
end
4949
private :cleanpath_aggressive
5050

51+
def cleanpath_conservative
52+
Pathname.new(FasterPath.cleanpath_conservative(@path))
53+
end
54+
private :cleanpath_conservative
55+
5156
def directory?
5257
FasterPath.directory?(@path)
5358
end

src/cleanpath_conservative.rs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use prepend_prefix::prepend_prefix;
2+
use basename::basename;
3+
use dirname::dirname;
4+
use chop_basename::chop_basename;
5+
extern crate array_tool;
6+
use self::array_tool::vec::Shift;
7+
use std::path::MAIN_SEPARATOR;
8+
9+
pub fn cleanpath_conservative(path: &str) -> String {
10+
let sep = MAIN_SEPARATOR.to_string();
11+
let mut names: Vec<String> = vec![];
12+
let mut pre = path.to_string();
13+
loop {
14+
match chop_basename(&pre) {
15+
Some((ref p, ref base)) => {
16+
pre = p.to_string();
17+
match base.as_ref() {
18+
"." => {},
19+
_ => names.unshift(base.to_string()),
20+
}
21+
},
22+
None => break,
23+
}
24+
}
25+
// // Windows Feature
26+
//
27+
// ```ruby
28+
// pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
29+
// ```
30+
//
31+
if basename(&pre, "").contains(&sep) {
32+
loop {
33+
if names.first() == Some(&"..".to_string()) {
34+
let _ = names.shift();
35+
} else {
36+
break
37+
}
38+
}
39+
}
40+
41+
if names.is_empty() {
42+
return dirname(&pre).to_string();
43+
}
44+
45+
if names.last() != Some(&"..".to_string()) && basename(&path, "") == &".".to_string() {
46+
names.push(".".to_string());
47+
}
48+
49+
let result = prepend_prefix(&pre, &names.join(&sep)[..]);
50+
let last = names.last();
51+
52+
if !(last == Some(&".".to_string()) || last == Some(&"..".to_string())) &&
53+
chop_basename(path).map(|(a, b)| a.len() + b.len()).unwrap() < path.len() {
54+
format!("{}{}", last.unwrap(), MAIN_SEPARATOR)
55+
} else {
56+
result
57+
}
58+
}
59+
60+
#[test]
61+
fn it_conservatively_cleans_the_path() {
62+
assert_eq!(cleanpath_conservative("/"), "/");
63+
assert_eq!(cleanpath_conservative(""), ".");
64+
assert_eq!(cleanpath_conservative("."), ".");
65+
assert_eq!(cleanpath_conservative(".."), "..");
66+
assert_eq!(cleanpath_conservative("a"), "a");
67+
assert_eq!(cleanpath_conservative("/."), "/");
68+
assert_eq!(cleanpath_conservative("/.."), "/");
69+
assert_eq!(cleanpath_conservative("/a"), "/a");
70+
assert_eq!(cleanpath_conservative("./"), ".");
71+
assert_eq!(cleanpath_conservative("../"), "..");
72+
assert_eq!(cleanpath_conservative("a/"), "a/");
73+
assert_eq!(cleanpath_conservative("a//b"), "a/b");
74+
assert_eq!(cleanpath_conservative("a/."), "a/.");
75+
assert_eq!(cleanpath_conservative("a/./"), "a/.");
76+
assert_eq!(cleanpath_conservative("a/../"), "a/..");
77+
assert_eq!(cleanpath_conservative("/a/."), "/a/.");
78+
assert_eq!(cleanpath_conservative("./.."), "..");
79+
assert_eq!(cleanpath_conservative("../."), "..");
80+
assert_eq!(cleanpath_conservative("./../"), "..");
81+
assert_eq!(cleanpath_conservative(".././"), "..");
82+
assert_eq!(cleanpath_conservative("/./.."), "/");
83+
assert_eq!(cleanpath_conservative("/../."), "/");
84+
assert_eq!(cleanpath_conservative("/./../"), "/");
85+
assert_eq!(cleanpath_conservative("/.././"), "/");
86+
assert_eq!(cleanpath_conservative("a/b/c"), "a/b/c");
87+
assert_eq!(cleanpath_conservative("./b/c"), "b/c");
88+
assert_eq!(cleanpath_conservative("a/./c"), "a/c");
89+
assert_eq!(cleanpath_conservative("a/b/."), "a/b/.");
90+
assert_eq!(cleanpath_conservative("a/../."), "a/..");
91+
assert_eq!(cleanpath_conservative("/../.././../a"), "/a");
92+
assert_eq!(cleanpath_conservative("a/b/../../../../c/../d"), "a/b/../../../../c/../d");
93+
;
94+
// Future Windows Support
95+
//
96+
// DOSISH = File::ALT_SEPARATOR != nil
97+
// DOSISH_DRIVE_LETTER = File.dirname("A:") == "A:."
98+
// DOSISH_UNC = File.dirname("//") == "//"
99+
//
100+
//
101+
// if DOSISH
102+
// assert_eq!(cleanpath_conservative, 'c:/foo/bar', 'c:\\foo\\bar')
103+
// end
104+
//
105+
// if DOSISH_UNC
106+
// assert_eq!(cleanpath_conservative, '//', '//')
107+
// else
108+
// assert_eq!(cleanpath_conservative, '/', '//')
109+
// end
110+
}

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ mod pathname;
1717
mod basename;
1818
mod chop_basename;
1919
mod cleanpath_aggressive;
20+
mod cleanpath_conservative;
2021
mod dirname;
2122
mod extname;
2223
mod pathname_sys;
@@ -66,7 +67,9 @@ methods!(
6667
pathname::pn_cleanpath_aggressive(pth)
6768
}
6869

69-
// fn r_cleanpath_conservative(pth: RString){}
70+
fn pub_cleanpath_conservative(pth: RString) -> RString {
71+
pathname::pn_cleanpath_conservative(pth)
72+
}
7073

7174
// fn r_del_trailing_separator(pth: RString){}
7275

@@ -149,6 +152,7 @@ pub extern "C" fn Init_faster_pathname(){
149152
itself.def_self("absolute?", pub_is_absolute);
150153
itself.def_self("add_trailing_separator", pub_add_trailing_separator);
151154
itself.def_self("cleanpath_aggressive", pub_cleanpath_aggressive);
155+
itself.def_self("cleanpath_conservative", pub_cleanpath_conservative);
152156
itself.def_self("directory?", pub_is_directory);
153157
itself.def_self("dirname", pub_dirname);
154158
itself.def_self("extname", pub_extname);

src/pathname.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use helpers::*;
22
use basename;
33
use chop_basename;
44
use cleanpath_aggressive;
5+
use cleanpath_conservative;
56
use dirname;
67
use extname;
78
use plus;
@@ -129,7 +130,13 @@ pub fn pn_cleanpath_aggressive(pth: MaybeString) -> RString {
129130
RString::new(&path)
130131
}
131132

132-
// pub fn pn_cleanpath_conservative(pth: MaybeString){}
133+
pub fn pn_cleanpath_conservative(pth: MaybeString) -> RString {
134+
let path = cleanpath_conservative::cleanpath_conservative(
135+
pth.ok().unwrap_or(RString::new("")).to_str()
136+
);
137+
138+
RString::new(&path)
139+
}
133140

134141
// pub fn pn_del_trailing_separator(pth: MaybeString){}
135142

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
require "benchmark_helper"
2+
3+
class CleanpathConservativeBenchmark < BenchmarkHelper
4+
def setup
5+
@file ||= __FILE__
6+
@one = 'a/../.'
7+
@two = 'a/b/../../../../c/../d'
8+
end
9+
10+
def teardown
11+
super
12+
graph_benchmarks
13+
end
14+
15+
def bench_rust_cleanpath_conservative
16+
benchmark :rust do
17+
FasterPath.cleanpath_conservative(@one)
18+
FasterPath.cleanpath_conservative(@two)
19+
end
20+
end
21+
22+
def bench_ruby_cleanpath_conservative
23+
one = Pathname.new(@one)
24+
two = Pathname.new(@two)
25+
benchmark :ruby do
26+
one.send :cleanpath_conservative
27+
two.send :cleanpath_conservative
28+
end
29+
end
30+
end

test/cleanpath_conservative_test.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
require 'test_helper'
2+
3+
class CleanpathConservativeTest < Minitest::Test
4+
def test_it_creates_cleanpath_conservative_correctly_part_1
5+
assert_equal FasterPath.cleanpath_conservative("/"), "/"
6+
assert_equal FasterPath.cleanpath_conservative(""), "."
7+
assert_equal FasterPath.cleanpath_conservative("."), "."
8+
assert_equal FasterPath.cleanpath_conservative(".."), ".."
9+
assert_equal FasterPath.cleanpath_conservative("a"), "a"
10+
assert_equal FasterPath.cleanpath_conservative("/."), "/"
11+
assert_equal FasterPath.cleanpath_conservative("/.."), "/"
12+
assert_equal FasterPath.cleanpath_conservative("/a"), "/a"
13+
assert_equal FasterPath.cleanpath_conservative("./"), "."
14+
assert_equal FasterPath.cleanpath_conservative("../"), ".."
15+
assert_equal FasterPath.cleanpath_conservative("a/"), "a/"
16+
assert_equal FasterPath.cleanpath_conservative("a//b"), "a/b"
17+
assert_equal FasterPath.cleanpath_conservative("a/."), "a/."
18+
assert_equal FasterPath.cleanpath_conservative("a/./"), "a/."
19+
assert_equal FasterPath.cleanpath_conservative("a/../"), "a/.."
20+
assert_equal FasterPath.cleanpath_conservative("/a/."), "/a/."
21+
end
22+
23+
def test_it_creates_cleanpath_conservative_correctly_part_2
24+
assert_equal FasterPath.cleanpath_conservative("./.."), ".."
25+
assert_equal FasterPath.cleanpath_conservative("../."), ".."
26+
assert_equal FasterPath.cleanpath_conservative("./../"), ".."
27+
assert_equal FasterPath.cleanpath_conservative(".././"), ".."
28+
assert_equal FasterPath.cleanpath_conservative("/./.."), "/"
29+
assert_equal FasterPath.cleanpath_conservative("/../."), "/"
30+
assert_equal FasterPath.cleanpath_conservative("/./../"), "/"
31+
assert_equal FasterPath.cleanpath_conservative("/.././"), "/"
32+
assert_equal FasterPath.cleanpath_conservative("a/b/c"), "a/b/c"
33+
assert_equal FasterPath.cleanpath_conservative("./b/c"), "b/c"
34+
assert_equal FasterPath.cleanpath_conservative("a/./c"), "a/c"
35+
assert_equal FasterPath.cleanpath_conservative("a/b/."), "a/b/."
36+
assert_equal FasterPath.cleanpath_conservative("a/../."), "a/.."
37+
assert_equal FasterPath.cleanpath_conservative("/../.././../a"), "/a"
38+
assert_equal FasterPath.cleanpath_conservative("a/b/../../../../c/../d"), "a/b/../../../../c/../d"
39+
end
40+
end

test/pbench/pbench_suite.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,20 @@
136136
end
137137
end
138138
}
139+
PBENCHES[:cleanpath_conservative] = {
140+
new: lambda do |x|
141+
x.times do
142+
Pathname.new(FasterPath.cleanpath_conservative '/../.././../a')
143+
Pathname.new(FasterPath.cleanpath_conservative 'a/b/../../../../c/../d')
144+
end
145+
end,
146+
old: lambda do |x|
147+
x.times do
148+
PATHNAME_CA1.send :cleanpath_conservative
149+
PATHNAME_CA2.send :cleanpath_conservative
150+
end
151+
end
152+
}
139153
PBENCHES[:"directory?"] = {
140154
new: lambda do |x|
141155
x.times do

0 commit comments

Comments
 (0)