##// END OF EJS Templates
rust: re-format with nightly rustfmt...
Martin von Zweigbergk -
r44652:73209802 default
parent child Browse files
Show More
@@ -1,646 +1,648 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
141
142 #[cfg(windows)]
142 #[cfg(windows)]
143 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
143 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
144 ///
144 ///
145 /// Split a pathname into drive/UNC sharepoint and relative path specifiers.
145 /// Split a pathname into drive/UNC sharepoint and relative path
146 /// Returns a 2-tuple (drive_or_unc, path); either part may be empty.
146 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
147 /// be empty.
147 ///
148 ///
148 /// If you assign
149 /// If you assign
149 /// result = split_drive(p)
150 /// result = split_drive(p)
150 /// It is always true that:
151 /// It is always true that:
151 /// result[0] + result[1] == p
152 /// result[0] + result[1] == p
152 ///
153 ///
153 /// If the path contained a drive letter, drive_or_unc will contain everything
154 /// If the path contained a drive letter, drive_or_unc will contain
154 /// up to and including the colon.
155 /// everything up to and including the colon.
155 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
156 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
156 ///
157 ///
157 /// If the path contained a UNC path, the drive_or_unc will contain the host
158 /// If the path contained a UNC path, the drive_or_unc will contain the
158 /// name and share up to but not including the fourth directory separator
159 /// host name and share up to but not including the fourth directory
159 /// character.
160 /// separator character.
160 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer", "/dir")
161 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
162 /// "/dir")
161 ///
163 ///
162 /// Paths cannot contain both a drive letter and a UNC path.
164 /// Paths cannot contain both a drive letter and a UNC path.
163 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
165 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
164 let bytes = self.as_bytes();
166 let bytes = self.as_bytes();
165 let is_sep = |b| std::path::is_separator(b as char);
167 let is_sep = |b| std::path::is_separator(b as char);
166
168
167 if self.len() < 2 {
169 if self.len() < 2 {
168 (HgPath::new(b""), &self)
170 (HgPath::new(b""), &self)
169 } else if is_sep(bytes[0])
171 } else if is_sep(bytes[0])
170 && is_sep(bytes[1])
172 && is_sep(bytes[1])
171 && (self.len() == 2 || !is_sep(bytes[2]))
173 && (self.len() == 2 || !is_sep(bytes[2]))
172 {
174 {
173 // Is a UNC path:
175 // Is a UNC path:
174 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
176 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
175 // \\machine\mountpoint\directory\etc\...
177 // \\machine\mountpoint\directory\etc\...
176 // directory ^^^^^^^^^^^^^^^
178 // directory ^^^^^^^^^^^^^^^
177
179
178 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
180 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
179 let mountpoint_start_index = if let Some(i) = machine_end_index {
181 let mountpoint_start_index = if let Some(i) = machine_end_index {
180 i + 2
182 i + 2
181 } else {
183 } else {
182 return (HgPath::new(b""), &self);
184 return (HgPath::new(b""), &self);
183 };
185 };
184
186
185 match bytes[mountpoint_start_index + 1..]
187 match bytes[mountpoint_start_index + 1..]
186 .iter()
188 .iter()
187 .position(|b| is_sep(*b))
189 .position(|b| is_sep(*b))
188 {
190 {
189 // A UNC path can't have two slashes in a row
191 // A UNC path can't have two slashes in a row
190 // (after the initial two)
192 // (after the initial two)
191 Some(0) => (HgPath::new(b""), &self),
193 Some(0) => (HgPath::new(b""), &self),
192 Some(i) => {
194 Some(i) => {
193 let (a, b) =
195 let (a, b) =
194 bytes.split_at(mountpoint_start_index + 1 + i);
196 bytes.split_at(mountpoint_start_index + 1 + i);
195 (HgPath::new(a), HgPath::new(b))
197 (HgPath::new(a), HgPath::new(b))
196 }
198 }
197 None => (&self, HgPath::new(b"")),
199 None => (&self, HgPath::new(b"")),
198 }
200 }
199 } else if bytes[1] == b':' {
201 } else if bytes[1] == b':' {
200 // Drive path c:\directory
202 // Drive path c:\directory
201 let (a, b) = bytes.split_at(2);
203 let (a, b) = bytes.split_at(2);
202 (HgPath::new(a), HgPath::new(b))
204 (HgPath::new(a), HgPath::new(b))
203 } else {
205 } else {
204 (HgPath::new(b""), &self)
206 (HgPath::new(b""), &self)
205 }
207 }
206 }
208 }
207
209
208 #[cfg(unix)]
210 #[cfg(unix)]
209 /// Split a pathname into drive and path. On Posix, drive is always empty.
211 /// Split a pathname into drive and path. On Posix, drive is always empty.
210 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
212 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
211 (HgPath::new(b""), &self)
213 (HgPath::new(b""), &self)
212 }
214 }
213
215
214 /// Checks for errors in the path, short-circuiting at the first one.
216 /// Checks for errors in the path, short-circuiting at the first one.
215 /// This generates fine-grained errors useful for debugging.
217 /// This generates fine-grained errors useful for debugging.
216 /// To simply check if the path is valid during tests, use `is_valid`.
218 /// To simply check if the path is valid during tests, use `is_valid`.
217 pub fn check_state(&self) -> Result<(), HgPathError> {
219 pub fn check_state(&self) -> Result<(), HgPathError> {
218 if self.len() == 0 {
220 if self.len() == 0 {
219 return Ok(());
221 return Ok(());
220 }
222 }
221 let bytes = self.as_bytes();
223 let bytes = self.as_bytes();
222 let mut previous_byte = None;
224 let mut previous_byte = None;
223
225
224 if bytes[0] == b'/' {
226 if bytes[0] == b'/' {
225 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
227 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
226 }
228 }
227 for (index, byte) in bytes.iter().enumerate() {
229 for (index, byte) in bytes.iter().enumerate() {
228 match byte {
230 match byte {
229 0 => {
231 0 => {
230 return Err(HgPathError::ContainsNullByte(
232 return Err(HgPathError::ContainsNullByte(
231 bytes.to_vec(),
233 bytes.to_vec(),
232 index,
234 index,
233 ))
235 ))
234 }
236 }
235 b'/' => {
237 b'/' => {
236 if previous_byte.is_some() && previous_byte == Some(b'/') {
238 if previous_byte.is_some() && previous_byte == Some(b'/') {
237 return Err(HgPathError::ConsecutiveSlashes(
239 return Err(HgPathError::ConsecutiveSlashes(
238 bytes.to_vec(),
240 bytes.to_vec(),
239 index,
241 index,
240 ));
242 ));
241 }
243 }
242 }
244 }
243 _ => (),
245 _ => (),
244 };
246 };
245 previous_byte = Some(*byte);
247 previous_byte = Some(*byte);
246 }
248 }
247 Ok(())
249 Ok(())
248 }
250 }
249
251
250 #[cfg(test)]
252 #[cfg(test)]
251 /// Only usable during tests to force developers to handle invalid states
253 /// Only usable during tests to force developers to handle invalid states
252 fn is_valid(&self) -> bool {
254 fn is_valid(&self) -> bool {
253 self.check_state().is_ok()
255 self.check_state().is_ok()
254 }
256 }
255 }
257 }
256
258
257 impl fmt::Debug for HgPath {
259 impl fmt::Debug for HgPath {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
261 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
260 }
262 }
261 }
263 }
262
264
263 impl fmt::Display for HgPath {
265 impl fmt::Display for HgPath {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 write!(f, "{}", String::from_utf8_lossy(&self.inner))
267 write!(f, "{}", String::from_utf8_lossy(&self.inner))
266 }
268 }
267 }
269 }
268
270
269 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
271 #[derive(Eq, Ord, Clone, PartialEq, PartialOrd, Hash)]
270 pub struct HgPathBuf {
272 pub struct HgPathBuf {
271 inner: Vec<u8>,
273 inner: Vec<u8>,
272 }
274 }
273
275
274 impl HgPathBuf {
276 impl HgPathBuf {
275 pub fn new() -> Self {
277 pub fn new() -> Self {
276 Self { inner: Vec::new() }
278 Self { inner: Vec::new() }
277 }
279 }
278 pub fn push(&mut self, byte: u8) {
280 pub fn push(&mut self, byte: u8) {
279 self.inner.push(byte);
281 self.inner.push(byte);
280 }
282 }
281 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
283 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
282 HgPath::new(s).to_owned()
284 HgPath::new(s).to_owned()
283 }
285 }
284 pub fn into_vec(self) -> Vec<u8> {
286 pub fn into_vec(self) -> Vec<u8> {
285 self.inner
287 self.inner
286 }
288 }
287 pub fn as_ref(&self) -> &[u8] {
289 pub fn as_ref(&self) -> &[u8] {
288 self.inner.as_ref()
290 self.inner.as_ref()
289 }
291 }
290 }
292 }
291
293
292 impl fmt::Debug for HgPathBuf {
294 impl fmt::Debug for HgPathBuf {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
296 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
295 }
297 }
296 }
298 }
297
299
298 impl fmt::Display for HgPathBuf {
300 impl fmt::Display for HgPathBuf {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 write!(f, "{}", String::from_utf8_lossy(&self.inner))
302 write!(f, "{}", String::from_utf8_lossy(&self.inner))
301 }
303 }
302 }
304 }
303
305
304 impl Deref for HgPathBuf {
306 impl Deref for HgPathBuf {
305 type Target = HgPath;
307 type Target = HgPath;
306
308
307 #[inline]
309 #[inline]
308 fn deref(&self) -> &HgPath {
310 fn deref(&self) -> &HgPath {
309 &HgPath::new(&self.inner)
311 &HgPath::new(&self.inner)
310 }
312 }
311 }
313 }
312
314
313 impl From<Vec<u8>> for HgPathBuf {
315 impl From<Vec<u8>> for HgPathBuf {
314 fn from(vec: Vec<u8>) -> Self {
316 fn from(vec: Vec<u8>) -> Self {
315 Self { inner: vec }
317 Self { inner: vec }
316 }
318 }
317 }
319 }
318
320
319 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
321 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
320 fn from(s: &T) -> HgPathBuf {
322 fn from(s: &T) -> HgPathBuf {
321 s.as_ref().to_owned()
323 s.as_ref().to_owned()
322 }
324 }
323 }
325 }
324
326
325 impl Into<Vec<u8>> for HgPathBuf {
327 impl Into<Vec<u8>> for HgPathBuf {
326 fn into(self) -> Vec<u8> {
328 fn into(self) -> Vec<u8> {
327 self.inner
329 self.inner
328 }
330 }
329 }
331 }
330
332
331 impl Borrow<HgPath> for HgPathBuf {
333 impl Borrow<HgPath> for HgPathBuf {
332 fn borrow(&self) -> &HgPath {
334 fn borrow(&self) -> &HgPath {
333 &HgPath::new(self.as_bytes())
335 &HgPath::new(self.as_bytes())
334 }
336 }
335 }
337 }
336
338
337 impl ToOwned for HgPath {
339 impl ToOwned for HgPath {
338 type Owned = HgPathBuf;
340 type Owned = HgPathBuf;
339
341
340 fn to_owned(&self) -> HgPathBuf {
342 fn to_owned(&self) -> HgPathBuf {
341 self.to_hg_path_buf()
343 self.to_hg_path_buf()
342 }
344 }
343 }
345 }
344
346
345 impl AsRef<HgPath> for HgPath {
347 impl AsRef<HgPath> for HgPath {
346 fn as_ref(&self) -> &HgPath {
348 fn as_ref(&self) -> &HgPath {
347 self
349 self
348 }
350 }
349 }
351 }
350
352
351 impl AsRef<HgPath> for HgPathBuf {
353 impl AsRef<HgPath> for HgPathBuf {
352 fn as_ref(&self) -> &HgPath {
354 fn as_ref(&self) -> &HgPath {
353 self
355 self
354 }
356 }
355 }
357 }
356
358
357 impl Extend<u8> for HgPathBuf {
359 impl Extend<u8> for HgPathBuf {
358 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
360 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
359 self.inner.extend(iter);
361 self.inner.extend(iter);
360 }
362 }
361 }
363 }
362
364
363 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
365 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
364 /// implemented, these conversion utils will have to work differently depending
366 /// implemented, these conversion utils will have to work differently depending
365 /// on the repository encoding: either `UTF-8` or `MBCS`.
367 /// on the repository encoding: either `UTF-8` or `MBCS`.
366
368
367 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
369 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
368 hg_path: P,
370 hg_path: P,
369 ) -> Result<OsString, HgPathError> {
371 ) -> Result<OsString, HgPathError> {
370 hg_path.as_ref().check_state()?;
372 hg_path.as_ref().check_state()?;
371 let os_str;
373 let os_str;
372 #[cfg(unix)]
374 #[cfg(unix)]
373 {
375 {
374 use std::os::unix::ffi::OsStrExt;
376 use std::os::unix::ffi::OsStrExt;
375 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
377 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
376 }
378 }
377 // TODO Handle other platforms
379 // TODO Handle other platforms
378 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
380 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
379 Ok(os_str.to_os_string())
381 Ok(os_str.to_os_string())
380 }
382 }
381
383
382 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
384 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
383 hg_path: P,
385 hg_path: P,
384 ) -> Result<PathBuf, HgPathError> {
386 ) -> Result<PathBuf, HgPathError> {
385 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
387 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
386 }
388 }
387
389
388 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
390 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
389 os_string: S,
391 os_string: S,
390 ) -> Result<HgPathBuf, HgPathError> {
392 ) -> Result<HgPathBuf, HgPathError> {
391 let buf;
393 let buf;
392 #[cfg(unix)]
394 #[cfg(unix)]
393 {
395 {
394 use std::os::unix::ffi::OsStrExt;
396 use std::os::unix::ffi::OsStrExt;
395 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
397 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
396 }
398 }
397 // TODO Handle other platforms
399 // TODO Handle other platforms
398 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
400 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
399
401
400 buf.check_state()?;
402 buf.check_state()?;
401 Ok(buf)
403 Ok(buf)
402 }
404 }
403
405
404 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
406 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
405 path: P,
407 path: P,
406 ) -> Result<HgPathBuf, HgPathError> {
408 ) -> Result<HgPathBuf, HgPathError> {
407 let buf;
409 let buf;
408 let os_str = path.as_ref().as_os_str();
410 let os_str = path.as_ref().as_os_str();
409 #[cfg(unix)]
411 #[cfg(unix)]
410 {
412 {
411 use std::os::unix::ffi::OsStrExt;
413 use std::os::unix::ffi::OsStrExt;
412 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
414 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
413 }
415 }
414 // TODO Handle other platforms
416 // TODO Handle other platforms
415 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
417 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
416
418
417 buf.check_state()?;
419 buf.check_state()?;
418 Ok(buf)
420 Ok(buf)
419 }
421 }
420
422
421 #[cfg(test)]
423 #[cfg(test)]
422 mod tests {
424 mod tests {
423 use super::*;
425 use super::*;
424
426
425 #[test]
427 #[test]
426 fn test_path_states() {
428 fn test_path_states() {
427 assert_eq!(
429 assert_eq!(
428 Err(HgPathError::LeadingSlash(b"/".to_vec())),
430 Err(HgPathError::LeadingSlash(b"/".to_vec())),
429 HgPath::new(b"/").check_state()
431 HgPath::new(b"/").check_state()
430 );
432 );
431 assert_eq!(
433 assert_eq!(
432 Err(HgPathError::ConsecutiveSlashes(b"a/b//c".to_vec(), 4)),
434 Err(HgPathError::ConsecutiveSlashes(b"a/b//c".to_vec(), 4)),
433 HgPath::new(b"a/b//c").check_state()
435 HgPath::new(b"a/b//c").check_state()
434 );
436 );
435 assert_eq!(
437 assert_eq!(
436 Err(HgPathError::ContainsNullByte(b"a/b/\0c".to_vec(), 4)),
438 Err(HgPathError::ContainsNullByte(b"a/b/\0c".to_vec(), 4)),
437 HgPath::new(b"a/b/\0c").check_state()
439 HgPath::new(b"a/b/\0c").check_state()
438 );
440 );
439 // TODO test HgPathError::DecodeError for the Windows implementation.
441 // TODO test HgPathError::DecodeError for the Windows implementation.
440 assert_eq!(true, HgPath::new(b"").is_valid());
442 assert_eq!(true, HgPath::new(b"").is_valid());
441 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
443 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
442 // Backslashes in paths are not significant, but allowed
444 // Backslashes in paths are not significant, but allowed
443 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
445 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
444 // Dots in paths are not significant, but allowed
446 // Dots in paths are not significant, but allowed
445 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
447 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
446 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
448 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
447 }
449 }
448
450
449 #[test]
451 #[test]
450 fn test_iter() {
452 fn test_iter() {
451 let path = HgPath::new(b"a");
453 let path = HgPath::new(b"a");
452 let mut iter = path.bytes();
454 let mut iter = path.bytes();
453 assert_eq!(Some(&b'a'), iter.next());
455 assert_eq!(Some(&b'a'), iter.next());
454 assert_eq!(None, iter.next_back());
456 assert_eq!(None, iter.next_back());
455 assert_eq!(None, iter.next());
457 assert_eq!(None, iter.next());
456
458
457 let path = HgPath::new(b"a");
459 let path = HgPath::new(b"a");
458 let mut iter = path.bytes();
460 let mut iter = path.bytes();
459 assert_eq!(Some(&b'a'), iter.next_back());
461 assert_eq!(Some(&b'a'), iter.next_back());
460 assert_eq!(None, iter.next_back());
462 assert_eq!(None, iter.next_back());
461 assert_eq!(None, iter.next());
463 assert_eq!(None, iter.next());
462
464
463 let path = HgPath::new(b"abc");
465 let path = HgPath::new(b"abc");
464 let mut iter = path.bytes();
466 let mut iter = path.bytes();
465 assert_eq!(Some(&b'a'), iter.next());
467 assert_eq!(Some(&b'a'), iter.next());
466 assert_eq!(Some(&b'c'), iter.next_back());
468 assert_eq!(Some(&b'c'), iter.next_back());
467 assert_eq!(Some(&b'b'), iter.next_back());
469 assert_eq!(Some(&b'b'), iter.next_back());
468 assert_eq!(None, iter.next_back());
470 assert_eq!(None, iter.next_back());
469 assert_eq!(None, iter.next());
471 assert_eq!(None, iter.next());
470
472
471 let path = HgPath::new(b"abc");
473 let path = HgPath::new(b"abc");
472 let mut iter = path.bytes();
474 let mut iter = path.bytes();
473 assert_eq!(Some(&b'a'), iter.next());
475 assert_eq!(Some(&b'a'), iter.next());
474 assert_eq!(Some(&b'b'), iter.next());
476 assert_eq!(Some(&b'b'), iter.next());
475 assert_eq!(Some(&b'c'), iter.next());
477 assert_eq!(Some(&b'c'), iter.next());
476 assert_eq!(None, iter.next_back());
478 assert_eq!(None, iter.next_back());
477 assert_eq!(None, iter.next());
479 assert_eq!(None, iter.next());
478
480
479 let path = HgPath::new(b"abc");
481 let path = HgPath::new(b"abc");
480 let iter = path.bytes();
482 let iter = path.bytes();
481 let mut vec = Vec::new();
483 let mut vec = Vec::new();
482 vec.extend(iter);
484 vec.extend(iter);
483 assert_eq!(vec![b'a', b'b', b'c'], vec);
485 assert_eq!(vec![b'a', b'b', b'c'], vec);
484
486
485 let path = HgPath::new(b"abc");
487 let path = HgPath::new(b"abc");
486 let mut iter = path.bytes();
488 let mut iter = path.bytes();
487 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
489 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
488
490
489 let path = HgPath::new(b"abc");
491 let path = HgPath::new(b"abc");
490 let mut iter = path.bytes();
492 let mut iter = path.bytes();
491 assert_eq!(None, iter.rposition(|c| *c == b'd'));
493 assert_eq!(None, iter.rposition(|c| *c == b'd'));
492 }
494 }
493
495
494 #[test]
496 #[test]
495 fn test_join() {
497 fn test_join() {
496 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
498 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
497 assert_eq!(b"a/b", path.as_bytes());
499 assert_eq!(b"a/b", path.as_bytes());
498
500
499 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
501 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
500 assert_eq!(b"a/b/c", path.as_bytes());
502 assert_eq!(b"a/b/c", path.as_bytes());
501
503
502 // No leading slash if empty before join
504 // No leading slash if empty before join
503 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
505 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
504 assert_eq!(b"b/c", path.as_bytes());
506 assert_eq!(b"b/c", path.as_bytes());
505
507
506 // The leading slash is an invalid representation of an `HgPath`, but
508 // The leading slash is an invalid representation of an `HgPath`, but
507 // it can happen. This creates another invalid representation of
509 // it can happen. This creates another invalid representation of
508 // consecutive bytes.
510 // consecutive bytes.
509 // TODO What should be done in this case? Should we silently remove
511 // TODO What should be done in this case? Should we silently remove
510 // the extra slash? Should we change the signature to a problematic
512 // the extra slash? Should we change the signature to a problematic
511 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
513 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
512 // let the error happen upon filesystem interaction?
514 // let the error happen upon filesystem interaction?
513 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"));
514 assert_eq!(b"a//b", path.as_bytes());
516 assert_eq!(b"a//b", path.as_bytes());
515 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
517 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
516 assert_eq!(b"a//b", path.as_bytes());
518 assert_eq!(b"a//b", path.as_bytes());
517 }
519 }
518
520
519 #[test]
521 #[test]
520 fn test_relative_to() {
522 fn test_relative_to() {
521 let path = HgPath::new(b"");
523 let path = HgPath::new(b"");
522 let base = HgPath::new(b"");
524 let base = HgPath::new(b"");
523 assert_eq!(Some(path), path.relative_to(base));
525 assert_eq!(Some(path), path.relative_to(base));
524
526
525 let path = HgPath::new(b"path");
527 let path = HgPath::new(b"path");
526 let base = HgPath::new(b"");
528 let base = HgPath::new(b"");
527 assert_eq!(Some(path), path.relative_to(base));
529 assert_eq!(Some(path), path.relative_to(base));
528
530
529 let path = HgPath::new(b"a");
531 let path = HgPath::new(b"a");
530 let base = HgPath::new(b"b");
532 let base = HgPath::new(b"b");
531 assert_eq!(None, path.relative_to(base));
533 assert_eq!(None, path.relative_to(base));
532
534
533 let path = HgPath::new(b"a/b");
535 let path = HgPath::new(b"a/b");
534 let base = HgPath::new(b"a");
536 let base = HgPath::new(b"a");
535 assert_eq!(None, path.relative_to(base));
537 assert_eq!(None, path.relative_to(base));
536
538
537 let path = HgPath::new(b"a/b");
539 let path = HgPath::new(b"a/b");
538 let base = HgPath::new(b"a/");
540 let base = HgPath::new(b"a/");
539 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
541 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
540
542
541 let path = HgPath::new(b"nested/path/to/b");
543 let path = HgPath::new(b"nested/path/to/b");
542 let base = HgPath::new(b"nested/path/");
544 let base = HgPath::new(b"nested/path/");
543 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
545 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
544
546
545 let path = HgPath::new(b"ends/with/dir/");
547 let path = HgPath::new(b"ends/with/dir/");
546 let base = HgPath::new(b"ends/");
548 let base = HgPath::new(b"ends/");
547 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
549 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
548 }
550 }
549
551
550 #[test]
552 #[test]
551 #[cfg(unix)]
553 #[cfg(unix)]
552 fn test_split_drive() {
554 fn test_split_drive() {
553 // Taken from the Python stdlib's tests
555 // Taken from the Python stdlib's tests
554 assert_eq!(
556 assert_eq!(
555 HgPath::new(br"/foo/bar").split_drive(),
557 HgPath::new(br"/foo/bar").split_drive(),
556 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
558 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
557 );
559 );
558 assert_eq!(
560 assert_eq!(
559 HgPath::new(br"foo:bar").split_drive(),
561 HgPath::new(br"foo:bar").split_drive(),
560 (HgPath::new(b""), HgPath::new(br"foo:bar"))
562 (HgPath::new(b""), HgPath::new(br"foo:bar"))
561 );
563 );
562 assert_eq!(
564 assert_eq!(
563 HgPath::new(br":foo:bar").split_drive(),
565 HgPath::new(br":foo:bar").split_drive(),
564 (HgPath::new(b""), HgPath::new(br":foo:bar"))
566 (HgPath::new(b""), HgPath::new(br":foo:bar"))
565 );
567 );
566 // Also try NT paths; should not split them
568 // Also try NT paths; should not split them
567 assert_eq!(
569 assert_eq!(
568 HgPath::new(br"c:\foo\bar").split_drive(),
570 HgPath::new(br"c:\foo\bar").split_drive(),
569 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
571 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
570 );
572 );
571 assert_eq!(
573 assert_eq!(
572 HgPath::new(b"c:/foo/bar").split_drive(),
574 HgPath::new(b"c:/foo/bar").split_drive(),
573 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
575 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
574 );
576 );
575 assert_eq!(
577 assert_eq!(
576 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
578 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
577 (
579 (
578 HgPath::new(b""),
580 HgPath::new(b""),
579 HgPath::new(br"\\conky\mountpoint\foo\bar")
581 HgPath::new(br"\\conky\mountpoint\foo\bar")
580 )
582 )
581 );
583 );
582 }
584 }
583
585
584 #[test]
586 #[test]
585 #[cfg(windows)]
587 #[cfg(windows)]
586 fn test_split_drive() {
588 fn test_split_drive() {
587 assert_eq!(
589 assert_eq!(
588 HgPath::new(br"c:\foo\bar").split_drive(),
590 HgPath::new(br"c:\foo\bar").split_drive(),
589 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
591 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
590 );
592 );
591 assert_eq!(
593 assert_eq!(
592 HgPath::new(b"c:/foo/bar").split_drive(),
594 HgPath::new(b"c:/foo/bar").split_drive(),
593 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
595 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
594 );
596 );
595 assert_eq!(
597 assert_eq!(
596 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
598 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
597 (
599 (
598 HgPath::new(br"\\conky\mountpoint"),
600 HgPath::new(br"\\conky\mountpoint"),
599 HgPath::new(br"\foo\bar")
601 HgPath::new(br"\foo\bar")
600 )
602 )
601 );
603 );
602 assert_eq!(
604 assert_eq!(
603 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
605 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
604 (
606 (
605 HgPath::new(br"//conky/mountpoint"),
607 HgPath::new(br"//conky/mountpoint"),
606 HgPath::new(br"/foo/bar")
608 HgPath::new(br"/foo/bar")
607 )
609 )
608 );
610 );
609 assert_eq!(
611 assert_eq!(
610 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
612 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
611 (
613 (
612 HgPath::new(br""),
614 HgPath::new(br""),
613 HgPath::new(br"\\\conky\mountpoint\foo\bar")
615 HgPath::new(br"\\\conky\mountpoint\foo\bar")
614 )
616 )
615 );
617 );
616 assert_eq!(
618 assert_eq!(
617 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
619 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
618 (
620 (
619 HgPath::new(br""),
621 HgPath::new(br""),
620 HgPath::new(br"///conky/mountpoint/foo/bar")
622 HgPath::new(br"///conky/mountpoint/foo/bar")
621 )
623 )
622 );
624 );
623 assert_eq!(
625 assert_eq!(
624 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
626 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
625 (
627 (
626 HgPath::new(br""),
628 HgPath::new(br""),
627 HgPath::new(br"\\conky\\mountpoint\foo\bar")
629 HgPath::new(br"\\conky\\mountpoint\foo\bar")
628 )
630 )
629 );
631 );
630 assert_eq!(
632 assert_eq!(
631 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
633 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
632 (
634 (
633 HgPath::new(br""),
635 HgPath::new(br""),
634 HgPath::new(br"//conky//mountpoint/foo/bar")
636 HgPath::new(br"//conky//mountpoint/foo/bar")
635 )
637 )
636 );
638 );
637 // UNC part containing U+0130
639 // UNC part containing U+0130
638 assert_eq!(
640 assert_eq!(
639 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
641 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
640 (
642 (
641 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
643 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
642 HgPath::new(br"/foo/bar")
644 HgPath::new(br"/foo/bar")
643 )
645 )
644 );
646 );
645 }
647 }
646 }
648 }
General Comments 0
You need to be logged in to leave comments. Login now