Skip to content

Commit

Permalink
refactor: migrate from typed-path to camino
Browse files Browse the repository at this point in the history
  • Loading branch information
Hofer-Julian committed Sep 13, 2024
1 parent 7d13a20 commit 4409066
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 55 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", default-features = false }
tracing-test = { version = "0.2.4" }
trybuild = { version = "1.0.91" }
typed-path = { version = "0.9.0" }
camino = { version = "1.1.9"}
url = { version = "2.5.0" }
uuid = { version = "1.8.0", default-features = false }
walkdir = "2.5.0"
Expand Down
2 changes: 1 addition & 1 deletion crates/file_url/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ license.workspace = true
url = { workspace = true }
percent-encoding = { workspace = true }
itertools = { workspace = true }
typed-path = { workspace = true }
camino = { workspace = true }
thiserror = "1.0.61"

[dev-dependencies]
Expand Down
38 changes: 14 additions & 24 deletions crates/file_url/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
//! This crates provides functionality that tries to parse a `file://` URL as a path on all operating
//! systems. This is useful when you want to convert a `file://` URL to a path and vice versa.

use camino::Utf8Path;
use itertools::Itertools;
use percent_encoding::{percent_decode, percent_encode, AsciiSet, CONTROLS};
use std::fmt::Write;
use std::path::PathBuf;
use std::str::FromStr;
use thiserror::Error;
use typed_path::{
Utf8TypedComponent, Utf8TypedPath, Utf8UnixComponent, Utf8WindowsComponent, Utf8WindowsPrefix,
};
use url::{Host, Url};

/// Returns true if the specified segment is considered to be a Windows drive letter segment.
Expand Down Expand Up @@ -98,42 +96,35 @@ fn starts_with_windows_drive_letter(s: &str) -> bool {
&& (s.len() == 2 || matches!(s.as_bytes()[2], b'/' | b'\\' | b'?' | b'#'))
}

fn path_to_url<'a>(path: impl Into<Utf8TypedPath<'a>>) -> Result<String, FileURLParseError> {
let path = path.into();
let mut components = path.components();
fn path_to_url(path: impl AsRef<Utf8Path>) -> Result<String, FileURLParseError> {
let mut components = path.as_ref().components();

let mut result = String::from("file://");
let host_start = result.len() + 1;

let root = components.next();
match root {
Some(Utf8TypedComponent::Windows(Utf8WindowsComponent::Prefix(ref p))) => match p.kind() {
Utf8WindowsPrefix::Disk(letter) | Utf8WindowsPrefix::VerbatimDisk(letter) => {
Some(camino::Utf8Component::Prefix(prefix)) => match prefix.kind() {
camino::Utf8Prefix::Disk(letter) | camino::Utf8Prefix::VerbatimDisk(letter) => {
result.push('/');
result.push(letter);
result.push(letter as char);
result.push(':');
}
Utf8WindowsPrefix::UNC(server, share)
| Utf8WindowsPrefix::VerbatimUNC(server, share) => {
let host =
Host::parse(server).map_err(|_err| FileURLParseError::NotAnAbsolutePath)?;
write!(result, "{host}").unwrap();
camino::Utf8Prefix::UNC(server, share)
| camino::Utf8Prefix::VerbatimUNC(server, share) => {
write!(result, "{server}").unwrap();
result.push('/');
result.extend(percent_encode(share.as_bytes(), PATH_SEGMENT));
}
_ => return Err(FileURLParseError::NotAnAbsolutePath),
},
Some(Utf8TypedComponent::Unix(Utf8UnixComponent::RootDir)) => {}
Some(camino::Utf8Component::RootDir) => {}
_ => return Err(FileURLParseError::NotAnAbsolutePath),
}

let mut path_only_has_prefix = true;
for component in components {
if matches!(
component,
Utf8TypedComponent::Windows(Utf8WindowsComponent::RootDir)
| Utf8TypedComponent::Unix(Utf8UnixComponent::RootDir)
) {
if matches!(component, camino::Utf8Component::RootDir) {
continue;
}

Expand Down Expand Up @@ -163,14 +154,13 @@ pub enum FileURLParseError {
#[error("The URL string is invalid")]
InvalidUrl(#[from] url::ParseError),
}
pub fn file_path_to_url<'a>(path: impl Into<Utf8TypedPath<'a>>) -> Result<Url, FileURLParseError> {

pub fn file_path_to_url(path: impl AsRef<Utf8Path>) -> Result<Url, FileURLParseError> {
let url = path_to_url(path)?;
Url::from_str(&url).map_err(FileURLParseError::InvalidUrl)
}

pub fn directory_path_to_url<'a>(
path: impl Into<Utf8TypedPath<'a>>,
) -> Result<Url, FileURLParseError> {
pub fn directory_path_to_url(path: impl AsRef<Utf8Path>) -> Result<Url, FileURLParseError> {
let mut url = path_to_url(path)?;
if !url.ends_with('/') {
url.push('/');
Expand Down
2 changes: 1 addition & 1 deletion crates/rattler_conda_types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ smallvec = { workspace = true, features = ["serde", "const_new", "const_generics
strum = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tracing = { workspace = true }
typed-path = { workspace = true }
camino = { workspace = true }
url = { workspace = true, features = ["serde"] }
indexmap = { workspace = true }
rattler_redaction = { version = "0.1.2", path = "../rattler_redaction" }
Expand Down
53 changes: 30 additions & 23 deletions crates/rattler_conda_types/src/channel/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ use std::{
str::FromStr,
};

use camino::{Utf8Path, Utf8PathBuf};
use file_url::directory_path_to_url;
use rattler_redaction::Redact;
use serde::{Deserialize, Serialize, Serializer};
use thiserror::Error;
use typed_path::{Utf8NativePathBuf, Utf8TypedPath, Utf8TypedPathBuf};
use url::Url;

use super::{ParsePlatformError, Platform};
Expand Down Expand Up @@ -205,7 +205,7 @@ impl Channel {
#[cfg(not(target_arch = "wasm32"))]
{
let absolute_path = absolute_path(channel, &config.root_dir)?;
let url = directory_path_to_url(absolute_path.to_path())
let url = directory_path_to_url(&absolute_path)
.map_err(|_err| ParseChannelError::InvalidPath(channel.to_owned()))?;
Self {
platforms,
Expand Down Expand Up @@ -398,6 +398,10 @@ pub enum ParseChannelError {
/// The root directory is not UTF-8 encoded.
#[error("root directory: '{0}' of channel config is not utf8 encoded")]
NotUtf8RootDir(PathBuf),

/// An I/O error occurred.
#[error("I/O error: {0}")]
IoError(String),
}

impl From<ParsePlatformError> for ParseChannelError {
Expand All @@ -412,6 +416,12 @@ impl From<url::ParseError> for ParseChannelError {
}
}

impl From<std::io::Error> for ParseChannelError {
fn from(err: std::io::Error) -> Self {
ParseChannelError::IoError(err.to_string())
}
}

/// Extract the platforms from the given human readable channel.
#[allow(clippy::type_complexity)]
fn parse_platforms(channel: &str) -> Result<(Option<Vec<Platform>>, &str), ParsePlatformError> {
Expand Down Expand Up @@ -444,43 +454,46 @@ pub(crate) const fn default_platforms() -> &'static [Platform] {
}

/// Returns the specified path as an absolute path
fn absolute_path(path_str: &str, root_dir: &Path) -> Result<Utf8TypedPathBuf, ParseChannelError> {
let path = Utf8TypedPath::from(path_str);
fn absolute_path(path_str: &str, root_dir: &Path) -> Result<Utf8PathBuf, ParseChannelError> {
let path = Utf8Path::new(path_str);
if path.is_absolute() {
return Ok(path.normalize());
return path.canonicalize_utf8().map_err(From::from);
}

// Parse the `~/` as the home folder
if let Ok(user_path) = path.strip_prefix("~/") {
return Ok(Utf8TypedPathBuf::from(
return Utf8PathBuf::from(
dirs::home_dir()
.ok_or(ParseChannelError::InvalidPath(path.to_string()))?
.to_str()
.ok_or(ParseChannelError::NotUtf8RootDir(PathBuf::from(path_str)))?,
)
.join(user_path)
.normalize());
.canonicalize_utf8()
.map_err(From::from);
}

let root_dir_str = root_dir
.to_str()
.ok_or_else(|| ParseChannelError::NotUtf8RootDir(root_dir.to_path_buf()))?;
let native_root_dir = Utf8NativePathBuf::from(root_dir_str);
let native_root_dir = Utf8Path::new(root_dir_str);

if !native_root_dir.is_absolute() {
return Err(ParseChannelError::NonAbsoluteRootDir(
root_dir.to_path_buf(),
));
}

Ok(native_root_dir.to_typed_path().join(path).normalize())
native_root_dir
.join(path)
.canonicalize_utf8()
.map_err(From::from)
}

#[cfg(test)]
mod tests {
use std::str::FromStr;

use typed_path::{NativePath, Utf8NativePath};
use url::Url;

use super::*;
Expand Down Expand Up @@ -509,9 +522,9 @@ mod tests {
#[test]
fn test_absolute_path() {
let current_dir = std::env::current_dir().expect("no current dir?");
let native_current_dir = typed_path::utils::utf8_current_dir()
.expect("")
.to_typed_path_buf();
let native_current_dir =
Utf8PathBuf::from_path_buf(current_dir.clone()).expect("invalid UTF-8 in current dir");

assert_eq!(
absolute_path(".", &current_dir).as_ref(),
Ok(&native_current_dir)
Expand All @@ -530,13 +543,9 @@ mod tests {
Ok(&parent_dir.join("foo"))
);

let home_dir = dirs::home_dir()
.unwrap()
.into_os_string()
.into_encoded_bytes();
let home_dir = Utf8NativePath::from_bytes_path(NativePath::new(&home_dir))
.unwrap()
.to_typed_path();
let home_dir = dirs::home_dir().expect("no home dir?");
let home_dir = Utf8PathBuf::from_path_buf(home_dir).expect("invalid UTF-8 in home dir");

assert_eq!(
absolute_path("~/unix_dir", &current_dir).unwrap(),
home_dir.join("unix_dir")
Expand Down Expand Up @@ -625,9 +634,7 @@ mod tests {
let expected = absolute_path("./dir/does/not_exist", &current_dir).unwrap();
assert_eq!(
channel.name(),
file_url::directory_path_to_url(expected.to_path())
.unwrap()
.as_str()
file_url::directory_path_to_url(&expected).unwrap().as_str()
);
assert_eq!(channel.platforms, None);
assert_eq!(
Expand Down
7 changes: 2 additions & 5 deletions crates/rattler_conda_types/src/match_spec/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use nom::{
use rattler_digest::{parse_digest_from_hex, Md5, Sha256};
use smallvec::SmallVec;
use thiserror::Error;
use typed_path::Utf8TypedPath;
use url::Url;

use super::{
Expand Down Expand Up @@ -253,8 +252,7 @@ fn parse_bracket_vec_into_components(
}
// 2 Is the spec an absolute path, parse it as an url
else if is_absolute_path(value) {
let path = Utf8TypedPath::from(value);
file_url::file_path_to_url(path)
file_url::file_path_to_url(value)
.map_err(|_error| ParseMatchSpecError::InvalidPackagePathOrUrl)?
} else {
return Err(ParseMatchSpecError::InvalidPackagePathOrUrl);
Expand Down Expand Up @@ -292,8 +290,7 @@ pub fn parse_url_like(input: &str) -> Result<Option<Url>, ParseMatchSpecError> {
}
// Is the spec a path, parse it as an url
if is_absolute_path(input) {
let path = Utf8TypedPath::from(input);
return file_url::file_path_to_url(path)
return file_url::file_path_to_url(input)
.map(Some)
.map_err(|_err| ParseMatchSpecError::InvalidPackagePathOrUrl);
}
Expand Down

0 comments on commit 4409066

Please sign in to comment.