faster_path 0.3.7 → 0.3.8

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/dirname.rs CHANGED
@@ -1,26 +1,66 @@
1
- extern crate memchr;
2
- use self::memchr::memrchr;
3
- use path_parsing::{SEP, SEP_STR, last_non_sep_i, last_non_sep_i_before};
1
+ use std::str;
2
+ use path_parsing::{find_last_sep_pos, find_last_non_sep_pos};
4
3
 
5
4
  pub fn dirname(path: &str) -> &str {
6
- if path.is_empty() { return "."; }
7
- let non_sep_i = last_non_sep_i(path);
8
- if non_sep_i == -1 { return *SEP_STR; }
9
- return match memrchr(SEP, &path.as_bytes()[..non_sep_i as usize]) {
10
- None => ".",
11
- Some(0) => *SEP_STR,
12
- Some(sep_i) => {
13
- let non_sep_i2 = last_non_sep_i_before(path, sep_i as isize);
14
- if non_sep_i2 != -1 {
15
- &path[..(non_sep_i2 + 1) as usize]
16
- } else {
17
- *SEP_STR
18
- }
19
- }
5
+ let bytes = path.as_bytes();
6
+ let mut last_slash_pos = match find_last_sep_pos(bytes) {
7
+ Some(pos) => pos,
8
+ _ => return ".",
9
+ };
10
+ // Skip trailing slashes.
11
+ if last_slash_pos == bytes.len() - 1 {
12
+ let last_non_slash_pos = match find_last_non_sep_pos(&bytes[..last_slash_pos]) {
13
+ Some(pos) => pos,
14
+ _ => return "/"
15
+ };
16
+ last_slash_pos = match find_last_sep_pos(&bytes[..last_non_slash_pos]) {
17
+ Some(pos) => pos,
18
+ _ => return "."
19
+ };
20
+ };
21
+ if let Some(end) = find_last_non_sep_pos(&bytes[..last_slash_pos]) {
22
+ &path[..end + 1]
23
+ } else {
24
+ "/"
20
25
  }
21
26
  }
22
27
 
23
28
  #[test]
24
- fn returns_dot_for_empty_string(){
25
- assert_eq!(dirname(""), ".".to_string());
29
+ fn absolute() {
30
+ assert_eq!(dirname("/a/b///c"), "/a/b");
31
+ }
32
+
33
+ #[test]
34
+ fn trailing_slashes_absolute() {
35
+ assert_eq!(dirname("/a/b///c//////"), "/a/b");
36
+ }
37
+
38
+ #[test]
39
+ fn relative() {
40
+ assert_eq!(dirname("b///c"), "b");
41
+ }
42
+
43
+ #[test]
44
+ fn trailing_slashes_relative() {
45
+ assert_eq!(dirname("b/c//"), "b");
46
+ }
47
+
48
+ #[test]
49
+ fn root() {
50
+ assert_eq!(dirname("//c"), "/");
51
+ }
52
+
53
+ #[test]
54
+ fn trailing_slashes_root() {
55
+ assert_eq!(dirname("//c//"), "/");
56
+ }
57
+
58
+ #[test]
59
+ fn trailing_slashes_relative_root() {
60
+ assert_eq!(dirname("c//"), ".");
61
+ }
62
+
63
+ #[test]
64
+ fn returns_dot_for_empty_string() {
65
+ assert_eq!(dirname(""), ".");
26
66
  }
data/src/extname.rs CHANGED
@@ -1,63 +1,25 @@
1
- use path_parsing::SEP;
2
1
  use std::str;
2
+ use path_parsing::{SEP, find_last_non_sep_pos};
3
3
 
4
- struct ExtnameCoords {
5
- word: bool,
6
- pred: bool,
7
- dot: bool,
8
- start: usize,
9
- end: usize,
10
- }
11
-
12
- impl ExtnameCoords {
13
- pub fn dec(&mut self) {
14
- self.start -= 1;
15
- if !self.word {
16
- self.end -= 1;
17
- }
18
- }
19
- }
20
-
21
- pub fn extname(pth: &str) -> &str {
22
- let path = pth.as_bytes();
23
- let mut extname = ExtnameCoords {
24
- word: false,
25
- pred: false,
26
- dot: false,
27
- start: path.len(),
28
- end: path.len(),
4
+ pub fn extname(path: &str) -> &str {
5
+ let end = match find_last_non_sep_pos(path.as_bytes()) {
6
+ Some(pos) => pos + 1,
7
+ _ => return "",
29
8
  };
30
-
31
- for &item in path.iter().rev() {
32
- if (item == b'.' && !extname.dot) || item == SEP {
33
- if item == SEP && extname.word {
34
- return ""
35
- }
36
-
37
- if !extname.pred {
38
- extname.dec();
39
- }
40
-
41
- if extname.word {
42
- extname.dot = true;
43
- }
44
- } else {
45
- if extname.dot {
46
- extname.pred = true;
47
- break;
48
- } else {
49
- extname.word = true;
50
- }
51
-
52
- if !extname.pred {
53
- extname.dec()
9
+ let bytes = &path.as_bytes()[..end];
10
+ for (pos, c) in bytes.iter().enumerate().rev() {
11
+ match *c {
12
+ b'.' => {
13
+ let prev = bytes.get(pos - 1);
14
+ if pos == end - 1 || prev == None || prev == Some(&SEP) {
15
+ return "";
16
+ } else {
17
+ return &path[pos..end]
18
+ };
54
19
  }
20
+ SEP => return "",
21
+ _ => {}
55
22
  }
56
- }
57
-
58
- if !extname.pred {
59
- return "";
60
- }
61
-
62
- str::from_utf8(&path[extname.start..extname.end]).unwrap_or("")
23
+ };
24
+ ""
63
25
  }
data/src/helpers.rs CHANGED
@@ -1,7 +1,9 @@
1
1
  use ruru::{RString, Object, Class, AnyObject};
2
2
  extern crate ruby_sys;
3
3
  use debug::RubyDebugInfo;
4
- use pathname::Pathname;
4
+ use ruru;
5
+
6
+ type MaybeString = Result<ruru::RString, ruru::result::Error>;
5
7
 
6
8
  pub trait TryFrom<T>: Sized {
7
9
  type Error;
@@ -23,21 +25,24 @@ pub fn anyobject_to_string(item: AnyObject) -> Result<String, RubyDebugInfo> {
23
25
  if Class::from_existing("String").case_equals(result) {
24
26
  return Ok(RString::from(result.value()).to_string())
25
27
  }
26
-
28
+
27
29
  if Class::from_existing("Pathname").case_equals(result) {
28
30
  return Ok(result.instance_variable_get("@path").
29
31
  try_convert_to::<RString>().
30
32
  unwrap_or(RString::new("")).
31
33
  to_string())
32
34
  }
33
-
35
+
34
36
  if result.respond_to("to_path") {
35
- return Ok(Pathname::from(result.send("to_path", None).value()).
36
- instance_variable_get("@path").
37
- try_convert_to::<RString>().
38
- unwrap_or(RString::new("")).
39
- to_string())
37
+ return Ok(RString::from(result.send("to_path", None).value()).to_string())
40
38
  }
41
39
 
42
40
  Ok(RString::from(result.send("to_s", None).value()).to_string())
43
41
  }
42
+
43
+ pub fn to_str(maybe_string: &MaybeString) -> &str {
44
+ match maybe_string {
45
+ &Ok(ref ruru_string) => ruru_string.to_str(),
46
+ &Err(_) => "",
47
+ }
48
+ }
data/src/lib.rs CHANGED
@@ -25,13 +25,14 @@ mod pathname_sys;
25
25
  mod plus;
26
26
  mod prepend_prefix;
27
27
  pub mod rust_arch_bits;
28
+ mod memrnchr;
28
29
  mod path_parsing;
29
30
  mod relative_path_from;
30
31
 
31
32
  use pathname::Pathname;
32
33
  use pathname_sys::raise;
33
34
 
34
- use ruru::{Module, Object, RString, Boolean, Array, AnyObject};
35
+ use ruru::{Module, Object, RString, Boolean, AnyObject};
35
36
 
36
37
  use pathname_sys::*;
37
38
 
@@ -54,14 +55,16 @@ methods!(
54
55
  }
55
56
 
56
57
  fn pub_children(pth: RString, with_dir: Boolean) -> AnyObject {
57
- pathname::pn_children(pth, with_dir)
58
+ pathname::pn_children(pth, with_dir).
59
+ map_err(|e| raise(e) ).unwrap()
58
60
  }
59
61
 
60
62
  fn pub_children_compat(pth: RString, with_dir: Boolean) -> AnyObject {
61
- pathname::pn_children_compat(pth, with_dir)
63
+ pathname::pn_children_compat(pth, with_dir).
64
+ map_err(|e| raise(e) ).unwrap()
62
65
  }
63
66
 
64
- fn pub_chop_basename(pth: RString) -> Array {
67
+ fn pub_chop_basename(pth: RString) -> AnyObject {
65
68
  pathname::pn_chop_basename(pth)
66
69
  }
67
70
 
@@ -99,12 +102,14 @@ methods!(
99
102
 
100
103
  // pub_entries returns an array of String objects
101
104
  fn pub_entries(pth: RString) -> AnyObject {
102
- pathname::pn_entries(pth)
105
+ pathname::pn_entries(pth).
106
+ map_err(|e| raise(e) ).unwrap()
103
107
  }
104
108
 
105
109
  // pub_entries_compat returns an array of Pathname objects
106
110
  fn pub_entries_compat(pth: RString) -> AnyObject {
107
- pathname::pn_entries_compat(pth)
111
+ pathname::pn_entries_compat(pth).
112
+ map_err(|e| raise(e) ).unwrap()
108
113
  }
109
114
 
110
115
  fn pub_extname(pth: RString) -> RString {
@@ -163,10 +168,13 @@ pub extern "C" fn Init_faster_pathname() {
163
168
  itself.def_self("absolute?", pub_is_absolute);
164
169
  itself.def_self("add_trailing_separator", pub_add_trailing_separator);
165
170
  itself.def_self("del_trailing_separator", pub_del_trailing_separator);
171
+ itself.def_self("chop_basename", pub_chop_basename);
166
172
  itself.def_self("cleanpath_aggressive", pub_cleanpath_aggressive);
167
173
  itself.def_self("cleanpath_conservative", pub_cleanpath_conservative);
168
174
  itself.def_self("directory?", pub_is_directory);
169
175
  itself.def_self("dirname", pub_dirname);
176
+ itself.def_self("entries", pub_entries);
177
+ itself.def_self("entries_compat", pub_entries_compat);
170
178
  itself.def_self("extname", pub_extname);
171
179
  itself.def_self("has_trailing_separator?", pub_has_trailing_separator);
172
180
  //itself.def_self("join", pub_join);
@@ -182,8 +190,5 @@ pub extern "C" fn Init_faster_pathname() {
182
190
  itself.def_self("basename", pub_basename);
183
191
  itself.def_self("children", pub_children);
184
192
  itself.def_self("children_compat", pub_children_compat);
185
- itself.def_self("chop_basename", pub_chop_basename);
186
- itself.def_self("entries", pub_entries);
187
- itself.def_self("entries_compat", pub_entries_compat);
188
193
  });
189
194
  }
data/src/memrnchr.rs ADDED
@@ -0,0 +1,70 @@
1
+ // The code below is based on the fallback `memrchr` implementation from the respective crate.
2
+ //
3
+ // We use this mainly to skip repeated `/`. If there is only one slash, `memrnchr` performs the same
4
+ // as a naive version (e.g. `rposition`). However, it is much faster in pathological cases.
5
+
6
+ use std::mem::size_of;
7
+
8
+ // Returns the byte offset of the last byte that is NOT equal to the given one.
9
+ #[inline(always)]
10
+ pub fn memrnchr(x: u8, text: &[u8]) -> Option<usize> {
11
+ // Scan for a single byte value by reading two `usize` words at a time.
12
+ //
13
+ // Split `text` in three parts
14
+ // - unaligned tail, after the last word aligned address in text
15
+ // - body, scan by 2 words at a time
16
+ // - the first remaining bytes, < 2 word size
17
+ let len = text.len();
18
+ let ptr = text.as_ptr();
19
+
20
+ // search to an aligned boundary
21
+ let end_align = (ptr as usize + len) & (size_of::<usize>() - 1);
22
+ let mut offset;
23
+ if end_align > 0 {
24
+ offset = if end_align >= len { 0 } else { len - end_align };
25
+ if let Some(index) = memrnchr_naive(x, &text[offset..]) {
26
+ return Some(offset + index);
27
+ }
28
+ } else {
29
+ offset = len;
30
+ }
31
+
32
+ // search the body of the text
33
+ let repeated_x = repeat_byte(x);
34
+ while offset >= 2 * size_of::<usize>() {
35
+ debug_assert_eq!((ptr as usize + offset) % size_of::<usize>(), 0);
36
+ unsafe {
37
+ let u = *(ptr.offset(offset as isize - 2 * size_of::<usize>() as isize) as *const usize);
38
+ let v = *(ptr.offset(offset as isize - size_of::<usize>() as isize) as *const usize);
39
+ if u & repeated_x != usize::max_value() || v & repeated_x != usize::max_value() {
40
+ break;
41
+ }
42
+ }
43
+ offset -= 2 * size_of::<usize>();
44
+ }
45
+
46
+ // find the byte before the point the body loop stopped
47
+ memrnchr_naive(x, &text[..offset])
48
+ }
49
+
50
+ #[inline(always)]
51
+ fn memrnchr_naive(x: u8, text: &[u8]) -> Option<usize> {
52
+ text.iter().rposition(|c| *c != x)
53
+ }
54
+
55
+ #[cfg(target_pointer_width = "32")]
56
+ #[inline]
57
+ fn repeat_byte(b: u8) -> usize {
58
+ let mut rep = (b as usize) << 8 | b as usize;
59
+ rep = rep << 16 | rep;
60
+ rep
61
+ }
62
+
63
+ #[cfg(target_pointer_width = "64")]
64
+ #[inline]
65
+ fn repeat_byte(b: u8) -> usize {
66
+ let mut rep = (b as usize) << 8 | b as usize;
67
+ rep = rep << 16 | rep;
68
+ rep = rep << 32 | rep;
69
+ rep
70
+ }
data/src/path_parsing.rs CHANGED
@@ -1,4 +1,7 @@
1
1
  extern crate memchr;
2
+
3
+ use self::memchr::{memchr, memrchr};
4
+ use memrnchr::memrnchr;
2
5
  use std::path::MAIN_SEPARATOR;
3
6
  use std::str;
4
7
 
@@ -7,20 +10,26 @@ lazy_static! {
7
10
  pub static ref SEP_STR: &'static str = str::from_utf8(&[SEP]).unwrap();
8
11
  }
9
12
 
10
- // Returns the byte offset of the last byte preceding a MAIN_SEPARATOR.
11
- pub fn last_non_sep_i(path: &str) -> isize {
12
- last_non_sep_i_before(path, path.len() as isize - 1)
13
+ // Returns the byte offset of the last byte that equals MAIN_SEPARATOR.
14
+ #[inline(always)]
15
+ pub fn find_last_dot_pos(bytes: &[u8]) -> Option<usize> {
16
+ memrchr(b'.', bytes)
17
+ }
18
+
19
+ // Returns the byte offset of the last byte that equals MAIN_SEPARATOR.
20
+ #[inline(always)]
21
+ pub fn find_last_sep_pos(bytes: &[u8]) -> Option<usize> {
22
+ memrchr(SEP, bytes)
23
+ }
24
+
25
+ // Returns the byte offset of the last byte that is not MAIN_SEPARATOR.
26
+ #[inline(always)]
27
+ pub fn find_last_non_sep_pos(bytes: &[u8]) -> Option<usize> {
28
+ memrnchr(SEP, bytes)
13
29
  }
14
30
 
15
- // Returns the byte offset of the last byte preceding a MAIN_SEPARATOR before the given end offset.
16
- pub fn last_non_sep_i_before(path: &str, end: isize) -> isize {
17
- // Works with bytes directly because MAIN_SEPARATOR is always in the ASCII 7-bit range so we can
18
- // avoid the overhead of full UTF-8 processing.
19
- let ptr = path.as_ptr();
20
- let mut i = end;
21
- while i >= 0 {
22
- if unsafe { *ptr.offset(i) } != SEP { break; };
23
- i -= 1;
24
- }
25
- i
31
+ // Whether the given byte sequence contains a MAIN_SEPARATOR.
32
+ #[inline(always)]
33
+ pub fn contains_sep(bytes: &[u8]) -> bool {
34
+ memchr(SEP, bytes) != None
26
35
  }
data/src/pathname.rs CHANGED
@@ -8,8 +8,9 @@ use extname;
8
8
  use plus;
9
9
  use relative_path_from;
10
10
  use debug;
11
- use helpers::TryFrom;
11
+ use helpers::{TryFrom, to_str};
12
12
  use pathname_sys::null_byte_check;
13
+ use path_parsing::{SEP, find_last_non_sep_pos};
13
14
 
14
15
  use ruru;
15
16
  use ruru::{
@@ -40,7 +41,7 @@ impl Pathname {
40
41
  pub fn new(path: &str) -> Pathname {
41
42
  let mut instance = Class::from_existing("Pathname").allocate();
42
43
  instance.instance_variable_set("@path", RString::new(path).to_any_object());
43
-
44
+
44
45
  Pathname { value: instance.value() }
45
46
  }
46
47
 
@@ -58,14 +59,14 @@ impl Pathname {
58
59
  )
59
60
  };
60
61
 
61
- if null_byte_check(path.value()) {
62
+ if null_byte_check(path.value()) {
62
63
  return Err( Exception::new("ArgumentError", Some("pathname contains null byte")) )
63
64
  }
64
65
 
65
66
  // if it crashes then dup the path string here before assigning to @path
66
67
  let mut instance = Class::from_existing("Pathname").allocate();
67
68
  instance.instance_variable_set("@path", RString::from(pth).to_any_object());
68
-
69
+
69
70
  Ok(Pathname { value: instance.value() })
70
71
  }
71
72
 
@@ -84,11 +85,11 @@ impl TryFrom<AnyObject> for Pathname {
84
85
  type Error = debug::RubyDebugInfo;
85
86
  fn try_from(obj: AnyObject) -> Result<Pathname, Self::Error> {
86
87
  if Class::from_existing("String").case_equals(&obj) {
87
- Ok(Pathname::new(&RString::from(obj.value()).to_string()))
88
+ Ok(Pathname::new(&RString::from(obj.value()).to_str()))
88
89
  } else if Class::from_existing("Pathname").case_equals(&obj) {
89
90
  Ok(Pathname::from(obj.value()))
90
91
  } else if obj.respond_to("to_path") {
91
- Ok(Pathname::from(obj.send("to_path", None).value()))
92
+ Ok(Pathname::new(&RString::from(obj.send("to_path", None).value()).to_str()))
92
93
  } else {
93
94
  Err(Self::Error::from(obj))
94
95
  }
@@ -114,7 +115,7 @@ impl VerifiedObject for Pathname {
114
115
  }
115
116
 
116
117
  pub fn pn_add_trailing_separator(pth: MaybeString) -> RString {
117
- let p = pth.ok().unwrap();
118
+ let p = pth.unwrap();
118
119
  let x = format!("{}{}", p.to_str(), "a");
119
120
  match x.rsplit_terminator(MAIN_SEPARATOR).next() {
120
121
  Some("a") => p,
@@ -123,34 +124,26 @@ pub fn pn_add_trailing_separator(pth: MaybeString) -> RString {
123
124
  }
124
125
 
125
126
  pub fn pn_is_absolute(pth: MaybeString) -> Boolean {
126
- Boolean::new(match pth.ok().unwrap_or(RString::new("")).to_str().chars().next() {
127
- Some(c) => c == MAIN_SEPARATOR,
128
- None => false
129
- })
127
+ Boolean::new(to_str(&pth).as_bytes().get(0) == Some(&SEP))
130
128
  }
131
129
 
132
130
  // pub fn pn_ascend(){}
133
131
 
134
132
  pub fn pn_basename(pth: MaybeString, ext: MaybeString) -> RString {
135
- RString::new(
136
- basename::basename(
137
- pth.ok().unwrap_or(RString::new("")).to_str(),
138
- ext.ok().unwrap_or(RString::new("")).to_str()
139
- )
140
- )
133
+ RString::new(basename::basename(to_str(&pth), to_str(&ext)))
141
134
  }
142
135
 
143
- pub fn pn_children(pth: MaybeString, with_dir: MaybeBoolean) -> AnyObject {
144
- let val = pth.ok().unwrap_or(RString::new("."));
145
- let val = val.to_str();
136
+ pub fn pn_children(pth: MaybeString, with_dir: MaybeBoolean) -> Result<AnyObject, Exception> {
137
+ let path = pth.unwrap_or(RString::new("."));
138
+ let path = path.to_str();
146
139
 
147
- if let Ok(entries) = fs::read_dir(val) {
148
- let mut with_directory = with_dir.ok().unwrap_or(Boolean::new(true)).to_bool();
149
- if val == "." {
140
+ if let Ok(entries) = fs::read_dir(path) {
141
+ let mut with_directory = with_dir.unwrap_or(Boolean::new(true)).to_bool();
142
+ if path == "." {
150
143
  with_directory = false;
151
144
  }
152
145
 
153
- let mut arr = Array::new();
146
+ let mut arr = Array::with_capacity(entries.size_hint().1.unwrap_or(0));
154
147
  for entry in entries {
155
148
  if with_directory {
156
149
  match entry {
@@ -165,25 +158,23 @@ pub fn pn_children(pth: MaybeString, with_dir: MaybeBoolean) -> AnyObject {
165
158
  }
166
159
  }
167
160
 
168
- arr.to_any_object()
161
+ Ok(arr.to_any_object())
169
162
  } else {
170
- // TODO: When ruru exceptions are available switch the exception logic
171
- // from the Ruby side to the Rust side
172
- NilClass::new().to_any_object()
163
+ let msg = format!("No such file or directory @ dir_initialize - {}", path);
164
+ Err(Exception::new("Errno::NOENT", Some(&msg)))
173
165
  }
174
166
  }
175
167
 
176
- pub fn pn_children_compat(pth: MaybeString, with_dir: MaybeBoolean) -> AnyObject {
177
- let val = pth.ok().unwrap_or(RString::new("."));
178
- let val = val.to_str();
168
+ pub fn pn_children_compat(pth: MaybeString, with_dir: MaybeBoolean) -> Result<AnyObject, Exception> {
169
+ let path = to_str(&pth);
179
170
 
180
- if let Ok(entries) = fs::read_dir(val) {
181
- let mut with_directory = with_dir.ok().unwrap_or(Boolean::new(true)).to_bool();
182
- if val == "." {
171
+ if let Ok(entries) = fs::read_dir(path) {
172
+ let mut with_directory = with_dir.unwrap_or(Boolean::new(true)).to_bool();
173
+ if path == "." {
183
174
  with_directory = false;
184
175
  }
185
176
 
186
- let mut arr = Array::new();
177
+ let mut arr = Array::with_capacity(entries.size_hint().1.unwrap_or(0));
187
178
  for entry in entries {
188
179
  if with_directory {
189
180
  if let Ok(v) = entry {
@@ -196,82 +187,60 @@ pub fn pn_children_compat(pth: MaybeString, with_dir: MaybeBoolean) -> AnyObject
196
187
  }
197
188
  }
198
189
 
199
- arr.to_any_object()
190
+ Ok(arr.to_any_object())
200
191
  } else {
201
- // TODO: When ruru exceptions are available switch the exception logic
202
- // from the Ruby side to the Rust side
203
- NilClass::new().to_any_object()
192
+ let msg = format!("No such file or directory @ dir_initialize - {}", path);
193
+ Err(Exception::new("Errno::NOENT", Some(&msg)))
204
194
  }
205
195
  }
206
196
 
207
- pub fn pn_chop_basename(pth: MaybeString) -> Array {
208
- let mut arr = Array::with_capacity(2);
209
- let pth = pth.ok().unwrap_or(RString::new(""));
210
- let results = chop_basename::chop_basename(pth.to_str());
211
- match results {
197
+ pub fn pn_chop_basename(pth: MaybeString) -> AnyObject {
198
+ match chop_basename::chop_basename(to_str(&pth)) {
212
199
  Some((dirname, basename)) => {
213
- arr.push(RString::new(&dirname[..]));
214
- arr.push(RString::new(&basename[..]));
215
- arr
200
+ let mut arr = Array::with_capacity(2);
201
+ arr.push(RString::new(&dirname));
202
+ arr.push(RString::new(&basename));
203
+ arr.to_any_object()
216
204
  },
217
- None => arr
205
+ None => NilClass::new().to_any_object()
218
206
  }
219
207
  }
220
208
 
221
209
  // pub fn pn_cleanpath(pth: MaybeString){}
222
210
 
223
211
  pub fn pn_cleanpath_aggressive(pth: MaybeString) -> RString {
224
- let path = cleanpath_aggressive::cleanpath_aggressive(
225
- pth.ok().unwrap_or(RString::new("")).to_str()
226
- );
227
-
228
- RString::new(&path)
212
+ RString::new(&cleanpath_aggressive::cleanpath_aggressive(to_str(&pth)))
229
213
  }
230
214
 
231
215
  pub fn pn_cleanpath_conservative(pth: MaybeString) -> RString {
232
- let path = cleanpath_conservative::cleanpath_conservative(
233
- pth.ok().unwrap_or(RString::new("")).to_str()
234
- );
235
-
236
- RString::new(&path)
216
+ RString::new(&cleanpath_conservative::cleanpath_conservative(to_str(&pth)))
237
217
  }
238
218
 
239
219
  pub fn pn_del_trailing_separator(pth: MaybeString) -> RString {
240
- if let &Ok(ref path) = &pth {
241
- let path = path.to_str();
242
-
243
- if !path.is_empty() {
244
- let path = path.trim_right_matches('/');
245
-
246
- if path.is_empty() {
247
- return RString::new("/");
248
- } else {
249
- return RString::new(path);
250
- }
220
+ {
221
+ let path = to_str(&pth);
222
+ if path.is_empty() {
223
+ return RString::new("/");
224
+ }
225
+ let pos = match find_last_non_sep_pos(path.as_bytes()) {
226
+ Some(pos) => pos,
227
+ None => return RString::new("/"),
228
+ };
229
+ if pos != path.len() - 1 {
230
+ return RString::new(&path[..pos + 1]);
251
231
  }
252
- } else {
253
- return RString::new("");
254
232
  }
255
-
256
233
  pth.unwrap()
257
234
  }
258
235
 
259
236
  // pub fn pn_descend(){}
260
237
 
261
238
  pub fn pn_is_directory(pth: MaybeString) -> Boolean {
262
- Boolean::new(
263
- Path::new(
264
- pth.ok().unwrap_or(RString::new("")).to_str()
265
- ).is_dir()
266
- )
239
+ Boolean::new(Path::new(to_str(&pth)).is_dir())
267
240
  }
268
241
 
269
242
  pub fn pn_dirname(pth: MaybeString) -> RString {
270
- RString::new(
271
- dirname::dirname(
272
- pth.ok().unwrap_or(RString::new("")).to_str()
273
- )
274
- )
243
+ RString::new(dirname::dirname(to_str(&pth)))
275
244
  }
276
245
 
277
246
  // pub fn pn_each_child(){}
@@ -280,60 +249,54 @@ pub fn pn_dirname(pth: MaybeString) -> RString {
280
249
  // NilClass::new()
281
250
  // }
282
251
 
283
- pub fn pn_entries(pth: MaybeString) -> AnyObject {
284
- if let Ok(files) = fs::read_dir(pth.ok().unwrap_or(RString::new("")).to_str()) {
285
- let mut arr = Array::new();
252
+ pub fn pn_entries(pth: MaybeString) -> Result<AnyObject, Exception> {
253
+ let path = to_str(&pth);
254
+ if let Ok(files) = fs::read_dir(path) {
255
+ let mut arr = Array::with_capacity(files.size_hint().1.unwrap_or(0) + 2);
286
256
 
287
257
  arr.push(RString::new("."));
288
258
  arr.push(RString::new(".."));
289
259
 
290
260
  for file in files {
291
- let file_name_str = file.unwrap().file_name().into_string().unwrap();
292
- arr.push(RString::new(&file_name_str[..]));
261
+ arr.push(RString::new(file.unwrap().file_name().to_str().unwrap()));
293
262
  }
294
263
 
295
- arr.to_any_object()
264
+ Ok(arr.to_any_object())
296
265
  } else {
297
- // TODO: When ruru exceptions are available switch the exception logic
298
- // from the Ruby side to the Rust side
299
- NilClass::new().to_any_object()
266
+ let msg = format!("No such file or directory @ dir_initialize - {}", path);
267
+ Err(Exception::new("Errno::NOENT", Some(&msg)))
300
268
  }
301
269
  }
302
270
 
303
- pub fn pn_entries_compat(pth: MaybeString) -> AnyObject {
304
- if let Ok(files) = fs::read_dir(pth.ok().unwrap_or(RString::new("")).to_str()) {
305
- let mut arr = Array::new();
271
+ pub fn pn_entries_compat(pth: MaybeString) -> Result<AnyObject, Exception> {
272
+ let path = to_str(&pth);
273
+ if let Ok(files) = fs::read_dir(path) {
274
+ let mut arr = Array::with_capacity(files.size_hint().1.unwrap_or(0) + 2);
306
275
 
307
276
  arr.push(Pathname::new("."));
308
277
  arr.push(Pathname::new(".."));
309
278
 
310
279
  for file in files {
311
- let file_name_str = file.unwrap().file_name().into_string().unwrap();
312
- arr.push(Pathname::new(&file_name_str));
280
+ arr.push(Pathname::new(file.unwrap().file_name().to_str().unwrap()));
313
281
  }
314
282
 
315
- arr.to_any_object()
283
+ Ok(arr.to_any_object())
316
284
  } else {
317
- // TODO: When ruru exceptions are available switch the exception logic
318
- // from the Ruby side to the Rust side
319
- NilClass::new().to_any_object()
285
+ let msg = format!("No such file or directory @ dir_initialize - {}", path);
286
+ Err(Exception::new("Errno::NOENT", Some(&msg)))
320
287
  }
321
288
  }
322
289
 
323
290
  pub fn pn_extname(pth: MaybeString) -> RString {
324
- RString::new(
325
- extname::extname(pth.ok().unwrap_or(RString::new("")).to_str())
326
- )
291
+ RString::new(extname::extname(to_str(&pth)))
327
292
  }
328
293
 
329
294
  // pub fn pn_find(pth: MaybeString, ignore_error: Boolean){}
330
295
 
331
296
  pub fn pn_has_trailing_separator(pth: MaybeString) -> Boolean {
332
- let v = pth.ok().unwrap_or(RString::new(""));
333
- match chop_basename::chop_basename(v.to_str()) {
334
- Some((a,b)) => {
335
- Boolean::new(a.len() + b.len() < v.to_str().len())
336
- },
297
+ let v = to_str(&pth);
298
+ match chop_basename::chop_basename(v) {
299
+ Some((a,b)) => Boolean::new(a.len() + b.len() < v.len()),
337
300
  _ => Boolean::new(false)
338
301
  }
339
302
  }
@@ -353,13 +316,13 @@ pub fn pn_join(args: MaybeArray) -> AnyObject {
353
316
 
354
317
  let item = args.pop();
355
318
  result = plus::plus_paths(&anyobject_to_string(item).unwrap(), &result);
356
- if result.chars().next() == Some(MAIN_SEPARATOR) {
319
+ if result.as_bytes().get(0) == Some(&SEP) {
357
320
  return Pathname::new(&result).to_any_object()
358
321
  }
359
322
 
360
323
  qty -= 1;
361
324
  }
362
-
325
+
363
326
  let result = plus::plus_paths(&path_self, &result);
364
327
 
365
328
  Pathname::new(&result).to_any_object()
@@ -374,23 +337,17 @@ pub fn pn_join(args: MaybeArray) -> AnyObject {
374
337
  // pub fn pn_parent(pth: MaybeString){}
375
338
 
376
339
  pub fn pn_plus(pth1: MaybeString, pth2: MaybeString) -> RString {
377
- RString::new(
378
- &plus::plus_paths(
379
- pth1.ok().unwrap_or(RString::new("")).to_str(),
380
- pth2.ok().unwrap_or(RString::new("")).to_str()
381
- )[..]
382
- )
340
+ RString::new(&plus::plus_paths(to_str(&pth1), to_str(&pth2)))
383
341
  }
384
342
 
385
343
  // pub fn pn_prepend_prefix(prefix: MaybeString, relpath: MaybeString){}
386
344
 
387
345
  pub fn pn_is_relative(pth: MaybeString) -> Boolean {
388
- Boolean::new(
389
- match pth.ok().unwrap_or(RString::new(&MAIN_SEPARATOR.to_string()[..])).to_str().chars().next() {
390
- Some(c) => c != MAIN_SEPARATOR,
391
- None => true
392
- }
393
- )
346
+ let path = match &pth {
347
+ &Ok(ref ruru_string) => ruru_string.to_str(),
348
+ &Err(_) => return Boolean::new(false),
349
+ };
350
+ Boolean::new(path.as_bytes().get(0) != Some(&SEP))
394
351
  }
395
352
 
396
353
  // pub fn pn_root(pth: MaybeString){}