##// END OF EJS Templates
rust-utils: add Rust implementation of Python's "os.path.splitdrive"...
Raphaël Gomès -
r44588:baa4e7fd default
parent child Browse files
Show More
@@ -1,476 +1,646 b''
1 // hg_path.rs
1 // hg_path.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 //
4 //
5 // This software may be used and distributed according to the terms of the
5 // This software may be used and distributed according to the terms of the
6 // GNU General Public License version 2 or any later version.
6 // GNU General Public License version 2 or any later version.
7
7
8 use std::borrow::Borrow;
8 use std::borrow::Borrow;
9 use std::ffi::{OsStr, OsString};
9 use std::ffi::{OsStr, OsString};
10 use std::fmt;
10 use std::fmt;
11 use std::ops::Deref;
11 use std::ops::Deref;
12 use std::path::{Path, PathBuf};
12 use std::path::{Path, PathBuf};
13
13
14 #[derive(Debug, Eq, PartialEq)]
14 #[derive(Debug, Eq, PartialEq)]
15 pub enum HgPathError {
15 pub enum HgPathError {
16 /// Bytes from the invalid `HgPath`
16 /// Bytes from the invalid `HgPath`
17 LeadingSlash(Vec<u8>),
17 LeadingSlash(Vec<u8>),
18 /// Bytes and index of the second slash
18 /// Bytes and index of the second slash
19 ConsecutiveSlashes(Vec<u8>, usize),
19 ConsecutiveSlashes(Vec<u8>, usize),
20 /// Bytes and index of the null byte
20 /// Bytes and index of the null byte
21 ContainsNullByte(Vec<u8>, usize),
21 ContainsNullByte(Vec<u8>, usize),
22 /// Bytes
22 /// Bytes
23 DecodeError(Vec<u8>),
23 DecodeError(Vec<u8>),
24 }
24 }
25
25
26 impl ToString for HgPathError {
26 impl ToString for HgPathError {
27 fn to_string(&self) -> String {
27 fn to_string(&self) -> String {
28 match self {
28 match self {
29 HgPathError::LeadingSlash(bytes) => {
29 HgPathError::LeadingSlash(bytes) => {
30 format!("Invalid HgPath '{:?}': has a leading slash.", bytes)
30 format!("Invalid HgPath '{:?}': has a leading slash.", bytes)
31 }
31 }
32 HgPathError::ConsecutiveSlashes(bytes, pos) => format!(
32 HgPathError::ConsecutiveSlashes(bytes, pos) => format!(
33 "Invalid HgPath '{:?}': consecutive slahes at pos {}.",
33 "Invalid HgPath '{:?}': consecutive slahes at pos {}.",
34 bytes, pos
34 bytes, pos
35 ),
35 ),
36 HgPathError::ContainsNullByte(bytes, pos) => format!(
36 HgPathError::ContainsNullByte(bytes, pos) => format!(
37 "Invalid HgPath '{:?}': contains null byte at pos {}.",
37 "Invalid HgPath '{:?}': contains null byte at pos {}.",
38 bytes, pos
38 bytes, pos
39 ),
39 ),
40 HgPathError::DecodeError(bytes) => {
40 HgPathError::DecodeError(bytes) => {
41 format!("Invalid HgPath '{:?}': could not be decoded.", bytes)
41 format!("Invalid HgPath '{:?}': could not be decoded.", bytes)
42 }
42 }
43 }
43 }
44 }
44 }
45 }
45 }
46
46
47 impl From<HgPathError> for std::io::Error {
47 impl From<HgPathError> for std::io::Error {
48 fn from(e: HgPathError) -> Self {
48 fn from(e: HgPathError) -> Self {
49 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
49 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
50 }
50 }
51 }
51 }
52
52
53 /// This is a repository-relative path (or canonical path):
53 /// This is a repository-relative path (or canonical path):
54 /// - no null characters
54 /// - no null characters
55 /// - `/` separates directories
55 /// - `/` separates directories
56 /// - no consecutive slashes
56 /// - no consecutive slashes
57 /// - no leading slash,
57 /// - no leading slash,
58 /// - no `.` nor `..` of special meaning
58 /// - no `.` nor `..` of special meaning
59 /// - stored in repository and shared across platforms
59 /// - stored in repository and shared across platforms
60 ///
60 ///
61 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
61 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
62 /// in its lifetime for performance reasons and to ease ergonomics. It is
62 /// in its lifetime for performance reasons and to ease ergonomics. It is
63 /// however checked using the `check_state` method before any file-system
63 /// however checked using the `check_state` method before any file-system
64 /// operation.
64 /// operation.
65 ///
65 ///
66 /// This allows us to be encoding-transparent as much as possible, until really
66 /// This allows us to be encoding-transparent as much as possible, until really
67 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
67 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
68 /// or `Path`) whenever more complex operations are needed:
68 /// or `Path`) whenever more complex operations are needed:
69 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
69 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
70 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
70 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
71 /// character encoding will be determined on a per-repository basis.
71 /// character encoding will be determined on a per-repository basis.
72 //
72 //
73 // FIXME: (adapted from a comment in the stdlib)
73 // FIXME: (adapted from a comment in the stdlib)
74 // `HgPath::new()` current implementation relies on `Slice` being
74 // `HgPath::new()` current implementation relies on `Slice` being
75 // layout-compatible with `[u8]`.
75 // layout-compatible with `[u8]`.
76 // When attribute privacy is implemented, `Slice` should be annotated as
76 // When attribute privacy is implemented, `Slice` should be annotated as
77 // `#[repr(transparent)]`.
77 // `#[repr(transparent)]`.
78 // Anyway, `Slice` representation and layout are considered implementation
78 // Anyway, `Slice` representation and layout are considered implementation
79 // detail, are not documented and must not be relied upon.
79 // detail, are not documented and must not be relied upon.
80 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
80 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
81 pub struct HgPath {
81 pub struct HgPath {
82 inner: [u8],
82 inner: [u8],
83 }
83 }
84
84
85 impl HgPath {
85 impl HgPath {
86 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
86 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
87 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
87 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
88 }
88 }
89 pub fn is_empty(&self) -> bool {
89 pub fn is_empty(&self) -> bool {
90 self.inner.is_empty()
90 self.inner.is_empty()
91 }
91 }
92 pub fn len(&self) -> usize {
92 pub fn len(&self) -> usize {
93 self.inner.len()
93 self.inner.len()
94 }
94 }
95 fn to_hg_path_buf(&self) -> HgPathBuf {
95 fn to_hg_path_buf(&self) -> HgPathBuf {
96 HgPathBuf {
96 HgPathBuf {
97 inner: self.inner.to_owned(),
97 inner: self.inner.to_owned(),
98 }
98 }
99 }
99 }
100 pub fn bytes(&self) -> std::slice::Iter<u8> {
100 pub fn bytes(&self) -> std::slice::Iter<u8> {
101 self.inner.iter()
101 self.inner.iter()
102 }
102 }
103 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
103 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
104 HgPathBuf::from(self.inner.to_ascii_uppercase())
104 HgPathBuf::from(self.inner.to_ascii_uppercase())
105 }
105 }
106 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
106 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
107 HgPathBuf::from(self.inner.to_ascii_lowercase())
107 HgPathBuf::from(self.inner.to_ascii_lowercase())
108 }
108 }
109 pub fn as_bytes(&self) -> &[u8] {
109 pub fn as_bytes(&self) -> &[u8] {
110 &self.inner
110 &self.inner
111 }
111 }
112 pub fn contains(&self, other: u8) -> bool {
112 pub fn contains(&self, other: u8) -> bool {
113 self.inner.contains(&other)
113 self.inner.contains(&other)
114 }
114 }
115 pub fn starts_with(&self, needle: impl AsRef<HgPath>) -> bool {
115 pub fn starts_with(&self, needle: impl AsRef<HgPath>) -> bool {
116 self.inner.starts_with(needle.as_ref().as_bytes())
116 self.inner.starts_with(needle.as_ref().as_bytes())
117 }
117 }
118 pub fn join<T: ?Sized + AsRef<HgPath>>(&self, other: &T) -> HgPathBuf {
118 pub fn join<T: ?Sized + AsRef<HgPath>>(&self, other: &T) -> HgPathBuf {
119 let mut inner = self.inner.to_owned();
119 let mut inner = self.inner.to_owned();
120 if inner.len() != 0 && inner.last() != Some(&b'/') {
120 if inner.len() != 0 && inner.last() != Some(&b'/') {
121 inner.push(b'/');
121 inner.push(b'/');
122 }
122 }
123 inner.extend(other.as_ref().bytes());
123 inner.extend(other.as_ref().bytes());
124 HgPathBuf::from_bytes(&inner)
124 HgPathBuf::from_bytes(&inner)
125 }
125 }
126 /// Given a base directory, returns the slice of `self` relative to the
126 /// Given a base directory, returns the slice of `self` relative to the
127 /// base directory. If `base` is not a directory (does not end with a
127 /// base directory. If `base` is not a directory (does not end with a
128 /// `b'/'`), returns `None`.
128 /// `b'/'`), returns `None`.
129 pub fn relative_to(&self, base: impl AsRef<HgPath>) -> Option<&HgPath> {
129 pub fn relative_to(&self, base: impl AsRef<HgPath>) -> Option<&HgPath> {
130 let base = base.as_ref();
130 let base = base.as_ref();
131 if base.is_empty() {
131 if base.is_empty() {
132 return Some(self);
132 return Some(self);
133 }
133 }
134 let is_dir = base.as_bytes().ends_with(b"/");
134 let is_dir = base.as_bytes().ends_with(b"/");
135 if is_dir && self.starts_with(base) {
135 if is_dir && self.starts_with(base) {
136 Some(HgPath::new(&self.inner[base.len()..]))
136 Some(HgPath::new(&self.inner[base.len()..]))
137 } else {
137 } else {
138 None
138 None
139 }
139 }
140 }
140 }
141
142 #[cfg(windows)]
143 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
144 ///
145 /// Split a pathname into drive/UNC sharepoint and relative path specifiers.
146 /// Returns a 2-tuple (drive_or_unc, path); either part may be empty.
147 ///
148 /// If you assign
149 /// result = split_drive(p)
150 /// It is always true that:
151 /// result[0] + result[1] == p
152 ///
153 /// If the path contained a drive letter, drive_or_unc will contain everything
154 /// up to and including the colon.
155 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
156 ///
157 /// If the path contained a UNC path, the drive_or_unc will contain the host
158 /// name and share up to but not including the fourth directory separator
159 /// character.
160 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer", "/dir")
161 ///
162 /// Paths cannot contain both a drive letter and a UNC path.
163 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
164 let bytes = self.as_bytes();
165 let is_sep = |b| std::path::is_separator(b as char);
166
167 if self.len() < 2 {
168 (HgPath::new(b""), &self)
169 } else if is_sep(bytes[0])
170 && is_sep(bytes[1])
171 && (self.len() == 2 || !is_sep(bytes[2]))
172 {
173 // Is a UNC path:
174 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
175 // \\machine\mountpoint\directory\etc\...
176 // directory ^^^^^^^^^^^^^^^
177
178 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
179 let mountpoint_start_index = if let Some(i) = machine_end_index {
180 i + 2
181 } else {
182 return (HgPath::new(b""), &self);
183 };
184
185 match bytes[mountpoint_start_index + 1..]
186 .iter()
187 .position(|b| is_sep(*b))
188 {
189 // A UNC path can't have two slashes in a row
190 // (after the initial two)
191 Some(0) => (HgPath::new(b""), &self),
192 Some(i) => {
193 let (a, b) =
194 bytes.split_at(mountpoint_start_index + 1 + i);
195 (HgPath::new(a), HgPath::new(b))
196 }
197 None => (&self, HgPath::new(b"")),
198 }
199 } else if bytes[1] == b':' {
200 // Drive path c:\directory
201 let (a, b) = bytes.split_at(2);
202 (HgPath::new(a), HgPath::new(b))
203 } else {
204 (HgPath::new(b""), &self)
205 }
206 }
207
208 #[cfg(unix)]
209 /// Split a pathname into drive and path. On Posix, drive is always empty.
210 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
211 (HgPath::new(b""), &self)
212 }
213
141 /// Checks for errors in the path, short-circuiting at the first one.
214 /// Checks for errors in the path, short-circuiting at the first one.
142 /// This generates fine-grained errors useful for debugging.
215 /// This generates fine-grained errors useful for debugging.
143 /// To simply check if the path is valid during tests, use `is_valid`.
216 /// To simply check if the path is valid during tests, use `is_valid`.
144 pub fn check_state(&self) -> Result<(), HgPathError> {
217 pub fn check_state(&self) -> Result<(), HgPathError> {
145 if self.len() == 0 {
218 if self.len() == 0 {
146 return Ok(());
219 return Ok(());
147 }
220 }
148 let bytes = self.as_bytes();
221 let bytes = self.as_bytes();
149 let mut previous_byte = None;
222 let mut previous_byte = None;
150
223
151 if bytes[0] == b'/' {
224 if bytes[0] == b'/' {
152 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
225 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
153 }
226 }
154 for (index, byte) in bytes.iter().enumerate() {
227 for (index, byte) in bytes.iter().enumerate() {
155 match byte {
228 match byte {
156 0 => {
229 0 => {
157 return Err(HgPathError::ContainsNullByte(
230 return Err(HgPathError::ContainsNullByte(
158 bytes.to_vec(),
231 bytes.to_vec(),
159 index,
232 index,
160 ))
233 ))
161 }
234 }
162 b'/' => {
235 b'/' => {
163 if previous_byte.is_some() && previous_byte == Some(b'/') {
236 if previous_byte.is_some() && previous_byte == Some(b'/') {
164 return Err(HgPathError::ConsecutiveSlashes(
237 return Err(HgPathError::ConsecutiveSlashes(
165 bytes.to_vec(),
238 bytes.to_vec(),
166 index,
239 index,
167 ));
240 ));
168 }
241 }
169 }
242 }
170 _ => (),
243 _ => (),
171 };
244 };
172 previous_byte = Some(*byte);
245 previous_byte = Some(*byte);
173 }
246 }
174 Ok(())
247 Ok(())
175 }
248 }
176
249
177 #[cfg(test)]
250 #[cfg(test)]
178 /// Only usable during tests to force developers to handle invalid states
251 /// Only usable during tests to force developers to handle invalid states
179 fn is_valid(&self) -> bool {
252 fn is_valid(&self) -> bool {
180 self.check_state().is_ok()
253 self.check_state().is_ok()
181 }
254 }
182 }
255 }
183
256
184 impl fmt::Debug for HgPath {
257 impl fmt::Debug for HgPath {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
259 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
187 }
260 }
188 }
261 }
189
262
190 impl fmt::Display for HgPath {
263 impl fmt::Display for HgPath {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 write!(f, "{}", String::from_utf8_lossy(&self.inner))
265 write!(f, "{}", String::from_utf8_lossy(&self.inner))
193 }
266 }
194 }
267 }
195
268
196 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
269 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
197 pub struct HgPathBuf {
270 pub struct HgPathBuf {
198 inner: Vec<u8>,
271 inner: Vec<u8>,
199 }
272 }
200
273
201 impl HgPathBuf {
274 impl HgPathBuf {
202 pub fn new() -> Self {
275 pub fn new() -> Self {
203 Self { inner: Vec::new() }
276 Self { inner: Vec::new() }
204 }
277 }
205 pub fn push(&mut self, byte: u8) {
278 pub fn push(&mut self, byte: u8) {
206 self.inner.push(byte);
279 self.inner.push(byte);
207 }
280 }
208 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
281 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
209 HgPath::new(s).to_owned()
282 HgPath::new(s).to_owned()
210 }
283 }
211 pub fn into_vec(self) -> Vec<u8> {
284 pub fn into_vec(self) -> Vec<u8> {
212 self.inner
285 self.inner
213 }
286 }
214 pub fn as_ref(&self) -> &[u8] {
287 pub fn as_ref(&self) -> &[u8] {
215 self.inner.as_ref()
288 self.inner.as_ref()
216 }
289 }
217 }
290 }
218
291
219 impl fmt::Debug for HgPathBuf {
292 impl fmt::Debug for HgPathBuf {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
294 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
222 }
295 }
223 }
296 }
224
297
225 impl fmt::Display for HgPathBuf {
298 impl fmt::Display for HgPathBuf {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 write!(f, "{}", String::from_utf8_lossy(&self.inner))
300 write!(f, "{}", String::from_utf8_lossy(&self.inner))
228 }
301 }
229 }
302 }
230
303
231 impl Deref for HgPathBuf {
304 impl Deref for HgPathBuf {
232 type Target = HgPath;
305 type Target = HgPath;
233
306
234 #[inline]
307 #[inline]
235 fn deref(&self) -> &HgPath {
308 fn deref(&self) -> &HgPath {
236 &HgPath::new(&self.inner)
309 &HgPath::new(&self.inner)
237 }
310 }
238 }
311 }
239
312
240 impl From<Vec<u8>> for HgPathBuf {
313 impl From<Vec<u8>> for HgPathBuf {
241 fn from(vec: Vec<u8>) -> Self {
314 fn from(vec: Vec<u8>) -> Self {
242 Self { inner: vec }
315 Self { inner: vec }
243 }
316 }
244 }
317 }
245
318
246 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
319 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
247 fn from(s: &T) -> HgPathBuf {
320 fn from(s: &T) -> HgPathBuf {
248 s.as_ref().to_owned()
321 s.as_ref().to_owned()
249 }
322 }
250 }
323 }
251
324
252 impl Into<Vec<u8>> for HgPathBuf {
325 impl Into<Vec<u8>> for HgPathBuf {
253 fn into(self) -> Vec<u8> {
326 fn into(self) -> Vec<u8> {
254 self.inner
327 self.inner
255 }
328 }
256 }
329 }
257
330
258 impl Borrow<HgPath> for HgPathBuf {
331 impl Borrow<HgPath> for HgPathBuf {
259 fn borrow(&self) -> &HgPath {
332 fn borrow(&self) -> &HgPath {
260 &HgPath::new(self.as_bytes())
333 &HgPath::new(self.as_bytes())
261 }
334 }
262 }
335 }
263
336
264 impl ToOwned for HgPath {
337 impl ToOwned for HgPath {
265 type Owned = HgPathBuf;
338 type Owned = HgPathBuf;
266
339
267 fn to_owned(&self) -> HgPathBuf {
340 fn to_owned(&self) -> HgPathBuf {
268 self.to_hg_path_buf()
341 self.to_hg_path_buf()
269 }
342 }
270 }
343 }
271
344
272 impl AsRef<HgPath> for HgPath {
345 impl AsRef<HgPath> for HgPath {
273 fn as_ref(&self) -> &HgPath {
346 fn as_ref(&self) -> &HgPath {
274 self
347 self
275 }
348 }
276 }
349 }
277
350
278 impl AsRef<HgPath> for HgPathBuf {
351 impl AsRef<HgPath> for HgPathBuf {
279 fn as_ref(&self) -> &HgPath {
352 fn as_ref(&self) -> &HgPath {
280 self
353 self
281 }
354 }
282 }
355 }
283
356
284 impl Extend<u8> for HgPathBuf {
357 impl Extend<u8> for HgPathBuf {
285 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
358 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
286 self.inner.extend(iter);
359 self.inner.extend(iter);
287 }
360 }
288 }
361 }
289
362
290 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
363 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
291 /// implemented, these conversion utils will have to work differently depending
364 /// implemented, these conversion utils will have to work differently depending
292 /// on the repository encoding: either `UTF-8` or `MBCS`.
365 /// on the repository encoding: either `UTF-8` or `MBCS`.
293
366
294 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
367 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
295 hg_path: P,
368 hg_path: P,
296 ) -> Result<OsString, HgPathError> {
369 ) -> Result<OsString, HgPathError> {
297 hg_path.as_ref().check_state()?;
370 hg_path.as_ref().check_state()?;
298 let os_str;
371 let os_str;
299 #[cfg(unix)]
372 #[cfg(unix)]
300 {
373 {
301 use std::os::unix::ffi::OsStrExt;
374 use std::os::unix::ffi::OsStrExt;
302 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
375 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
303 }
376 }
304 // TODO Handle other platforms
377 // TODO Handle other platforms
305 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
378 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
306 Ok(os_str.to_os_string())
379 Ok(os_str.to_os_string())
307 }
380 }
308
381
309 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
382 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
310 hg_path: P,
383 hg_path: P,
311 ) -> Result<PathBuf, HgPathError> {
384 ) -> Result<PathBuf, HgPathError> {
312 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
385 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
313 }
386 }
314
387
315 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
388 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
316 os_string: S,
389 os_string: S,
317 ) -> Result<HgPathBuf, HgPathError> {
390 ) -> Result<HgPathBuf, HgPathError> {
318 let buf;
391 let buf;
319 #[cfg(unix)]
392 #[cfg(unix)]
320 {
393 {
321 use std::os::unix::ffi::OsStrExt;
394 use std::os::unix::ffi::OsStrExt;
322 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
395 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
323 }
396 }
324 // TODO Handle other platforms
397 // TODO Handle other platforms
325 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
398 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
326
399
327 buf.check_state()?;
400 buf.check_state()?;
328 Ok(buf)
401 Ok(buf)
329 }
402 }
330
403
331 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
404 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
332 path: P,
405 path: P,
333 ) -> Result<HgPathBuf, HgPathError> {
406 ) -> Result<HgPathBuf, HgPathError> {
334 let buf;
407 let buf;
335 let os_str = path.as_ref().as_os_str();
408 let os_str = path.as_ref().as_os_str();
336 #[cfg(unix)]
409 #[cfg(unix)]
337 {
410 {
338 use std::os::unix::ffi::OsStrExt;
411 use std::os::unix::ffi::OsStrExt;
339 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
412 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
340 }
413 }
341 // TODO Handle other platforms
414 // TODO Handle other platforms
342 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
415 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
343
416
344 buf.check_state()?;
417 buf.check_state()?;
345 Ok(buf)
418 Ok(buf)
346 }
419 }
347
420
348 #[cfg(test)]
421 #[cfg(test)]
349 mod tests {
422 mod tests {
350 use super::*;
423 use super::*;
351
424
352 #[test]
425 #[test]
353 fn test_path_states() {
426 fn test_path_states() {
354 assert_eq!(
427 assert_eq!(
355 Err(HgPathError::LeadingSlash(b"/".to_vec())),
428 Err(HgPathError::LeadingSlash(b"/".to_vec())),
356 HgPath::new(b"/").check_state()
429 HgPath::new(b"/").check_state()
357 );
430 );
358 assert_eq!(
431 assert_eq!(
359 Err(HgPathError::ConsecutiveSlashes(b"a/b//c".to_vec(), 4)),
432 Err(HgPathError::ConsecutiveSlashes(b"a/b//c".to_vec(), 4)),
360 HgPath::new(b"a/b//c").check_state()
433 HgPath::new(b"a/b//c").check_state()
361 );
434 );
362 assert_eq!(
435 assert_eq!(
363 Err(HgPathError::ContainsNullByte(b"a/b/\0c".to_vec(), 4)),
436 Err(HgPathError::ContainsNullByte(b"a/b/\0c".to_vec(), 4)),
364 HgPath::new(b"a/b/\0c").check_state()
437 HgPath::new(b"a/b/\0c").check_state()
365 );
438 );
366 // TODO test HgPathError::DecodeError for the Windows implementation.
439 // TODO test HgPathError::DecodeError for the Windows implementation.
367 assert_eq!(true, HgPath::new(b"").is_valid());
440 assert_eq!(true, HgPath::new(b"").is_valid());
368 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
441 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
369 // Backslashes in paths are not significant, but allowed
442 // Backslashes in paths are not significant, but allowed
370 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
443 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
371 // Dots in paths are not significant, but allowed
444 // Dots in paths are not significant, but allowed
372 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
445 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
373 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
446 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
374 }
447 }
375
448
376 #[test]
449 #[test]
377 fn test_iter() {
450 fn test_iter() {
378 let path = HgPath::new(b"a");
451 let path = HgPath::new(b"a");
379 let mut iter = path.bytes();
452 let mut iter = path.bytes();
380 assert_eq!(Some(&b'a'), iter.next());
453 assert_eq!(Some(&b'a'), iter.next());
381 assert_eq!(None, iter.next_back());
454 assert_eq!(None, iter.next_back());
382 assert_eq!(None, iter.next());
455 assert_eq!(None, iter.next());
383
456
384 let path = HgPath::new(b"a");
457 let path = HgPath::new(b"a");
385 let mut iter = path.bytes();
458 let mut iter = path.bytes();
386 assert_eq!(Some(&b'a'), iter.next_back());
459 assert_eq!(Some(&b'a'), iter.next_back());
387 assert_eq!(None, iter.next_back());
460 assert_eq!(None, iter.next_back());
388 assert_eq!(None, iter.next());
461 assert_eq!(None, iter.next());
389
462
390 let path = HgPath::new(b"abc");
463 let path = HgPath::new(b"abc");
391 let mut iter = path.bytes();
464 let mut iter = path.bytes();
392 assert_eq!(Some(&b'a'), iter.next());
465 assert_eq!(Some(&b'a'), iter.next());
393 assert_eq!(Some(&b'c'), iter.next_back());
466 assert_eq!(Some(&b'c'), iter.next_back());
394 assert_eq!(Some(&b'b'), iter.next_back());
467 assert_eq!(Some(&b'b'), iter.next_back());
395 assert_eq!(None, iter.next_back());
468 assert_eq!(None, iter.next_back());
396 assert_eq!(None, iter.next());
469 assert_eq!(None, iter.next());
397
470
398 let path = HgPath::new(b"abc");
471 let path = HgPath::new(b"abc");
399 let mut iter = path.bytes();
472 let mut iter = path.bytes();
400 assert_eq!(Some(&b'a'), iter.next());
473 assert_eq!(Some(&b'a'), iter.next());
401 assert_eq!(Some(&b'b'), iter.next());
474 assert_eq!(Some(&b'b'), iter.next());
402 assert_eq!(Some(&b'c'), iter.next());
475 assert_eq!(Some(&b'c'), iter.next());
403 assert_eq!(None, iter.next_back());
476 assert_eq!(None, iter.next_back());
404 assert_eq!(None, iter.next());
477 assert_eq!(None, iter.next());
405
478
406 let path = HgPath::new(b"abc");
479 let path = HgPath::new(b"abc");
407 let iter = path.bytes();
480 let iter = path.bytes();
408 let mut vec = Vec::new();
481 let mut vec = Vec::new();
409 vec.extend(iter);
482 vec.extend(iter);
410 assert_eq!(vec![b'a', b'b', b'c'], vec);
483 assert_eq!(vec![b'a', b'b', b'c'], vec);
411
484
412 let path = HgPath::new(b"abc");
485 let path = HgPath::new(b"abc");
413 let mut iter = path.bytes();
486 let mut iter = path.bytes();
414 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
487 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
415
488
416 let path = HgPath::new(b"abc");
489 let path = HgPath::new(b"abc");
417 let mut iter = path.bytes();
490 let mut iter = path.bytes();
418 assert_eq!(None, iter.rposition(|c| *c == b'd'));
491 assert_eq!(None, iter.rposition(|c| *c == b'd'));
419 }
492 }
420
493
421 #[test]
494 #[test]
422 fn test_join() {
495 fn test_join() {
423 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
496 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
424 assert_eq!(b"a/b", path.as_bytes());
497 assert_eq!(b"a/b", path.as_bytes());
425
498
426 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
499 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
427 assert_eq!(b"a/b/c", path.as_bytes());
500 assert_eq!(b"a/b/c", path.as_bytes());
428
501
429 // No leading slash if empty before join
502 // No leading slash if empty before join
430 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
503 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
431 assert_eq!(b"b/c", path.as_bytes());
504 assert_eq!(b"b/c", path.as_bytes());
432
505
433 // The leading slash is an invalid representation of an `HgPath`, but
506 // The leading slash is an invalid representation of an `HgPath`, but
434 // it can happen. This creates another invalid representation of
507 // it can happen. This creates another invalid representation of
435 // consecutive bytes.
508 // consecutive bytes.
436 // TODO What should be done in this case? Should we silently remove
509 // TODO What should be done in this case? Should we silently remove
437 // the extra slash? Should we change the signature to a problematic
510 // the extra slash? Should we change the signature to a problematic
438 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
511 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
439 // let the error happen upon filesystem interaction?
512 // let the error happen upon filesystem interaction?
440 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
513 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
441 assert_eq!(b"a//b", path.as_bytes());
514 assert_eq!(b"a//b", path.as_bytes());
442 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
515 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
443 assert_eq!(b"a//b", path.as_bytes());
516 assert_eq!(b"a//b", path.as_bytes());
444 }
517 }
445
518
446 #[test]
519 #[test]
447 fn test_relative_to() {
520 fn test_relative_to() {
448 let path = HgPath::new(b"");
521 let path = HgPath::new(b"");
449 let base = HgPath::new(b"");
522 let base = HgPath::new(b"");
450 assert_eq!(Some(path), path.relative_to(base));
523 assert_eq!(Some(path), path.relative_to(base));
451
524
452 let path = HgPath::new(b"path");
525 let path = HgPath::new(b"path");
453 let base = HgPath::new(b"");
526 let base = HgPath::new(b"");
454 assert_eq!(Some(path), path.relative_to(base));
527 assert_eq!(Some(path), path.relative_to(base));
455
528
456 let path = HgPath::new(b"a");
529 let path = HgPath::new(b"a");
457 let base = HgPath::new(b"b");
530 let base = HgPath::new(b"b");
458 assert_eq!(None, path.relative_to(base));
531 assert_eq!(None, path.relative_to(base));
459
532
460 let path = HgPath::new(b"a/b");
533 let path = HgPath::new(b"a/b");
461 let base = HgPath::new(b"a");
534 let base = HgPath::new(b"a");
462 assert_eq!(None, path.relative_to(base));
535 assert_eq!(None, path.relative_to(base));
463
536
464 let path = HgPath::new(b"a/b");
537 let path = HgPath::new(b"a/b");
465 let base = HgPath::new(b"a/");
538 let base = HgPath::new(b"a/");
466 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
539 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
467
540
468 let path = HgPath::new(b"nested/path/to/b");
541 let path = HgPath::new(b"nested/path/to/b");
469 let base = HgPath::new(b"nested/path/");
542 let base = HgPath::new(b"nested/path/");
470 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
543 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
471
544
472 let path = HgPath::new(b"ends/with/dir/");
545 let path = HgPath::new(b"ends/with/dir/");
473 let base = HgPath::new(b"ends/");
546 let base = HgPath::new(b"ends/");
474 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
547 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
475 }
548 }
549
550 #[test]
551 #[cfg(unix)]
552 fn test_split_drive() {
553 // Taken from the Python stdlib's tests
554 assert_eq!(
555 HgPath::new(br"/foo/bar").split_drive(),
556 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
557 );
558 assert_eq!(
559 HgPath::new(br"foo:bar").split_drive(),
560 (HgPath::new(b""), HgPath::new(br"foo:bar"))
561 );
562 assert_eq!(
563 HgPath::new(br":foo:bar").split_drive(),
564 (HgPath::new(b""), HgPath::new(br":foo:bar"))
565 );
566 // Also try NT paths; should not split them
567 assert_eq!(
568 HgPath::new(br"c:\foo\bar").split_drive(),
569 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
570 );
571 assert_eq!(
572 HgPath::new(b"c:/foo/bar").split_drive(),
573 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
574 );
575 assert_eq!(
576 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
577 (
578 HgPath::new(b""),
579 HgPath::new(br"\\conky\mountpoint\foo\bar")
580 )
581 );
476 }
582 }
583
584 #[test]
585 #[cfg(windows)]
586 fn test_split_drive() {
587 assert_eq!(
588 HgPath::new(br"c:\foo\bar").split_drive(),
589 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
590 );
591 assert_eq!(
592 HgPath::new(b"c:/foo/bar").split_drive(),
593 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
594 );
595 assert_eq!(
596 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
597 (
598 HgPath::new(br"\\conky\mountpoint"),
599 HgPath::new(br"\foo\bar")
600 )
601 );
602 assert_eq!(
603 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
604 (
605 HgPath::new(br"//conky/mountpoint"),
606 HgPath::new(br"/foo/bar")
607 )
608 );
609 assert_eq!(
610 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
611 (
612 HgPath::new(br""),
613 HgPath::new(br"\\\conky\mountpoint\foo\bar")
614 )
615 );
616 assert_eq!(
617 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
618 (
619 HgPath::new(br""),
620 HgPath::new(br"///conky/mountpoint/foo/bar")
621 )
622 );
623 assert_eq!(
624 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
625 (
626 HgPath::new(br""),
627 HgPath::new(br"\\conky\\mountpoint\foo\bar")
628 )
629 );
630 assert_eq!(
631 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
632 (
633 HgPath::new(br""),
634 HgPath::new(br"//conky//mountpoint/foo/bar")
635 )
636 );
637 // UNC part containing U+0130
638 assert_eq!(
639 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
640 (
641 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
642 HgPath::new(br"/foo/bar")
643 )
644 );
645 }
646 }
General Comments 0
You need to be logged in to leave comments. Login now