path_absolutize/
lib.rs

1/*!
2# Path Absolutize
3
4This is a library for extending `Path` and `PathBuf` in order to get an absolute path and remove the containing dots.
5
6The difference between `absolutize` and `canonicalize` methods is that `absolutize` does not care about whether the file exists and what the file really is.
7
8Please read the following examples to know the parsing rules.
9
10## Examples
11
12There are two methods you can use.
13
14### absolutize
15
16Get an absolute path.
17
18The dots in a path will be parsed even if it is already an absolute path (which means the path starts with a `MAIN_SEPARATOR` on Unix-like systems).
19
20```rust
21use std::path::Path;
22
23use path_absolutize::*;
24
25let p = Path::new("/path/to/123/456");
26
27# if cfg!(unix) {
28# #[cfg(feature = "unsafe_cache")]
29# {
30#     unsafe {
31#         update_cwd();
32#     }
33# }
34assert_eq!("/path/to/123/456", p.absolutize().unwrap().to_str().unwrap());
35# }
36```
37
38```rust
39use std::path::Path;
40
41use path_absolutize::*;
42
43let p = Path::new("/path/to/./123/../456");
44
45# if cfg!(unix) {
46# #[cfg(feature = "unsafe_cache")]
47# {
48#     unsafe {
49#         update_cwd();
50#     }
51# }
52assert_eq!("/path/to/456", p.absolutize().unwrap().to_str().unwrap());
53# }
54```
55
56If a path starts with a single dot, the dot means your program's **current working directory** (CWD).
57
58```rust
59use std::path::Path;
60use std::env;
61
62use path_absolutize::*;
63
64let p = Path::new("./path/to/123/456");
65
66# if cfg!(unix) {
67# #[cfg(feature = "unsafe_cache")]
68# {
69#     unsafe {
70#         update_cwd();
71#     }
72# }
73assert_eq!(Path::join(env::current_dir().unwrap().as_path(), Path::new("path/to/123/456")).to_str().unwrap(), p.absolutize().unwrap().to_str().unwrap());
74# }
75```
76
77If a path starts with a pair of dots, the dots means the parent of the CWD. If the CWD is **root**, the parent is still **root**.
78
79```rust
80use std::path::Path;
81use std::env;
82
83use path_absolutize::*;
84
85let p = Path::new("../path/to/123/456");
86
87let cwd = env::current_dir().unwrap();
88
89let cwd_parent = cwd.parent();
90
91# if cfg!(unix) {
92# #[cfg(feature = "unsafe_cache")]
93# {
94#     unsafe {
95#         update_cwd();
96#     }
97# }
98match cwd_parent {
99   Some(cwd_parent) => {
100       assert_eq!(Path::join(&cwd_parent, Path::new("path/to/123/456")).to_str().unwrap(), p.absolutize().unwrap().to_str().unwrap());
101   }
102   None => {
103       assert_eq!(Path::join(Path::new("/"), Path::new("path/to/123/456")).to_str().unwrap(), p.absolutize().unwrap().to_str().unwrap());
104   }
105}
106# }
107```
108
109A path which does not start with a `MAIN_SEPARATOR`, **Single Dot** and **Double Dots**, will act like having a single dot at the start when `absolutize` method is used.
110
111```rust
112use std::path::Path;
113use std::env;
114
115use path_absolutize::*;
116
117let p = Path::new("path/to/123/456");
118
119# if cfg!(unix) {
120# #[cfg(feature = "unsafe_cache")]
121# {
122#     unsafe {
123#         update_cwd();
124#     }
125# }
126assert_eq!(Path::join(env::current_dir().unwrap().as_path(), Path::new("path/to/123/456")).to_str().unwrap(), p.absolutize().unwrap().to_str().unwrap());
127# }
128```
129
130```rust
131use std::path::Path;
132use std::env;
133
134use path_absolutize::*;
135
136let p = Path::new("path/../../to/123/456");
137
138let cwd = env::current_dir().unwrap();
139
140let cwd_parent = cwd.parent();
141
142# if cfg!(unix) {
143# #[cfg(feature = "unsafe_cache")]
144# {
145#     unsafe {
146#         update_cwd();
147#     }
148# }
149match cwd_parent {
150   Some(cwd_parent) => {
151       assert_eq!(Path::join(&cwd_parent, Path::new("to/123/456")).to_str().unwrap(), p.absolutize().unwrap().to_str().unwrap());
152   }
153   None => {
154       assert_eq!(Path::join(Path::new("/"), Path::new("to/123/456")).to_str().unwrap(), p.absolutize().unwrap().to_str().unwrap());
155   }
156}
157# }
158```
159
160### Starting from a given current working directory
161
162With the `absolutize_from` function, you can provide the current working directory that the relative paths should be resolved from.
163
164```rust
165use std::env;
166use std::path::Path;
167
168use path_absolutize::*;
169
170let p = Path::new("../path/to/123/456");
171let cwd = env::current_dir().unwrap();
172
173println!("{}", p.absolutize_from(cwd).unwrap().to_str().unwrap());
174```
175
176### absolutize_virtually
177
178Get an absolute path **only under a specific directory**.
179
180The dots in a path will be parsed even if it is already an absolute path (which means the path starts with a `MAIN_SEPARATOR` on Unix-like systems).
181
182```rust
183use std::path::Path;
184
185use path_absolutize::*;
186
187let p = Path::new("/path/to/123/456");
188
189# if cfg!(unix) {
190# #[cfg(feature = "unsafe_cache")]
191# {
192#     unsafe {
193#         update_cwd();
194#     }
195# }
196assert_eq!("/path/to/123/456", p.absolutize_virtually("/").unwrap().to_str().unwrap());
197# }
198```
199
200```rust
201use std::path::Path;
202
203use path_absolutize::*;
204
205let p = Path::new("/path/to/./123/../456");
206
207# if cfg!(unix) {
208# #[cfg(feature = "unsafe_cache")]
209# {
210#     unsafe {
211#         update_cwd();
212#     }
213# }
214assert_eq!("/path/to/456", p.absolutize_virtually("/").unwrap().to_str().unwrap());
215# }
216```
217
218Every absolute path should under the virtual root.
219
220```rust
221use std::path::Path;
222
223use std::io::ErrorKind;
224
225use path_absolutize::*;
226
227let p = Path::new("/path/to/123/456");
228
229# if cfg!(unix) {
230# #[cfg(feature = "unsafe_cache")]
231# {
232#     unsafe {
233#         update_cwd();
234#     }
235# }
236assert_eq!(ErrorKind::InvalidInput, p.absolutize_virtually("/virtual/root").unwrap_err().kind());
237# }
238```
239
240Every relative path should under the virtual root.
241
242```rust
243use std::path::Path;
244
245use std::io::ErrorKind;
246
247use path_absolutize::*;
248
249let p = Path::new("./path/to/123/456");
250
251# if cfg!(unix) {
252# #[cfg(feature = "unsafe_cache")]
253# {
254#     unsafe {
255#         update_cwd();
256#     }
257# }
258assert_eq!(ErrorKind::InvalidInput, p.absolutize_virtually("/virtual/root").unwrap_err().kind());
259# }
260```
261
262```rust
263use std::path::Path;
264
265use std::io::ErrorKind;
266
267use path_absolutize::*;
268
269let p = Path::new("../path/to/123/456");
270
271# if cfg!(unix) {
272# #[cfg(feature = "unsafe_cache")]
273# {
274#     unsafe {
275#         update_cwd();
276#     }
277# }
278assert_eq!(ErrorKind::InvalidInput, p.absolutize_virtually("/virtual/root").unwrap_err().kind());
279# }
280```
281
282A path which does not start with a `MAIN_SEPARATOR`, **Single Dot** and **Double Dots**, will be located in the virtual root after the `absolutize_virtually` method is used.
283
284```rust
285use std::path::Path;
286
287use path_absolutize::*;
288
289let p = Path::new("path/to/123/456");
290
291# if cfg!(unix) {
292# #[cfg(feature = "unsafe_cache")]
293# {
294#     unsafe {
295#         update_cwd();
296#     }
297# }
298assert_eq!("/virtual/root/path/to/123/456", p.absolutize_virtually("/virtual/root").unwrap().to_str().unwrap());
299# }
300```
301
302```rust
303use std::path::Path;
304
305use path_absolutize::*;
306
307let p = Path::new("path/to/../../../../123/456");
308
309# if cfg!(unix) {
310# #[cfg(feature = "unsafe_cache")]
311# {
312#     unsafe {
313#         update_cwd();
314#     }
315# }
316assert_eq!("/virtual/root/123/456", p.absolutize_virtually("/virtual/root").unwrap().to_str().unwrap());
317# }
318```
319
320## Caching
321
322By default, the `absolutize` method and the `absolutize_virtually` method create a new `PathBuf` instance of the CWD every time in their operation. The overhead is obvious. Although it allows us to safely change the CWD at runtime by the program itself (e.g. using the `std::env::set_current_dir` function) or outside controls (e.g. using gdb to call `chdir`), we don't need that in most cases.
323
324In order to parse paths with better performance, this crate provides three ways to cache the CWD.
325
326### once_cell_cache
327
328Enabling the `once_cell_cache` feature can let this crate use `once_cell` to cache the CWD. It's thread-safe and does not need to modify any code, but once the CWD is cached, it cannot be changed anymore at runtime.
329
330```toml
331[dependencies.path-absolutize]
332version = "*"
333features = ["once_cell_cache"]
334```
335
336### lazy_static_cache
337
338Enabling the `lazy_static_cache` feature can let this crate use `lazy_static` to cache the CWD. It's thread-safe and does not need to modify any code, but once the CWD is cached, it cannot be changed anymore at runtime.
339
340```toml
341[dependencies.path-absolutize]
342version = "*"
343features = ["lazy_static_cache"]
344```
345
346### unsafe_cache
347
348Enabling the `unsafe_cache` feature can let this crate use a mutable static variable to cache the CWD. It allows the program to change the CWD at runtime by the program itself, but it's not thread-safe.
349
350You need to use the `update_cwd` function to initialize the CWD first. The function should also be used to update the CWD after the CWD is changed.
351
352```toml
353[dependencies.path-absolutize]
354version = "*"
355features = ["unsafe_cache"]
356```
357
358```rust
359use std::path::Path;
360
361use path_absolutize::*;
362
363# #[cfg(feature = "unsafe_cache")]
364unsafe {
365    update_cwd();
366}
367
368let p = Path::new("./path/to/123/456");
369
370println!("{}", p.absolutize().unwrap().to_str().unwrap());
371
372std::env::set_current_dir("/").unwrap();
373
374# #[cfg(feature = "unsafe_cache")]
375unsafe {
376    update_cwd();
377}
378
379println!("{}", p.absolutize().unwrap().to_str().unwrap());
380```
381
382## Benchmark
383
384#### No-cache
385
386```bash
387cargo bench
388```
389
390#### once_cell_cache
391
392```bash
393cargo bench --features once_cell_cache
394```
395
396#### lazy_static_cache
397
398```bash
399cargo bench --features lazy_static_cache
400```
401
402#### unsafe_cache
403
404```bash
405cargo bench --features unsafe_cache
406```
407
408*/
409
410#[cfg(any(
411    all(feature = "lazy_static_cache", feature = "unsafe_cache"),
412    all(feature = "once_cell_cache", feature = "unsafe_cache"),
413    all(feature = "lazy_static_cache", feature = "once_cell_cache")
414))]
415compile_error!("You can only enable at most one caching mechanism for `path-absolutize`.");
416
417pub extern crate path_dedot;
418
419use std::{
420    borrow::Cow,
421    io,
422    path::{Path, PathBuf},
423};
424
425#[cfg(feature = "unsafe_cache")]
426pub use path_dedot::update_cwd;
427#[cfg(any(
428    feature = "once_cell_cache",
429    feature = "lazy_static_cache",
430    feature = "unsafe_cache"
431))]
432pub use path_dedot::CWD;
433
434mod absolutize;
435
436#[macro_use]
437mod macros;
438
439#[cfg(any(unix, all(target_family = "wasm", feature = "use_unix_paths_on_wasm")))]
440mod unix;
441
442#[cfg(windows)]
443mod windows;
444
445pub use absolutize::*;
446
447impl Absolutize for PathBuf {
448    #[inline]
449    fn absolutize(&self) -> io::Result<Cow<Path>> {
450        self.as_path().absolutize()
451    }
452
453    #[inline]
454    fn absolutize_from(&self, cwd: impl AsRef<Path>) -> io::Result<Cow<'_, Path>> {
455        self.as_path().absolutize_from(cwd)
456    }
457
458    #[inline]
459    fn absolutize_virtually(&self, virtual_root: impl AsRef<Path>) -> io::Result<Cow<Path>> {
460        self.as_path().absolutize_virtually(virtual_root)
461    }
462}