1use crate::{Error, Manifest, Value};
2use std::collections::HashSet;
3use std::fs::read_dir;
4use std::io;
5use std::path::{Path, PathBuf};
6
7pub trait AbstractFilesystem {
11 fn file_names_in(&self, rel_path: &str) -> io::Result<HashSet<Box<str>>>;
13
14 #[deprecated(note = "implement parse_root_workspace instead")]
22 #[doc(hidden)]
23 fn read_root_workspace(&self, _rel_path_hint: Option<&Path>) -> io::Result<(Vec<u8>, PathBuf)> {
24 Err(io::Error::new(io::ErrorKind::Unsupported, "AbstractFilesystem::read_root_workspace unimplemented"))
25 }
26
27 #[allow(deprecated)]
33 fn parse_root_workspace(&self, rel_path_hint: Option<&Path>) -> Result<(Manifest<Value>, PathBuf), Error> {
34 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)))))?;
35 let manifest = match Manifest::from_slice(&data) {
36 Ok(m) => m,
37 Err(e) => return Err(Error::Workspace(Box::new((e, Some(path))))),
38 };
39 if manifest.workspace.is_none() {
40 return Err(Error::Workspace(Box::new(
41 (Error::WorkspaceIntegrity("Not a Workspace.\nUse package.workspace to select a differnt path, or implement cargo_toml::AbstractFilesystem::parse_root_workspace".into()), Some(path))
42 )));
43 }
44 Ok((manifest, path))
45 }
46}
47
48impl<T> AbstractFilesystem for &T
49where
50 T: AbstractFilesystem + ?Sized,
51{
52 fn file_names_in(&self, rel_path: &str) -> io::Result<HashSet<Box<str>>> {
53 <T as AbstractFilesystem>::file_names_in(*self, rel_path)
54 }
55
56 #[allow(deprecated)]
57 fn read_root_workspace(&self, rel_path_hint: Option<&Path>) -> io::Result<(Vec<u8>, PathBuf)> {
58 <T as AbstractFilesystem>::read_root_workspace(*self, rel_path_hint)
59 }
60
61 fn parse_root_workspace(&self, rel_path_hint: Option<&Path>) -> Result<(Manifest<Value>, PathBuf), Error> {
62 <T as AbstractFilesystem>::parse_root_workspace(*self, rel_path_hint)
63 }
64}
65
66pub struct Filesystem<'a> {
68 path: &'a Path,
69}
70
71impl<'a> Filesystem<'a> {
72 #[must_use]
73 pub fn new(path: &'a Path) -> Self {
74 Self { path }
75 }
76}
77
78impl AbstractFilesystem for Filesystem<'_> {
79 fn file_names_in(&self, rel_path: &str) -> io::Result<HashSet<Box<str>>> {
80 Ok(read_dir(self.path.join(rel_path))?
81 .filter_map(|entry| entry.ok().map(|e| e.file_name().to_string_lossy().into_owned().into()))
82 .collect())
83 }
84
85 fn parse_root_workspace(&self, path: Option<&Path>) -> Result<(Manifest<Value>, PathBuf), Error> {
86 match path {
87 Some(path) => {
88 let ws = self.path.join(path);
89 let toml_path = ws.join("Cargo.toml");
90 let data = match std::fs::read(&toml_path) {
91 Ok(d) => d,
92 Err(e) => return Err(Error::Workspace(Box::new((Error::Io(e), Some(toml_path))))),
93 };
94 Ok((parse_workspace(&data, &toml_path)?, ws))
95 },
96 None => {
97 match find_workspace(self.path) {
99 Ok(found) => Ok(found),
100 Err(err) if self.path.is_absolute() => Err(err),
101 Err(_) => find_workspace(&self.path.ancestors().last().unwrap().canonicalize()?),
102 }
103 },
104 }
105 }
106}
107
108#[inline(never)]
109fn find_workspace(path: &Path) -> Result<(Manifest<Value>, PathBuf), Error> {
110 if path.parent().is_none() {
111 return Err(io::Error::new(io::ErrorKind::NotFound, format!("Can't find workspace in '{}', because it has no parent directories", path.display())).into())
112 }
113 let mut last_error = None;
114 path.ancestors().skip(1)
115 .map(|parent| parent.join("Cargo.toml"))
116 .find_map(|p| {
117 let data = std::fs::read(&p).ok()?;
118 match parse_workspace(&data, &p) {
119 Ok(manifest) => Some((manifest, p)),
120 Err(e) => {
121 last_error = Some(e);
122 None
123 },
124 }
125 })
126 .ok_or(last_error.unwrap_or_else(|| {
127 let has_slash = path.to_str().is_some_and(|s| s.ends_with('/'));
128 io::Error::new(io::ErrorKind::NotFound, format!("Can't find workspace in '{}{}..'", path.display(), if has_slash { "" } else { "/" })).into()
129 }))
130}
131
132#[inline(never)]
133fn parse_workspace(data: &[u8], path: &Path) -> Result<Manifest<Value>, Error> {
134 let manifest = Manifest::from_slice(data)?;
135 if manifest.workspace.is_none() {
136 return Err(Error::WorkspaceIntegrity(format!("Manifest at {} was expected to be a workspace.", path.display())));
137 }
138 Ok(manifest)
139}