use crate::{Error, Manifest, Value};
use std::collections::HashSet;
use std::fs::read_dir;
use std::io;
use std::path::{Path, PathBuf};
pub trait AbstractFilesystem {
fn file_names_in(&self, rel_path: &str) -> io::Result<HashSet<Box<str>>>;
#[deprecated(note = "implement parse_root_workspace instead")]
#[doc(hidden)]
fn read_root_workspace(&self, _rel_path_hint: Option<&Path>) -> io::Result<(Vec<u8>, PathBuf)> {
Err(io::Error::new(io::ErrorKind::Unsupported, "AbstractFilesystem::read_root_workspace unimplemented"))
}
#[allow(deprecated)]
fn parse_root_workspace(&self, rel_path_hint: Option<&Path>) -> Result<(Manifest<Value>, PathBuf), Error> {
let (data, path) = self.read_root_workspace(rel_path_hint).map_err(|e| Error::Workspace(Box::new((e.into(), rel_path_hint.map(PathBuf::from)))))?;
let manifest = match Manifest::from_slice(&data) {
Ok(m) => m,
Err(e) => return Err(Error::Workspace(Box::new((e, Some(path))))),
};
if manifest.workspace.is_none() {
return Err(Error::Workspace(Box::new(
(Error::WorkspaceIntegrity("Not a Workspace.\nUse package.workspace to select a differnt path, or implement cargo_toml::AbstractFilesystem::parse_root_workspace".into()), Some(path))
)));
}
Ok((manifest, path))
}
}
impl<T> AbstractFilesystem for &T
where
T: AbstractFilesystem + ?Sized,
{
fn file_names_in(&self, rel_path: &str) -> io::Result<HashSet<Box<str>>> {
<T as AbstractFilesystem>::file_names_in(*self, rel_path)
}
#[allow(deprecated)]
fn read_root_workspace(&self, rel_path_hint: Option<&Path>) -> io::Result<(Vec<u8>, PathBuf)> {
<T as AbstractFilesystem>::read_root_workspace(*self, rel_path_hint)
}
fn parse_root_workspace(&self, rel_path_hint: Option<&Path>) -> Result<(Manifest<Value>, PathBuf), Error> {
<T as AbstractFilesystem>::parse_root_workspace(*self, rel_path_hint)
}
}
pub struct Filesystem<'a> {
path: &'a Path,
}
impl<'a> Filesystem<'a> {
#[must_use]
pub fn new(path: &'a Path) -> Self {
Self { path }
}
}
impl AbstractFilesystem for Filesystem<'_> {
fn file_names_in(&self, rel_path: &str) -> io::Result<HashSet<Box<str>>> {
Ok(read_dir(self.path.join(rel_path))?
.filter_map(|entry| entry.ok().map(|e| e.file_name().to_string_lossy().into_owned().into()))
.collect())
}
fn parse_root_workspace(&self, path: Option<&Path>) -> Result<(Manifest<Value>, PathBuf), Error> {
match path {
Some(path) => {
let ws = self.path.join(path);
let toml_path = ws.join("Cargo.toml");
let data = match std::fs::read(&toml_path) {
Ok(d) => d,
Err(e) => return Err(Error::Workspace(Box::new((Error::Io(e), Some(toml_path))))),
};
Ok((parse_workspace(&data, &toml_path)?, ws))
},
None => {
match find_workspace(self.path) {
Ok(found) => Ok(found),
Err(err) if self.path.is_absolute() => Err(err),
Err(_) => find_workspace(&self.path.ancestors().last().unwrap().canonicalize()?),
}
},
}
}
}
#[inline(never)]
fn find_workspace(path: &Path) -> Result<(Manifest<Value>, PathBuf), Error> {
if path.parent().is_none() {
return Err(io::Error::new(io::ErrorKind::NotFound, format!("Can't find workspace in '{}', because it has no parent directories", path.display())).into())
}
let mut last_error = None;
path.ancestors().skip(1)
.map(|parent| parent.join("Cargo.toml"))
.find_map(|p| {
let data = std::fs::read(&p).ok()?;
match parse_workspace(&data, &p) {
Ok(manifest) => Some((manifest, p)),
Err(e) => {
last_error = Some(e);
None
},
}
})
.ok_or(last_error.unwrap_or_else(|| {
let has_slash = path.to_str().is_some_and(|s| s.ends_with('/'));
io::Error::new(io::ErrorKind::NotFound, format!("Can't find workspace in '{}{}..'", path.display(), if has_slash { "" } else { "/" })).into()
}))
}
#[inline(never)]
fn parse_workspace(data: &[u8], path: &Path) -> Result<Manifest<Value>, Error> {
let manifest = Manifest::from_slice(data)?;
if manifest.workspace.is_none() {
return Err(Error::WorkspaceIntegrity(format!("Manifest at {} was expected to be a workspace.", path.display())));
}
Ok(manifest)
}