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/pathname.rs
CHANGED
|
@@ -1,364 +1,365 @@
|
|
|
1
|
-
use
|
|
2
|
-
use
|
|
3
|
-
use chop_basename;
|
|
4
|
-
use cleanpath_aggressive;
|
|
5
|
-
use cleanpath_conservative;
|
|
6
|
-
use dirname;
|
|
7
|
-
use extname;
|
|
8
|
-
use plus;
|
|
9
|
-
use relative_path_from;
|
|
10
|
-
use debug;
|
|
11
|
-
use helpers::{TryFrom, to_str};
|
|
12
|
-
use pathname_sys::null_byte_check;
|
|
13
|
-
use path_parsing::{SEP, find_last_non_sep_pos};
|
|
14
|
-
|
|
15
|
-
use ruru;
|
|
16
|
-
use ruru::{
|
|
17
|
-
RString,
|
|
18
|
-
Boolean,
|
|
19
|
-
Array,
|
|
20
|
-
AnyObject,
|
|
21
|
-
NilClass,
|
|
22
|
-
Object,
|
|
23
|
-
Class,
|
|
24
|
-
VerifiedObject,
|
|
25
|
-
Exception as Exc,
|
|
26
|
-
AnyException as Exception
|
|
27
|
-
};
|
|
28
|
-
use ruru::types::{Value, ValueType};
|
|
29
|
-
use std::path::{MAIN_SEPARATOR,Path};
|
|
1
|
+
use std::borrow::Cow;
|
|
2
|
+
use std::ffi::OsStr;
|
|
30
3
|
use std::fs;
|
|
4
|
+
use std::io;
|
|
5
|
+
|
|
6
|
+
use rutie::{AnyException, AnyObject, Array, Boolean, Class, NilClass, Object, RString};
|
|
7
|
+
|
|
8
|
+
use crate::basename;
|
|
9
|
+
use crate::chop_basename::{self, is_relative};
|
|
10
|
+
use crate::cleanpath_aggressive;
|
|
11
|
+
use crate::cleanpath_conservative::{self, add_trailing_separator, del_trailing_separator, has_trailing_separator};
|
|
12
|
+
use crate::dirname;
|
|
13
|
+
use crate::extname;
|
|
14
|
+
use crate::path_parsing::Rules;
|
|
15
|
+
use crate::plus;
|
|
16
|
+
use crate::relative_path_from::{self, RelativePathError};
|
|
17
|
+
use crate::ruby::{
|
|
18
|
+
argument_error, check_max_arguments, error, new_pathname, path_like_to_string, pathname_class,
|
|
19
|
+
truthy_argument, EncodingId, EncodingOf, PathArgument, PathString, RubyResult,
|
|
20
|
+
};
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
type MaybeBoolean = Result<ruru::Boolean, ruru::result::Error>;
|
|
35
|
-
|
|
36
|
-
pub struct Pathname {
|
|
37
|
-
value: Value
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
impl Pathname {
|
|
41
|
-
pub fn new(path: &str) -> Pathname {
|
|
42
|
-
let mut instance = Class::from_existing("Pathname").allocate();
|
|
43
|
-
instance.instance_variable_set("@path", RString::new(path).to_any_object());
|
|
44
|
-
|
|
45
|
-
Pathname { value: instance.value() }
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
pub fn new_checked(path: AnyObject) -> Result<Pathname, Exception> {
|
|
49
|
-
let pth: Value = if Class::from_existing("String").case_equals(&path) {
|
|
50
|
-
path.value()
|
|
51
|
-
} else if path.respond_to("to_path") {
|
|
52
|
-
path.send("to_path", None).value()
|
|
53
|
-
} else {
|
|
54
|
-
return Err(
|
|
55
|
-
Exception::new(
|
|
56
|
-
"ArgumentError",
|
|
57
|
-
Some("The type for the argument provided to Pathname.new was invalid.")
|
|
58
|
-
)
|
|
59
|
-
)
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
if null_byte_check(path.value()) {
|
|
63
|
-
return Err( Exception::new("ArgumentError", Some("pathname contains null byte")) )
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// if it crashes then dup the path string here before assigning to @path
|
|
67
|
-
let mut instance = Class::from_existing("Pathname").allocate();
|
|
68
|
-
instance.instance_variable_set("@path", RString::from(pth).to_any_object());
|
|
22
|
+
// Ruby's path rules on this platform
|
|
23
|
+
const RULES: Rules = Rules::NATIVE;
|
|
69
24
|
|
|
70
|
-
|
|
25
|
+
pub fn pn_add_trailing_separator(arguments: &[AnyObject]) -> RubyResult {
|
|
26
|
+
check_max_arguments(arguments, 1)?;
|
|
27
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
28
|
+
if argument.is_missing() {
|
|
29
|
+
return Err(implicit_conversion_error(arguments.first()));
|
|
71
30
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
31
|
+
let path = argument.path()?;
|
|
32
|
+
match (add_trailing_separator(RULES, Cow::Borrowed(path.bytes())), argument.string()) {
|
|
33
|
+
(Cow::Borrowed(_), Some(string)) => Ok(string.to_any_object()),
|
|
34
|
+
(result, _) => Ok(path.to_ruby(&result).into()),
|
|
75
35
|
}
|
|
76
36
|
}
|
|
77
37
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
38
|
+
pub fn pn_is_absolute(arguments: &[AnyObject]) -> RubyResult {
|
|
39
|
+
check_max_arguments(arguments, 1)?;
|
|
40
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
41
|
+
let path = argument.path()?;
|
|
42
|
+
Ok(Boolean::new(!is_relative(RULES, path.bytes())).into())
|
|
82
43
|
}
|
|
83
44
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
45
|
+
pub fn pn_basename(arguments: &[AnyObject]) -> RubyResult {
|
|
46
|
+
check_max_arguments(arguments, 2)?;
|
|
47
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
48
|
+
let path = argument.path()?;
|
|
49
|
+
let ext_argument = PathArgument::new(arguments, 1)?;
|
|
50
|
+
let ext = ext_argument.path()?;
|
|
51
|
+
let (component, base_len) = basename::last_component(RULES, path.bytes());
|
|
52
|
+
let result = match base_len {
|
|
53
|
+
// Like Ruby, ignore an extension in an incompatible encoding, and only
|
|
54
|
+
// remove one that starts a character.
|
|
55
|
+
Some(base_len) if !ext.bytes().is_empty() &&
|
|
56
|
+
(path.encoding == ext.encoding || EncodingOf::new(&path).merge(&ext).is_ok()) => {
|
|
57
|
+
let end = basename::ext_end(RULES, component, base_len, ext.bytes());
|
|
58
|
+
if end == component.len() || path.is_char_boundary(component, end) { &component[..end] } else { component }
|
|
95
59
|
}
|
|
96
|
-
|
|
60
|
+
_ => component,
|
|
61
|
+
};
|
|
62
|
+
Ok(path.to_ruby(result).into())
|
|
97
63
|
}
|
|
98
64
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
65
|
+
pub fn pn_children(arguments: &[AnyObject]) -> RubyResult {
|
|
66
|
+
check_max_arguments(arguments, 2)?;
|
|
67
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
68
|
+
let path = argument.path_or(b".")?;
|
|
69
|
+
let with_directory = truthy_argument(arguments, 1, true) && path.bytes() != b".";
|
|
70
|
+
let error_path = error_path(&argument, ".");
|
|
71
|
+
children(&path, with_directory, &error_path, |entry| Ok(entry.into()))
|
|
104
72
|
}
|
|
105
73
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
74
|
+
pub fn pn_children_compat(arguments: &[AnyObject]) -> RubyResult {
|
|
75
|
+
check_max_arguments(arguments, 2)?;
|
|
76
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
77
|
+
let path = argument.path()?;
|
|
78
|
+
let with_directory = truthy_argument(arguments, 1, true) && path.bytes() != b".";
|
|
79
|
+
let error_path = error_path(&argument, "");
|
|
80
|
+
let pathname = pathname_class()?;
|
|
81
|
+
children(&path, with_directory, &error_path, |entry| new_pathname(&pathname, entry))
|
|
82
|
+
}
|
|
111
83
|
|
|
112
|
-
|
|
113
|
-
|
|
84
|
+
fn children<F>(path: &PathString, with_directory: bool, error_path: &AnyObject, mut wrap: F) -> RubyResult
|
|
85
|
+
where F: FnMut(RString) -> RubyResult {
|
|
86
|
+
let entries = read_dir(path, error_path)?;
|
|
87
|
+
let filesystem = EncodingId::filesystem();
|
|
88
|
+
let mut array = Array::with_capacity(entries.size_hint().0);
|
|
89
|
+
for entry in entries {
|
|
90
|
+
let entry = entry.map_err(|e| system_call_error(&e, error_path, "dir_read"))?;
|
|
91
|
+
let os_name = entry.file_name();
|
|
92
|
+
let (name, name_encoding) = file_name(&os_name, filesystem);
|
|
93
|
+
let string = if with_directory {
|
|
94
|
+
// `File.join(path, name)`, encoded like that, or as the file name if they don't mix.
|
|
95
|
+
let encoding = EncodingOf::new(path).merge_with(EncodingOf::of_bytes(&name, name_encoding)).
|
|
96
|
+
map_or(name_encoding, |encoding| encoding.encoding);
|
|
97
|
+
encoding.new_string(&RULES.join(path.bytes(), &name))
|
|
98
|
+
} else {
|
|
99
|
+
name_encoding.new_string(&name)
|
|
100
|
+
};
|
|
101
|
+
array.push(wrap(string)?);
|
|
114
102
|
}
|
|
103
|
+
Ok(array.into())
|
|
115
104
|
}
|
|
116
105
|
|
|
117
|
-
pub fn
|
|
118
|
-
|
|
119
|
-
let
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
106
|
+
pub fn pn_chop_basename(arguments: &[AnyObject]) -> RubyResult {
|
|
107
|
+
check_max_arguments(arguments, 1)?;
|
|
108
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
109
|
+
let path = argument.path()?;
|
|
110
|
+
match chop_basename::chop_basename(RULES, path.bytes()) {
|
|
111
|
+
Some((dirname, basename)) => {
|
|
112
|
+
let mut array = Array::with_capacity(2);
|
|
113
|
+
array.push(path.to_ruby(dirname));
|
|
114
|
+
array.push(path.to_ruby(basename));
|
|
115
|
+
Ok(array.into())
|
|
116
|
+
},
|
|
117
|
+
None => Ok(NilClass::new().into()),
|
|
123
118
|
}
|
|
124
119
|
}
|
|
125
120
|
|
|
126
|
-
pub fn
|
|
127
|
-
|
|
121
|
+
pub fn pn_cleanpath_aggressive(arguments: &[AnyObject]) -> RubyResult {
|
|
122
|
+
check_max_arguments(arguments, 1)?;
|
|
123
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
124
|
+
let path = argument.path()?;
|
|
125
|
+
Ok(path.to_ruby(&cleanpath_aggressive::cleanpath_aggressive(RULES, path.bytes())).into())
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
128
|
+
pub fn pn_cleanpath_conservative(arguments: &[AnyObject]) -> RubyResult {
|
|
129
|
+
check_max_arguments(arguments, 1)?;
|
|
130
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
131
|
+
let path = argument.path()?;
|
|
132
|
+
Ok(path.to_ruby(&cleanpath_conservative::cleanpath_conservative(RULES, path.bytes())).into())
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
pub fn
|
|
137
|
-
|
|
138
|
-
let
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
with_directory = false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let mut arr = Array::with_capacity(entries.size_hint().1.unwrap_or(0));
|
|
147
|
-
for entry in entries {
|
|
148
|
-
if with_directory {
|
|
149
|
-
match entry {
|
|
150
|
-
Ok(v) => { arr.push(RString::new(v.path().to_str().unwrap())); },
|
|
151
|
-
_ => {}
|
|
152
|
-
};
|
|
153
|
-
} else {
|
|
154
|
-
match entry {
|
|
155
|
-
Ok(v) => { arr.push(RString::new(v.file_name().to_str().unwrap())); },
|
|
156
|
-
_ => {}
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
Ok(arr.to_any_object())
|
|
162
|
-
} else {
|
|
163
|
-
let msg = format!("No such file or directory @ dir_initialize - {}", path);
|
|
164
|
-
Err(Exception::new("Errno::NOENT", Some(&msg)))
|
|
135
|
+
pub fn pn_del_trailing_separator(arguments: &[AnyObject]) -> RubyResult {
|
|
136
|
+
check_max_arguments(arguments, 1)?;
|
|
137
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
138
|
+
let path = argument.path()?;
|
|
139
|
+
match (del_trailing_separator(RULES, path.bytes()), argument.string()) {
|
|
140
|
+
(Cow::Borrowed(result), Some(string)) if result.len() == path.bytes().len() => Ok(string.to_any_object()),
|
|
141
|
+
(result, _) => Ok(path.to_ruby(&result).into()),
|
|
165
142
|
}
|
|
166
143
|
}
|
|
167
144
|
|
|
168
|
-
pub fn
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
let mut arr = Array::with_capacity(entries.size_hint().1.unwrap_or(0));
|
|
178
|
-
for entry in entries {
|
|
179
|
-
if with_directory {
|
|
180
|
-
if let Ok(v) = entry {
|
|
181
|
-
arr.push(Pathname::new(v.path().to_str().unwrap()));
|
|
182
|
-
};
|
|
183
|
-
} else {
|
|
184
|
-
if let Ok(v) = entry {
|
|
185
|
-
arr.push(Pathname::new(v.file_name().to_str().unwrap()));
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
Ok(arr.to_any_object())
|
|
191
|
-
} else {
|
|
192
|
-
let msg = format!("No such file or directory @ dir_initialize - {}", path);
|
|
193
|
-
Err(Exception::new("Errno::NOENT", Some(&msg)))
|
|
194
|
-
}
|
|
145
|
+
pub fn pn_is_directory(arguments: &[AnyObject]) -> RubyResult {
|
|
146
|
+
check_max_arguments(arguments, 1)?;
|
|
147
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
148
|
+
let path = argument.path()?;
|
|
149
|
+
// Windows takes "dir\\..." for "dir"; Ruby doesn't.
|
|
150
|
+
let is_directory = !RULES.has_dots_name(path.bytes()) &&
|
|
151
|
+
path.os_path().map_or(false, |os_path| os_path.is_dir());
|
|
152
|
+
Ok(Boolean::new(is_directory).into())
|
|
195
153
|
}
|
|
196
154
|
|
|
197
|
-
pub fn
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
arr.push(RString::new(&basename));
|
|
203
|
-
arr.to_any_object()
|
|
204
|
-
},
|
|
205
|
-
None => NilClass::new().to_any_object()
|
|
206
|
-
}
|
|
155
|
+
pub fn pn_dirname(arguments: &[AnyObject]) -> RubyResult {
|
|
156
|
+
check_max_arguments(arguments, 1)?;
|
|
157
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
158
|
+
let path = argument.path()?;
|
|
159
|
+
Ok(path.to_ruby(&dirname::dirname(RULES, path.bytes())).into())
|
|
207
160
|
}
|
|
208
161
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
162
|
+
// Returns an array of `String`s
|
|
163
|
+
pub fn pn_entries(arguments: &[AnyObject]) -> RubyResult {
|
|
164
|
+
check_max_arguments(arguments, 1)?;
|
|
165
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
166
|
+
let path = argument.path()?;
|
|
167
|
+
let error_path = error_path(&argument, "");
|
|
168
|
+
entries(&path, &error_path, |entry| Ok(entry.into()))
|
|
213
169
|
}
|
|
214
170
|
|
|
215
|
-
|
|
216
|
-
|
|
171
|
+
// Returns an array of `Pathname`s
|
|
172
|
+
pub fn pn_entries_compat(arguments: &[AnyObject]) -> RubyResult {
|
|
173
|
+
check_max_arguments(arguments, 1)?;
|
|
174
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
175
|
+
let path = argument.path()?;
|
|
176
|
+
let error_path = error_path(&argument, "");
|
|
177
|
+
let pathname = pathname_class()?;
|
|
178
|
+
entries(&path, &error_path, |entry| new_pathname(&pathname, entry))
|
|
217
179
|
}
|
|
218
180
|
|
|
219
|
-
|
|
220
|
-
{
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
181
|
+
fn entries<F>(path: &PathString, error_path: &AnyObject, mut wrap: F) -> RubyResult
|
|
182
|
+
where F: FnMut(RString) -> RubyResult {
|
|
183
|
+
let files = read_dir(path, error_path)?;
|
|
184
|
+
let filesystem = EncodingId::filesystem();
|
|
185
|
+
let mut array = Array::with_capacity(files.size_hint().0 + 2);
|
|
186
|
+
array.push(wrap(filesystem.new_string(b"."))?);
|
|
187
|
+
array.push(wrap(filesystem.new_string(b".."))?);
|
|
188
|
+
for file in files {
|
|
189
|
+
let file = file.map_err(|e| system_call_error(&e, error_path, "dir_read"))?;
|
|
190
|
+
let os_name = file.file_name();
|
|
191
|
+
let (name, encoding) = file_name(&os_name, filesystem);
|
|
192
|
+
array.push(wrap(encoding.new_string(&name))?);
|
|
232
193
|
}
|
|
233
|
-
|
|
194
|
+
Ok(array.into())
|
|
234
195
|
}
|
|
235
196
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
197
|
+
pub fn pn_extname(arguments: &[AnyObject]) -> RubyResult {
|
|
198
|
+
check_max_arguments(arguments, 1)?;
|
|
199
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
200
|
+
let path = argument.path()?;
|
|
201
|
+
Ok(path.to_ruby(extname::extname(RULES, path.bytes())).into())
|
|
240
202
|
}
|
|
241
203
|
|
|
242
|
-
pub fn
|
|
243
|
-
|
|
204
|
+
pub fn pn_has_trailing_separator(arguments: &[AnyObject]) -> RubyResult {
|
|
205
|
+
check_max_arguments(arguments, 1)?;
|
|
206
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
207
|
+
let path = argument.path()?;
|
|
208
|
+
Ok(Boolean::new(has_trailing_separator(RULES, path.bytes())).into())
|
|
244
209
|
}
|
|
245
210
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
} else {
|
|
266
|
-
let msg = format!("No such file or directory @ dir_initialize - {}", path);
|
|
267
|
-
Err(Exception::new("Errno::NOENT", Some(&msg)))
|
|
211
|
+
pub fn pn_join(arguments: &[AnyObject]) -> RubyResult {
|
|
212
|
+
if arguments.is_empty() {
|
|
213
|
+
return Err(argument_error("wrong number of arguments (given 0, expected 1+)"));
|
|
214
|
+
}
|
|
215
|
+
let pathname = pathname_class()?;
|
|
216
|
+
|
|
217
|
+
// Copy every path out of Ruby before calling more Ruby code: the strings
|
|
218
|
+
// returned by `to_path` or `to_s` aren't referenced from anywhere the
|
|
219
|
+
// garbage collector looks once we only hold their bytes.
|
|
220
|
+
let mut paths: Vec<Vec<u8>> = Vec::with_capacity(arguments.len());
|
|
221
|
+
let mut encoding: Option<EncodingOf> = None;
|
|
222
|
+
for argument in arguments {
|
|
223
|
+
let string = path_like_to_string(argument, &pathname)?;
|
|
224
|
+
let path = PathString::new(&string)?;
|
|
225
|
+
encoding = Some(match encoding {
|
|
226
|
+
None => EncodingOf::new(&path),
|
|
227
|
+
Some(previous) => previous.merge(&path)?,
|
|
228
|
+
});
|
|
229
|
+
paths.push(path.bytes().to_vec());
|
|
268
230
|
}
|
|
269
|
-
}
|
|
270
|
-
|
|
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);
|
|
275
|
-
|
|
276
|
-
arr.push(Pathname::new("."));
|
|
277
|
-
arr.push(Pathname::new(".."));
|
|
278
231
|
|
|
279
|
-
|
|
280
|
-
|
|
232
|
+
let mut parts = paths.iter().rev();
|
|
233
|
+
let mut result: Cow<[u8]> = match parts.next() {
|
|
234
|
+
Some(last) => Cow::Borrowed(last),
|
|
235
|
+
None => Cow::Borrowed(b""),
|
|
236
|
+
};
|
|
237
|
+
for part in parts {
|
|
238
|
+
if !is_relative(RULES, &result) {
|
|
239
|
+
break;
|
|
281
240
|
}
|
|
282
|
-
|
|
283
|
-
Ok(arr.to_any_object())
|
|
284
|
-
} else {
|
|
285
|
-
let msg = format!("No such file or directory @ dir_initialize - {}", path);
|
|
286
|
-
Err(Exception::new("Errno::NOENT", Some(&msg)))
|
|
241
|
+
result = Cow::Owned(plus::plus_paths(RULES, part, &result).into_owned());
|
|
287
242
|
}
|
|
243
|
+
let encoding = match encoding {
|
|
244
|
+
Some(encoding) => encoding.encoding,
|
|
245
|
+
None => EncodingId::us_ascii(),
|
|
246
|
+
};
|
|
247
|
+
new_pathname(&pathname, encoding.new_string(&result))
|
|
288
248
|
}
|
|
289
249
|
|
|
290
|
-
pub fn
|
|
291
|
-
|
|
250
|
+
pub fn pn_plus(arguments: &[AnyObject]) -> RubyResult {
|
|
251
|
+
check_max_arguments(arguments, 2)?;
|
|
252
|
+
let argument1 = PathArgument::new(arguments, 0)?;
|
|
253
|
+
let path1 = argument1.path()?;
|
|
254
|
+
let argument2 = PathArgument::new(arguments, 1)?;
|
|
255
|
+
let path2 = argument2.path()?;
|
|
256
|
+
let encoding = EncodingOf::new(&path1).merge(&path2)?.encoding;
|
|
257
|
+
Ok(encoding.new_string(&plus::plus_paths(RULES, path1.bytes(), path2.bytes())).into())
|
|
292
258
|
}
|
|
293
259
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
Some((a,b)) => Boolean::new(a.len() + b.len() < v.len()),
|
|
300
|
-
_ => Boolean::new(false)
|
|
260
|
+
pub fn pn_is_relative(arguments: &[AnyObject]) -> RubyResult {
|
|
261
|
+
check_max_arguments(arguments, 1)?;
|
|
262
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
263
|
+
if argument.is_missing() {
|
|
264
|
+
return Ok(Boolean::new(false).into());
|
|
301
265
|
}
|
|
266
|
+
let path = argument.path()?;
|
|
267
|
+
Ok(Boolean::new(is_relative(RULES, path.bytes())).into())
|
|
302
268
|
}
|
|
303
269
|
|
|
304
|
-
pub fn
|
|
305
|
-
|
|
306
|
-
let
|
|
307
|
-
let
|
|
308
|
-
|
|
309
|
-
|
|
270
|
+
pub fn pn_relative_path_from(arguments: &[AnyObject]) -> RubyResult {
|
|
271
|
+
check_max_arguments(arguments, 2)?;
|
|
272
|
+
let pathname = pathname_class()?;
|
|
273
|
+
let argument = PathArgument::new(arguments, 0)?;
|
|
274
|
+
let dest = argument.path()?;
|
|
275
|
+
let base = match arguments.get(1) {
|
|
276
|
+
Some(base_directory) => {
|
|
277
|
+
let base_string = path_like_to_string(base_directory, &pathname)?;
|
|
278
|
+
// Copied right away, see `pn_join`.
|
|
279
|
+
PathString::new(&base_string)?.into_owned()
|
|
280
|
+
}
|
|
281
|
+
None => PathString::empty(),
|
|
282
|
+
};
|
|
283
|
+
let encoding = EncodingOf::new(&dest).merge(&base)?.encoding;
|
|
284
|
+
let inspect = |bytes: &[u8]| crate::ruby::inspect(&encoding.new_string(bytes));
|
|
285
|
+
|
|
286
|
+
match relative_path_from::relative_path_from(RULES, dest.bytes(), base.bytes()) {
|
|
287
|
+
Ok(path) => new_pathname(&pathname, encoding.new_string(&path)),
|
|
288
|
+
Err(RelativePathError::DifferentPrefix(dest_prefix, base_directory)) => {
|
|
289
|
+
let message = format!("different prefix: {} and {}", inspect(&dest_prefix)?, inspect(&base_directory)?);
|
|
290
|
+
Err(argument_error(&message))
|
|
291
|
+
},
|
|
292
|
+
Err(RelativePathError::BaseDirectoryHasDotDot(base_directory)) => {
|
|
293
|
+
Err(argument_error(&format!("base_directory has ..: {}", inspect(&base_directory)?)))
|
|
294
|
+
},
|
|
310
295
|
}
|
|
296
|
+
}
|
|
311
297
|
|
|
312
|
-
|
|
298
|
+
// The path to name in a `SystemCallError`
|
|
299
|
+
fn error_path(argument: &PathArgument, default: &str) -> AnyObject {
|
|
300
|
+
match argument.string() {
|
|
301
|
+
Some(string) => string.to_any_object(),
|
|
302
|
+
None => RString::new_utf8(default).into(),
|
|
303
|
+
}
|
|
304
|
+
}
|
|
313
305
|
|
|
314
|
-
|
|
315
|
-
|
|
306
|
+
fn read_dir(path: &PathString, error_path: &AnyObject) -> Result<fs::ReadDir, AnyException> {
|
|
307
|
+
let os_path = path.os_path().map_err(|e| system_call_error(&e, error_path, "dir_initialize"))?;
|
|
308
|
+
fs::read_dir(&*os_path).map_err(|e| system_call_error(&e, error_path, "dir_initialize"))
|
|
309
|
+
}
|
|
316
310
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
311
|
+
// `SystemCallError.new(path, errno, func)` gives the same `Errno::*` error
|
|
312
|
+
// and message as Ruby's `Dir` methods, e.g.
|
|
313
|
+
// "No such file or directory @ dir_initialize - path".
|
|
314
|
+
fn system_call_error(err: &io::Error, path: &AnyObject, func: &str) -> AnyException {
|
|
315
|
+
#[cfg(unix)]
|
|
316
|
+
{
|
|
317
|
+
if let Some(errno) = err.raw_os_error() {
|
|
318
|
+
let arguments = [path.clone(), rutie::Integer::new(errno.into()).into(), RString::new_utf8(func).into()];
|
|
319
|
+
if let Ok(exception) = Class::system_call_error().protect_send("new", &arguments) {
|
|
320
|
+
if let Ok(exception) = exception.try_convert_to::<AnyException>() {
|
|
321
|
+
return exception;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
321
324
|
}
|
|
322
|
-
|
|
323
|
-
qty -= 1;
|
|
324
325
|
}
|
|
325
|
-
|
|
326
|
-
let
|
|
327
|
-
|
|
328
|
-
|
|
326
|
+
#[cfg(not(unix))]
|
|
327
|
+
let _ = func;
|
|
328
|
+
let path = crate::ruby::inspect(path).unwrap_or_default();
|
|
329
|
+
AnyException::from_io_error(err, &path)
|
|
329
330
|
}
|
|
330
331
|
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
// pub fn pn_parent(pth: MaybeString){}
|
|
338
|
-
|
|
339
|
-
pub fn pn_plus(pth1: MaybeString, pth2: MaybeString) -> RString {
|
|
340
|
-
RString::new(&plus::plus_paths(to_str(&pth1), to_str(&pth2)))
|
|
332
|
+
// A file name from the OS as Ruby's `Dir` methods return it, in the
|
|
333
|
+
// filesystem encoding: its bytes, and their encoding.
|
|
334
|
+
#[cfg(unix)]
|
|
335
|
+
fn file_name(name: &OsStr, filesystem: EncodingId) -> (Cow<'_, [u8]>, EncodingId) {
|
|
336
|
+
use std::os::unix::ffi::OsStrExt;
|
|
337
|
+
(Cow::Borrowed(name.as_bytes()), filesystem)
|
|
341
338
|
}
|
|
342
339
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
&Err(_) => return Boolean::new(false),
|
|
340
|
+
#[cfg(not(unix))]
|
|
341
|
+
fn file_name(name: &OsStr, filesystem: EncodingId) -> (Cow<'_, [u8]>, EncodingId) {
|
|
342
|
+
let utf8 = match name.to_string_lossy() {
|
|
343
|
+
Cow::Borrowed(name) => Cow::Borrowed(name.as_bytes()),
|
|
344
|
+
Cow::Owned(name) => Cow::Owned(name.into_bytes()),
|
|
349
345
|
};
|
|
350
|
-
|
|
346
|
+
if utf8.is_ascii() || filesystem == EncodingId::utf8() {
|
|
347
|
+
return (utf8, filesystem);
|
|
348
|
+
}
|
|
349
|
+
match EncodingId::utf8().transcode(&utf8, filesystem) {
|
|
350
|
+
Some(encoded) => (Cow::Owned(encoded), filesystem),
|
|
351
|
+
None => (utf8, EncodingId::utf8()),
|
|
352
|
+
}
|
|
351
353
|
}
|
|
352
354
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
355
|
+
fn implicit_conversion_error(argument: Option<&AnyObject>) -> AnyException {
|
|
356
|
+
let name = match argument {
|
|
357
|
+
None => "nil".to_string(),
|
|
358
|
+
Some(argument) if argument.is_nil() => "nil".to_string(),
|
|
359
|
+
Some(argument) if argument.value().is_true() => "true".to_string(),
|
|
360
|
+
Some(argument) if argument.value().is_false() => "false".to_string(),
|
|
361
|
+
Some(argument) => argument.protect_send("class", &[]).ok().
|
|
362
|
+
and_then(|class| crate::ruby::inspect(&class).ok()).unwrap_or_default(),
|
|
363
|
+
};
|
|
364
|
+
error(&Class::type_error(), &format!("no implicit conversion of {} into String", name))
|
|
359
365
|
}
|
|
360
|
-
|
|
361
|
-
// pub fn pn_rmtree(pth: MaybeString) -> NilClass {
|
|
362
|
-
// NilClass::new()
|
|
363
|
-
// }
|
|
364
|
-
//
|