x_path/lib.rs
1//!
2//! > <span style="color:darkorange">**⚠️ WARNING**</span>
3//! >
4//! > This is work in progress and is not ready for use
5//!
6//!
7//! # Use cases
8//!
9//! ## Config files
10//!
11//! The paths below are valid on any platform. They will be cleaned
12//! and have environment variables resolved at load.
13//!
14//! ```toml
15//! dir1 = "~/mydir/${SOME_ENV}/../"
16//! dir2 = "c:\\anotherdir\\%ANOTHER_ENV%"
17//! ```
18//!
19//! ## Clear expectations
20//!
21//! Use one of the below to communicate what your function or API expects.
22//!
23//! | | Any | Dir | File |
24//! | --- | --- | --- | --- |
25//! | Any | [AnyPath] | [DirPath] | [FilePath] |
26//! | Rel | [RelPath] | [RelDirPath] | [RelFilePath] |
27//! | Abs | [AbsPath] | [AbsDirPath] | [AbsFilePath] |
28//!
29//! ```rust
30//! # use x_path::{RelFilePath, AbsDirPath};
31//! #
32//! fn mirror(file: RelFilePath, from: AbsDirPath, to: AbsDirPath) {}
33//! ```
34//!
35//! ## Testable
36//!
37//! ```rust
38//! # use x_path::AbsDirPath;
39//! #
40//! #[test]
41//! fn test() -> anyhow::Result<()> {
42//! // imagine that the path string is read from a conf.toml file:
43//! let dir = AbsDirPath::new(r"~/dir1//..\dir2");
44//!
45//! // when using the alternative debug specifier, if the path starts
46//! // with current working directory or user home, then they are replaced
47//! // with '.' or '~' respectively. The path separator used is always '/'.
48//! assert_eq!(format!("{:#?}", dir), "AbsDirPath(~/dir2)");
49//!
50//! // standard debug output uses the internal representation of the path
51//! // which uses the full path with platform specific path separators.
52//! // linux:
53//! assert_eq!(format!("{:?}", dir), "AbsDirPath(/home/me/code/dir2)");
54//! // windows:
55//! assert_eq!(format!("{:?}", dir), r"AbsDirPath(c:\Users\me\code\dir2)");
56//! }
57//! ```
58//!
59//! ## Cross-platform
60//!
61//! Both Windows-style and Unix-style paths can be used on all platforms. They
62//! are all resolved and converted into a unified format that is comparable.
63//!
64//! The typical file system restrictions are enforced when read.
65//! On Windows, the NTFS, VFAT and exFAT restrictions are applied which are
66//! much more stringent than the Unix ones. Enable the feature `strict` if
67//! you want the same restrictions applied when running on Unix.
68//!
69//! ## Convenient
70//!
71//! Access the paths as `&str`, all paths implement:
72//! - [Display](std::fmt::Display) for easy display.
73//! - `AsRef<Path>` for interoperability with all the [std::fs] operations.
74//! - Iterate through all the path segments as `&str`ings with `path.segments()`.
75//! - Many convenient functions: see the doc for each path type.
76//!
77//! # Design goals
78//!
79//! - Make rust's typical _"if it compiles it works"_ experience work for cross-platform path handling as well.
80//! - Make Paths comparable, i.e. they are resolved to a common format in memory, and converted to
81//! a platform-specific format when used.
82//! - Write config files using paths that work across platforms (as far as possible).
83//! - AnyPath for general use and specific ones when you need to assure that
84//! - Provide types distinguishing between Absolute or Relative and Directory or File:
85//! - FilePath, FileAbsPath, FileRelPath
86//! - DirPath, DirAbsPath, DirAbsPath
87//! - Support for the major operating systems and file systems:
88//! - Linux & Unix: most file systems.
89//! - macOS: HFS+, APFS.
90//! - Windows: exFAT, NTFS. With feature `strict` enabled.
91//! - Comparable paths (because they are resolved, see [Path Comparison](#path-comparison) below).
92//!
93//! Non-goals:
94//! - Maximum performance.
95//! - Crazy filenames. I.e. only UTF-8 filenames are supported.
96//!
97//! Other:
98//! - Displays resolved paths or use `.native_string()` or `format("{path:#}")` for outputting OS native string.
99//! - Error:
100//! - handling with [anyhow](https://crates.io/crates/anyhow) aims to produce comprehensive
101//! human-readable messages instead of machine-parsable ones.
102//! - the message always includes the path in question.
103//! - the message includes the current working directory for relative paths.
104//!
105//!
106//! # Limits
107//!
108//! The limits are verified when creating and manipulating a path. By default, on Unix-based platforms,
109//! only a few limits are applied. On Windows, there are automatically more restrictions.
110//!
111//! If you want to ensure that the paths work seamlessly (as far as possible)
112//! on all platforms (i.e. paths authored on Linux work on Windows) then turn on the `strict`
113//! Cargo feature.
114//!
115//! ## Characters
116//!
117//! Reserved characters:
118//! - Slash (`/` and `\`): are used as path separators on all platforms.
119//! - `$` and `%`: when at the start of a path or immediately after a slash it will be
120//! interpreted as an environment variable see section [Environment variables](#environment-variables)
121//! - `.` and `~` when at the start of a path followed by either a slash or nothing are
122//! interpreted as the current working dir and user home dir respectively.
123//!
124//! Always forbidden:
125//! - Non UTF-8 characters (i.e. don't use [OsStr](std::ffi::OsStr) or [OsString](std::ffi::OsString))
126//! - NULL, `:`
127//!
128//! Forbidden in `strict` mode or when running on Windows:
129//! - Ascii control characters: 0x00-0x1F, 0x7F
130//! - `"`, `*`, `/`, `<`, `>`, `?`, `\`, `|`
131//! - Filenames: CON, PRN, AUX, NUL, COM0 - COM9 and LPT0 - LPT9. Also any of these filenames
132//! followed by an extension (ex: .txt).
133//!
134//! ## Path separators (slash) and drives
135//!
136//! The path separators are kept in memory and displayed in a platform-native representation,
137//! i.e. using the platform where the binary is running. For Windows, it's `\` and for the others `/`.
138//!
139//! On Windows, any drive letters are kept lower-cased, and on the others, it is discarded.
140//!
141//! This means that a string written as either `c:\my\path` or `/my/path`
142//! is converted and stored in memory and displayed as:
143//! - Windows: `c:\my\path` when the current directory's drive letter is `c`
144//! - Others: `/my/path`
145//!
146//! ## Path components
147//!
148//! Path components are limited to a maximum of 255 characters.
149//!
150//! ## Filenames
151//!
152//! Forbidden in `strict` mode or when running on Windows: CON, PRN, AUX, NUL, COM0 - COM9 and LPT0 - LPT9.
153//! Also any of these filenames followed by an extension (ex: .txt).
154//!
155//! # Path resolution
156//!
157//! Path resolution is done without file-system access so that paths don't need to exist.
158//!
159//! | Path<sup>*</sup> | Becomes | When | Is | Comment
160//! | --- | --- | --- | --- | ---
161//! | `.`, `./` | nix: `/tmp`<br>win: `c:\tmp` | current_dir() | nix: `/tmp`<br>win: `c:\tmp` |
162//! | `~`, `~/` | nix: `/Users/tom`<br>win: `c:\Users\tom` | home_dir() | nix: `/Users/tom`<br>win: `c:\Users\tom` |
163//! | `/` | nix: `/`<br>win: `c:\` | -<br>current_dir() | - <br>win: `c:/somedir` | - <br> win: Same drive as the current dir
164//! | `c:/`, `C:/` | nix: `/`<br>win: `c:\` | | | nix: Drive letter removed<br>win: Drive letters always in lower case
165//! | `c:dir` | nix: `/tmp/dir`<br>win: `c:\tmp\dir` | current_dir() | nix: `/tmp`<br>win: `c:\tmp` |
166//! | `dir//dir` | nix: `dir/dir`<br>win: `dir\dir` | | | Multiple slashes are joined
167//! | `dir/./dir` | nix: `dir/dir`<br>win: `dir\dir` | | | Dots inside of a path are ignored
168//! | `dir/..` | | | | Empty path
169//! | `dir1/dir2/..` | `dir1` | | |
170//! | `${MYDIR}`,<br>`%MYDIR%` | `dir` | var("MYDIR") | `dir` | See [Environment variables](#environment-variables)
171//!
172//! Legend:
173//! - <sup>*</sup> - Any `/` can also be `\`.
174//! - nix - Unix-based platforms: Linux, Unix, macOS.
175//! - win - Windows
176//! - current_dir() - refers to rust's [std::env::current_dir()](https://doc.rust-lang.org/std/env/fn.current_dir.html)
177//! - var() - refers to rust's [std::env::var(key)](https://doc.rust-lang.org/std/env/fn.var.html)
178//! - home_dir() - refers to the [dirs_sys::home_dir()](https://docs.rs/dirs-sys/0.4.0/dirs_sys/fn.home_dir.html)
179//!
180//! # Environment variables
181//!
182//! There is restricted support for environment variables where only a path segment that
183//! in Unix style: starts with `${` and ends with `}` or in Windows style starts and ends with `%`
184//! is interpreted as an environment variable and expanded when read. The stricter-than-usual
185//! requirements reduce interference with normal paths.
186//!
187//! Interpreted as environment variables:
188//! - `/dir/${MYVAR}/`, `${MYVAR}`, `${MYVAR}/dir`, `/dir/${MYVAR}`
189//! - `/dir/%MYVAR%/`, `%MYVAR%`, `%MYVAR%/dir`, `/dir/%MYVAR%`
190//!
191//! Not interpreted as environment vars:
192//! - `$MYVAR` - missing curly braces
193//! - `hi${MYVAR}`, `${MYVAR}hi`, `hi%MYVAR%`, `%MYVAR%hi` - any character before or after that is not a slash.
194//! - `${MYVAR`, `%MYVAR` - not closed.
195//! - `${MY-VAR}`, `%MY-VAR%`: use of character not permitted in environment variables.
196//!
197//! Returns an error:
198//! - `${}`, `\${}`, `\${}\` - empty keys are invalid
199//! - `%MYVAR` when the environment variable MYVAR is not defined.
200//!
201//! # Path comparison
202//!
203//! While paths preserve casing when kept in memory comparing is done in a case-insensitive manner.
204//!
205//! # References
206//!
207//! - [File path formats on Windows systems](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats)
208//! - [Naming Files, Paths, and Namespaces](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file)
209//! - [Wikipedia: Filenames - Comparison of filename limitations](https://en.wikipedia.org/wiki/Filename#Comparison_of_filename_limitations)
210
211mod env;
212mod ext;
213mod inner;
214mod iter;
215mod path;
216
217const SEP: char = std::path::MAIN_SEPARATOR;
218const SLASH: [char; 2] = ['/', '\\'];
219
220pub use path::*;
221
222#[cfg(test)]
223#[test]
224fn update_readme() {
225 markdown_includes::update("src/readme.tpl.md", "README.md").unwrap();
226}