Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(turborepo): AbsoluteSystemPath #4841

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/turbopath/Cargo.toml
Expand Up @@ -12,3 +12,6 @@ path-slash = "0.2.1"
# TODO: Make this a crate feature
serde = { workspace = true }
thiserror = { workspace = true }

[dev-dependencies]
anyhow = { workspace = true }
199 changes: 199 additions & 0 deletions crates/turbopath/src/absolute_system_path.rs
@@ -0,0 +1,199 @@
#[cfg(not(windows))]
use std::os::unix::fs::symlink as symlink_file;
#[cfg(not(windows))]
use std::os::unix::fs::symlink as symlink_dir;
#[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file};
use std::{
borrow::Cow,
fmt, fs,
fs::Metadata,
io,
path::{Path, PathBuf},
};

use path_slash::CowExt;

use crate::{
AbsoluteSystemPathBuf, AnchoredSystemPathBuf, IntoSystem, PathError, PathValidationError,
RelativeSystemPathBuf, RelativeUnixPath,
};

pub struct AbsoluteSystemPath(Path);

impl ToOwned for AbsoluteSystemPath {
type Owned = AbsoluteSystemPathBuf;

fn to_owned(&self) -> Self::Owned {
AbsoluteSystemPathBuf(self.0.to_owned())
}
}

impl AsRef<AbsoluteSystemPath> for AbsoluteSystemPath {
fn as_ref(&self) -> &AbsoluteSystemPath {
self
}
}

impl fmt::Display for AbsoluteSystemPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.display().fmt(f)
}
}

impl AsRef<Path> for AbsoluteSystemPath {
fn as_ref(&self) -> &Path {
&self.0
}
}

impl AbsoluteSystemPath {
/// Creates a path that is known to be absolute and a system path.
/// If either of these conditions are not met, we error.
/// Does *not* do automatic conversion like `AbsoluteSystemPathBuf::new`
/// does
///
/// # Arguments
///
/// * `value`: The path to convert to an absolute system path
///
/// returns: Result<&AbsoluteSystemPath, PathError>
///
/// # Examples
///
/// ```
/// use turbopath::AbsoluteSystemPath;
/// #[cfg(unix)]
/// {
/// assert!(AbsoluteSystemPath::new("/foo/bar").is_ok());
/// assert!(AbsoluteSystemPath::new("foo/bar").is_err());
/// assert!(AbsoluteSystemPath::new("C:\\foo\\bar").is_err());
/// }
///
/// #[cfg(windows)]
/// {
/// assert!(AbsoluteSystemPath::new("C:\\foo\\bar").is_ok());
/// assert!(AbsoluteSystemPath::new("foo\\bar").is_err());
/// assert!(AbsoluteSystemPath::new("/foo/bar").is_err());
/// }
/// ```
pub fn new<P: AsRef<Path> + ?Sized>(value: &P) -> Result<&Self, PathError> {
let path = value.as_ref();
if path.is_relative() {
return Err(PathValidationError::NotAbsolute(path.to_owned()).into());
}
let path_str = path.to_str().ok_or_else(|| {
PathError::PathValidationError(PathValidationError::InvalidUnicode(path.to_owned()))
})?;

let system_path = Cow::from_slash(path_str);

match system_path {
Cow::Owned(path) => {
Err(PathValidationError::NotSystem(path.to_string_lossy().to_string()).into())
}
Cow::Borrowed(path) => {
let path = Path::new(path);
// copied from stdlib path.rs: relies on the representation of
// AbsoluteSystemPath being just a Path, the same way Path relies on
// just being an OsStr
let absolute_system_path = unsafe { &*(path as *const Path as *const Self) };
Ok(absolute_system_path)
}
}
}

pub fn as_path(&self) -> &Path {
&self.0
}

pub fn join_relative(&self, path: &RelativeSystemPathBuf) -> AbsoluteSystemPathBuf {
let path = self.0.join(path.as_path());
AbsoluteSystemPathBuf(path)
}

pub fn join_literal(&self, segment: &str) -> AbsoluteSystemPathBuf {
AbsoluteSystemPathBuf(self.0.join(segment))
}

pub fn join_unix_path(
&self,
unix_path: &RelativeUnixPath,
) -> Result<AbsoluteSystemPathBuf, PathError> {
let tail = unix_path.to_system_path()?;
Ok(AbsoluteSystemPathBuf(self.0.join(tail.as_path())))
}

pub fn anchor(&self, path: &AbsoluteSystemPath) -> Result<AnchoredSystemPathBuf, PathError> {
AnchoredSystemPathBuf::new(self, path)
}

pub fn ensure_dir(&self) -> Result<(), io::Error> {
if let Some(parent) = self.0.parent() {
fs::create_dir_all(parent)
} else {
Ok(())
}
}

pub fn symlink_to_file<P: AsRef<Path>>(&self, to: P) -> Result<(), PathError> {
let system_path = to.as_ref();
let system_path = system_path.into_system()?;
symlink_file(system_path, &self.0)?;
Ok(())
}

pub fn symlink_to_dir<P: AsRef<Path>>(&self, to: P) -> Result<(), PathError> {
let system_path = to.as_ref();
let system_path = system_path.into_system()?;
symlink_dir(&system_path, &self.0)?;
Ok(())
}

pub fn resolve(&self, path: &AnchoredSystemPathBuf) -> AbsoluteSystemPathBuf {
let path = self.0.join(path.as_path());
AbsoluteSystemPathBuf(path)
}

// note that this is *not* lstat. If this is a symlink, it
// will return metadata for the target.
pub fn stat(&self) -> Result<Metadata, PathError> {
Ok(fs::metadata(&self.0)?)
}

pub fn symlink_metadata(&self) -> Result<Metadata, PathError> {
Ok(fs::symlink_metadata(&self.0)?)
}

pub fn read_link(&self) -> Result<PathBuf, io::Error> {
fs::read_link(&self.0)
}

pub fn remove_file(&self) -> Result<(), io::Error> {
fs::remove_file(&self.0)
}
}

#[cfg(test)]
mod tests {
use anyhow::Result;

use super::*;

#[test]
fn test_create_absolute_path() -> Result<()> {
#[cfg(unix)]
{
let absolute_path = AbsoluteSystemPath::new("/foo/bar")?;
assert_eq!(absolute_path.to_string(), "/foo/bar");
}

#[cfg(windows)]
{
let absolute_path = AbsoluteSystemPath::new(r"C:\foo\bar")?;
assert_eq!(absolute_path.to_string(), r"C:\foo\bar");
}

Ok(())
}
}
90 changes: 38 additions & 52 deletions crates/turbopath/src/absolute_system_path_buf.rs
@@ -1,27 +1,33 @@
#[cfg(not(windows))]
use std::os::unix::fs::symlink as symlink_dir;
#[cfg(not(windows))]
use std::os::unix::fs::symlink as symlink_file;
#[cfg(windows)]
use std::os::windows::fs::{symlink_dir, symlink_file};
use std::{
borrow::Cow,
borrow::{Borrow, Cow},
ffi::OsStr,
fmt,
fs::{self, Metadata},
fmt, fs,
io::{self, Write},
path::{Components, Path, PathBuf},
};

use serde::Serialize;

use crate::{
AnchoredSystemPathBuf, IntoSystem, PathError, PathValidationError, RelativeSystemPathBuf,
RelativeUnixPath,
AbsoluteSystemPath, AnchoredSystemPathBuf, IntoSystem, PathError, PathValidationError,
RelativeSystemPathBuf,
};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize)]
pub struct AbsoluteSystemPathBuf(PathBuf);
pub struct AbsoluteSystemPathBuf(pub(crate) PathBuf);

impl Borrow<AbsoluteSystemPath> for AbsoluteSystemPathBuf {
fn borrow(&self) -> &AbsoluteSystemPath {
let path = self.as_path();
unsafe { &*(path as *const Path as *const AbsoluteSystemPath) }
}
}

impl AsRef<AbsoluteSystemPath> for AbsoluteSystemPathBuf {
fn as_ref(&self) -> &AbsoluteSystemPath {
self.borrow()
}
}

impl AbsoluteSystemPathBuf {
/// Create a new AbsoluteSystemPathBuf from `unchecked_path`.
Expand Down Expand Up @@ -91,7 +97,10 @@ impl AbsoluteSystemPathBuf {
/// assert_eq!(anchored_path.as_path(), Path::new("Documents"));
/// }
/// ```
pub fn anchor(&self, path: &AbsoluteSystemPathBuf) -> Result<AnchoredSystemPathBuf, PathError> {
pub fn anchor(
&self,
path: impl AsRef<AbsoluteSystemPath>,
) -> Result<AnchoredSystemPathBuf, PathError> {
AnchoredSystemPathBuf::new(self, path)
}

Expand Down Expand Up @@ -125,18 +134,14 @@ impl AbsoluteSystemPathBuf {
AbsoluteSystemPathBuf(self.0.join(path.as_path()))
}

pub fn join_unix_path(
&self,
unix_path: &RelativeUnixPath,
) -> Result<AbsoluteSystemPathBuf, PathError> {
let tail = unix_path.to_system_path()?;
Ok(AbsoluteSystemPathBuf(self.0.join(tail.as_path())))
}

pub fn as_path(&self) -> &Path {
self.0.as_path()
}

pub fn as_absolute_path(&self) -> &AbsoluteSystemPath {
self.borrow()
}

pub fn components(&self) -> Components<'_> {
self.0.components()
}
Expand All @@ -160,7 +165,7 @@ impl AbsoluteSystemPathBuf {
}

pub fn join_literal(&self, segment: &str) -> Self {
AbsoluteSystemPathBuf(self.0.join(Path::new(segment)))
AbsoluteSystemPathBuf(self.0.join(segment))
}

pub fn join_unix_path_literal<S: AsRef<str>>(
Expand Down Expand Up @@ -188,32 +193,15 @@ impl AbsoluteSystemPathBuf {
}

pub fn set_readonly(&self) -> Result<(), PathError> {
let mut perms = self.metadata()?.permissions();
let metadata = fs::symlink_metadata(self)?;
let mut perms = metadata.permissions();
perms.set_readonly(true);
fs::set_permissions(self.0.as_path(), perms)?;
Ok(())
}

pub fn is_readonly(&self) -> Result<bool, PathError> {
Ok(self.metadata()?.permissions().readonly())
}

pub fn symlink_to_file<P: AsRef<Path>>(&self, to: P) -> Result<(), PathError> {
let system_path = to.as_ref();
let system_path = system_path.into_system()?;
symlink_file(&system_path, &self.0.as_path())?;
Ok(())
}

pub fn symlink_to_dir<P: AsRef<Path>>(&self, to: P) -> Result<(), PathError> {
let system_path = to.as_ref();
let system_path = system_path.into_system()?;
symlink_dir(&system_path, &self.0.as_path())?;
Ok(())
}

pub fn read_symlink(&self) -> Result<PathBuf, io::Error> {
fs::read_link(self.0.as_path())
Ok(self.0.symlink_metadata()?.permissions().readonly())
}

pub fn create_with_contents(&self, contents: &str) -> Result<(), io::Error> {
Expand Down Expand Up @@ -244,16 +232,6 @@ impl AbsoluteSystemPathBuf {
self.0.extension()
}

pub fn metadata(&self) -> Result<Metadata, PathError> {
Ok(fs::symlink_metadata(&self.0)?)
}

// note that this is *not* lstat. If this is a symlink, it
// will return metadata for the target.
pub fn stat(&self) -> Result<Metadata, PathError> {
Ok(fs::metadata(&self.0)?)
}

pub fn open(&self) -> Result<fs::File, PathError> {
Ok(fs::File::open(&self.0)?)
}
Expand All @@ -262,6 +240,14 @@ impl AbsoluteSystemPathBuf {
let realpath = fs::canonicalize(&self.0)?;
Ok(Self(realpath))
}

pub fn symlink_to_file(&self, target: impl AsRef<Path>) -> Result<(), PathError> {
self.as_absolute_path().symlink_to_file(target)
}

pub fn symlink_to_dir(&self, target: impl AsRef<Path>) -> Result<(), PathError> {
self.as_absolute_path().symlink_to_dir(target)
}
}

impl From<AbsoluteSystemPathBuf> for PathBuf {
Expand Down
10 changes: 5 additions & 5 deletions crates/turbopath/src/anchored_system_path_buf.rs
Expand Up @@ -2,9 +2,7 @@ use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

use crate::{
AbsoluteSystemPathBuf, IntoSystem, PathError, PathValidationError, RelativeUnixPathBuf,
};
use crate::{AbsoluteSystemPath, IntoSystem, PathError, PathValidationError, RelativeUnixPathBuf};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
pub struct AnchoredSystemPathBuf(PathBuf);
Expand All @@ -24,9 +22,11 @@ impl TryFrom<&Path> for AnchoredSystemPathBuf {

impl AnchoredSystemPathBuf {
pub fn new(
root: &AbsoluteSystemPathBuf,
path: &AbsoluteSystemPathBuf,
root: impl AsRef<AbsoluteSystemPath>,
path: impl AsRef<AbsoluteSystemPath>,
) -> Result<Self, PathError> {
let root = root.as_ref();
let path = path.as_ref();
let stripped_path = path
.as_path()
.strip_prefix(root.as_path())
Expand Down