1use anyhow::{anyhow, Context, Result};
4use std::path::{Path, PathBuf};
5
6use crate::ui::quoted_path;
7
8pub const DEFAULT_MANIFEST_FILE: &str = "spin.toml";
10
11pub fn find_manifest_file_path(
19 provided_path: Option<impl AsRef<Path>>,
20) -> Result<(PathBuf, usize)> {
21 match provided_path {
22 Some(provided_path) => resolve_manifest_file_path(provided_path).map(|p| (p, 0)),
23 None => search_upwards_for_manifest()
24 .ok_or_else(|| anyhow!("\"{}\" not found", DEFAULT_MANIFEST_FILE)),
25 }
26}
27
28pub fn resolve_manifest_file_path(provided_path: impl AsRef<Path>) -> Result<PathBuf> {
31 let path = provided_path.as_ref();
32
33 if path.is_file() {
34 Ok(path.to_owned())
35 } else if path.is_dir() {
36 let file_path = path.join(DEFAULT_MANIFEST_FILE);
37 if file_path.is_file() {
38 Ok(file_path)
39 } else {
40 Err(anyhow!(
41 "Directory {} does not contain a file named 'spin.toml'",
42 path.display()
43 ))
44 }
45 } else {
46 let pd = path.display();
47 let err = match path.try_exists() {
48 Err(e) => anyhow!("Error accessing path {pd}: {e:#}"),
49 Ok(false) => anyhow!("No such file or directory '{pd}'"),
50 Ok(true) => anyhow!("Path {pd} is neither a file nor a directory"),
51 };
52 Err(err)
53 }
54}
55
56pub fn search_upwards_for_manifest() -> Option<(PathBuf, usize)> {
68 let candidate = PathBuf::from(DEFAULT_MANIFEST_FILE);
69
70 if candidate.is_file() {
71 return Some((candidate, 0));
72 }
73
74 for distance in 1..20 {
75 let inferred_dir = PathBuf::from("../".repeat(distance));
76 if !inferred_dir.is_dir() {
77 return None;
78 }
79
80 let candidate = inferred_dir.join(DEFAULT_MANIFEST_FILE);
81 if candidate.is_file() {
82 return Some((candidate, distance));
83 }
84
85 if is_git_root(&inferred_dir) {
86 return None;
87 }
88 }
89
90 None
91}
92
93pub fn parent_dir(path: impl AsRef<Path>) -> Result<PathBuf> {
96 let path = path.as_ref();
97 let mut parent = path
98 .parent()
99 .with_context(|| format!("No parent directory for path {}", quoted_path(path)))?;
100 if parent == Path::new("") {
101 parent = Path::new(".");
102 }
103 Ok(parent.into())
104}
105
106fn is_git_root(dir: &Path) -> bool {
107 dir.join(".git").is_dir()
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn parent_returns_parent() {
116 assert_eq!(parent_dir("foo/bar").unwrap(), Path::new("foo"));
117 }
118
119 #[test]
120 fn blank_parent_returns_dot() {
121 assert_eq!(parent_dir("foo").unwrap(), Path::new("."));
122 }
123
124 #[test]
125 fn no_parent_returns_err() {
126 parent_dir("").unwrap_err();
127 }
128}