refactor(rust): cleanup path handling

- Avoids unnecessary string allocation when tracing paths
- Replaces `mut path & path.push()` with `path.join()`
- Avoids unncessary cloning of paths where applicable
- Use `dunce` crate to remove `UNC` prefix
- Improve performance of resolving `~` by avoiding unnecessary string allocations
- Resolve `~`, `$Env:USERPROFILE` and `$HOME` consistenly between different code paths
- Use `PathBuf` instead of `String` for paths in CLI args

I may have missed a couple of places but I think I covered 90% of path handling in the codebase
This commit is contained in:
amrbashir
2023-11-25 09:14:54 +02:00
committed by LGUG2Z
parent a68f3843b7
commit b39e65642b
18 changed files with 305 additions and 530 deletions

View File

@@ -7,10 +7,12 @@ edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
color-eyre = "0.6"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
strum = { version = "0.25", features = ["derive"] }
schemars = "0.8"
color-eyre = { workspace = true }
windows = { workspace = true }
dunce = { workspace = true }
dirs = { workspace = true }

View File

@@ -3,9 +3,10 @@ use std::fs::File;
use std::io::BufReader;
use std::ops::Deref;
use std::ops::DerefMut;
use std::path::PathBuf;
use std::path::Path;
use color_eyre::eyre::anyhow;
use color_eyre::eyre::bail;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -31,23 +32,20 @@ impl DerefMut for CustomLayout {
}
impl CustomLayout {
pub fn from_path_buf(path: PathBuf) -> Result<Self> {
let invalid_filetype = anyhow!("custom layouts must be json or yaml files");
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let layout: Self = match path.extension() {
Some(extension) => {
if extension == "yaml" || extension == "yml" {
serde_yaml::from_reader(BufReader::new(File::open(path)?))?
} else if extension == "json" {
serde_json::from_reader(BufReader::new(File::open(path)?))?
} else {
return Err(invalid_filetype);
}
Some(extension) if extension == "yaml" || extension == "yml" => {
serde_json::from_reader(BufReader::new(File::open(path)?))?
}
None => return Err(invalid_filetype),
Some(extension) if extension == "json" => {
serde_json::from_reader(BufReader::new(File::open(path)?))?
}
_ => return Err(anyhow!("custom layouts must be json or yaml files")),
};
if !layout.is_valid() {
return Err(anyhow!("the layout file provided was invalid"));
bail!("the layout file provided was invalid");
}
Ok(layout)

View File

@@ -1,10 +1,12 @@
#![warn(clippy::all, clippy::nursery, clippy::pedantic)]
#![allow(clippy::missing_errors_doc, clippy::use_self)]
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use clap::ValueEnum;
use color_eyre::eyre::anyhow;
use color_eyre::Result;
use schemars::JsonSchema;
use serde::Deserialize;
@@ -298,3 +300,36 @@ impl Sizing {
}
}
}
pub fn resolve_home_path<P: AsRef<Path>>(path: P) -> Result<PathBuf> {
let mut resolved_path = PathBuf::new();
let mut resolved = false;
for c in path.as_ref().components() {
match c {
std::path::Component::Normal(c)
if (c == "~" || c == "$Env:USERPROFILE" || c == "$HOME") && !resolved =>
{
let home = dirs::home_dir().ok_or_else(|| anyhow!("there is no home directory"))?;
resolved_path.extend(home.components());
resolved = true;
}
_ => resolved_path.push(c),
}
}
let parent = resolved_path
.parent()
.ok_or_else(|| anyhow!("cannot parse parent directory"))?;
Ok(if parent.is_dir() {
let file = resolved_path
.components()
.last()
.ok_or_else(|| anyhow!("cannot parse filename"))?;
dunce::canonicalize(parent)?.join(file)
} else {
resolved_path
})
}