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