Skip to content

Commit

Permalink
Adding a proper AbsoluteSystemPath
Browse files Browse the repository at this point in the history
  • Loading branch information
NicholasLYang committed May 11, 2023
1 parent 1e5493f commit 2e7c1b4
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 100 deletions.
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

0 comments on commit 2e7c1b4

Please sign in to comment.