mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Add typed path forms (#13115)
# Description This PR adds new types to `nu-path` to enforce path invariants. Namely, this PR adds: - `Path` and `PathBuf`. These types are different from, but analogous to `std::path::Path` and `std::path::PathBuf`. - `RelativePath` and `RelativePathBuf`. These types must be/contain strictly relative paths. - `AbsolutePath` and `AbsolutePathBuf`. These types must be/contain strictly absolute paths. - `CanonicalPath` and `CanonicalPathBuf`. These types must be/contain canonical paths. Operations are prohibited as necessary to ensure that the invariants of each type are upheld (needs double-checking). Only paths that are absolute (or canonical) can be easily used as / converted to `std::path::Path`s. This is to help force us to account for the emulated current working directory instead of accidentally using the current directory of the Nushell process (i.e., `std::env::current_dir`). Related to #12975 and #12976. Note that this PR uses several declarative macros, as the file / this PR would otherwise be 5000 lines long. # User-Facing Changes No major changes yet, just adds types to `nu-path` to be used in the future. # After Submitting Actually use the new path types in all our crates where it makes sense, removing usages of `std::path` types.
This commit is contained in:
parent
def36865ef
commit
5a486029db
4 changed files with 3260 additions and 0 deletions
161
crates/nu-path/src/form.rs
Normal file
161
crates/nu-path/src/form.rs
Normal file
|
@ -0,0 +1,161 @@
|
|||
use std::ffi::OsStr;
|
||||
|
||||
mod private {
|
||||
use std::ffi::OsStr;
|
||||
|
||||
// This trait should not be extended by external crates in order to uphold safety guarantees.
|
||||
// As such, this trait is put inside a private module to prevent external impls.
|
||||
// This ensures that all possible [`PathForm`]s can only be defined here and will:
|
||||
// - be zero sized (enforced anyways by the `repr(transparent)` on `Path`)
|
||||
// - have a no-op [`Drop`] implementation
|
||||
pub trait Sealed: 'static {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(path: &P) -> bool;
|
||||
}
|
||||
}
|
||||
|
||||
/// A marker trait for the different kinds of path forms.
|
||||
/// Each form has its own invariants that are guaranteed be upheld.
|
||||
/// The list of path forms are:
|
||||
/// - [`Any`]: a path with no invariants. It may be a relative or an absolute path.
|
||||
/// - [`Relative`]: a strictly relative path.
|
||||
/// - [`Absolute`]: a strictly absolute path.
|
||||
/// - [`Canonical`]: a path that must be in canonicalized form.
|
||||
pub trait PathForm: private::Sealed {}
|
||||
impl PathForm for Any {}
|
||||
impl PathForm for Relative {}
|
||||
impl PathForm for Absolute {}
|
||||
impl PathForm for Canonical {}
|
||||
|
||||
/// A path whose form is unknown. It could be a relative, absolute, or canonical path.
|
||||
///
|
||||
/// The path is not guaranteed to be normalized. It may contain unresolved symlinks,
|
||||
/// trailing slashes, dot components (`..` or `.`), and repeated path separators.
|
||||
pub struct Any;
|
||||
|
||||
impl private::Sealed for Any {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(_: &P) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A strictly relative path.
|
||||
///
|
||||
/// The path is not guaranteed to be normalized. It may contain unresolved symlinks,
|
||||
/// trailing slashes, dot components (`..` or `.`), and repeated path separators.
|
||||
pub struct Relative;
|
||||
|
||||
impl private::Sealed for Relative {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(path: &P) -> bool {
|
||||
std::path::Path::new(path).is_relative()
|
||||
}
|
||||
}
|
||||
|
||||
/// An absolute path.
|
||||
///
|
||||
/// The path is not guaranteed to be normalized. It may contain unresolved symlinks,
|
||||
/// trailing slashes, dot components (`..` or `.`), and repeated path separators.
|
||||
pub struct Absolute;
|
||||
|
||||
impl private::Sealed for Absolute {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(path: &P) -> bool {
|
||||
std::path::Path::new(path).is_absolute()
|
||||
}
|
||||
}
|
||||
|
||||
// A canonical path.
|
||||
//
|
||||
// An absolute path with all intermediate components normalized and symbolic links resolved.
|
||||
pub struct Canonical;
|
||||
|
||||
impl private::Sealed for Canonical {
|
||||
fn invariants_satisfied<P: AsRef<OsStr> + ?Sized>(_: &P) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that may be relative paths.
|
||||
/// This includes only the [`Any`] and [`Relative`] path forms.
|
||||
///
|
||||
/// [`push`](crate::PathBuf::push) and [`join`](crate::Path::join)
|
||||
/// operations only support [`MaybeRelative`] path forms as input.
|
||||
pub trait MaybeRelative: PathForm {}
|
||||
impl MaybeRelative for Any {}
|
||||
impl MaybeRelative for Relative {}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that may be absolute paths.
|
||||
/// This includes the [`Any`], [`Absolute`], and [`Canonical`] path forms.
|
||||
pub trait MaybeAbsolute: PathForm {}
|
||||
impl MaybeAbsolute for Any {}
|
||||
impl MaybeAbsolute for Absolute {}
|
||||
impl MaybeAbsolute for Canonical {}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that are absolute paths.
|
||||
/// This includes only the [`Absolute`] and [`Canonical`] path forms.
|
||||
///
|
||||
/// Only [`PathForm`]s that implement this trait can be easily converted to [`std::path::Path`]
|
||||
/// or [`std::path::PathBuf`]. This is to encourage/force other Nushell crates to account for
|
||||
/// the emulated current working directory, instead of using the [`std::env::current_dir`].
|
||||
pub trait IsAbsolute: PathForm {}
|
||||
impl IsAbsolute for Absolute {}
|
||||
impl IsAbsolute for Canonical {}
|
||||
|
||||
/// A marker trait that signifies one [`PathForm`] can be used as or trivially converted to
|
||||
/// another [`PathForm`].
|
||||
///
|
||||
/// The list of possible conversions are:
|
||||
/// - [`Relative`], [`Absolute`], or [`Canonical`] into [`Any`].
|
||||
/// - [`Canonical`] into [`Absolute`].
|
||||
/// - Any form into itself.
|
||||
pub trait PathCast<Form: PathForm>: PathForm {}
|
||||
impl<Form: PathForm> PathCast<Form> for Form {}
|
||||
impl PathCast<Any> for Relative {}
|
||||
impl PathCast<Any> for Absolute {}
|
||||
impl PathCast<Any> for Canonical {}
|
||||
impl PathCast<Absolute> for Canonical {}
|
||||
|
||||
/// A trait used to specify the output [`PathForm`] of a path join operation.
|
||||
///
|
||||
/// The output path forms based on the left hand side path form are as follows:
|
||||
///
|
||||
/// | Left hand side | Output form |
|
||||
/// | --------------:|:------------ |
|
||||
/// | [`Any`] | [`Any`] |
|
||||
/// | [`Relative`] | [`Any`] |
|
||||
/// | [`Absolute`] | [`Absolute`] |
|
||||
/// | [`Canonical`] | [`Absolute`] |
|
||||
pub trait PathJoin: PathForm {
|
||||
type Output: PathForm;
|
||||
}
|
||||
impl PathJoin for Any {
|
||||
type Output = Self;
|
||||
}
|
||||
impl PathJoin for Relative {
|
||||
type Output = Any;
|
||||
}
|
||||
impl PathJoin for Absolute {
|
||||
type Output = Self;
|
||||
}
|
||||
impl PathJoin for Canonical {
|
||||
type Output = Absolute;
|
||||
}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that support setting the file name or extension.
|
||||
///
|
||||
/// This includes the [`Any`], [`Relative`], and [`Absolute`] path forms.
|
||||
/// [`Canonical`] paths do not support this, since appending file names and extensions that contain
|
||||
/// path separators can cause the path to no longer be canonical.
|
||||
pub trait PathSet: PathForm {}
|
||||
impl PathSet for Any {}
|
||||
impl PathSet for Relative {}
|
||||
impl PathSet for Absolute {}
|
||||
|
||||
/// A marker trait for [`PathForm`]s that support pushing [`MaybeRelative`] paths.
|
||||
///
|
||||
/// This includes only [`Any`] and [`Absolute`] path forms.
|
||||
/// Pushing onto a [`Relative`] path could cause it to become [`Absolute`],
|
||||
/// which is why they do not support pushing.
|
||||
/// In the future, a `push_rel` and/or a `try_push` method could be added as an alternative.
|
||||
/// Similarly, [`Canonical`] paths may become uncanonical if a non-canonical path is pushed onto it.
|
||||
pub trait PathPush: PathSet {}
|
||||
impl PathPush for Any {}
|
||||
impl PathPush for Absolute {}
|
|
@ -2,12 +2,15 @@ mod assert_path_eq;
|
|||
mod components;
|
||||
pub mod dots;
|
||||
pub mod expansions;
|
||||
pub mod form;
|
||||
mod helpers;
|
||||
mod path;
|
||||
mod tilde;
|
||||
mod trailing_slash;
|
||||
|
||||
pub use components::components;
|
||||
pub use expansions::{canonicalize_with, expand_path_with, expand_to_real_path, locate_in_dirs};
|
||||
pub use helpers::{cache_dir, config_dir, data_dir, get_canonicalized_path, home_dir};
|
||||
pub use path::*;
|
||||
pub use tilde::expand_tilde;
|
||||
pub use trailing_slash::{has_trailing_slash, strip_trailing_slash};
|
||||
|
|
3095
crates/nu-path/src/path.rs
Normal file
3095
crates/nu-path/src/path.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -17,6 +17,7 @@ extend-ignore-re = [
|
|||
"--find ba\\b",
|
||||
"0x\\[ba be\\]",
|
||||
"\\)BaR'",
|
||||
"fo<66>.txt",
|
||||
]
|
||||
|
||||
[type.rust.extend-words]
|
||||
|
|
Loading…
Reference in a new issue