Skip to content

Commit 7d9e69d

Browse files
committed
Precise description of directory relations when searching upward for spin.toml. Such precise, very great-grand.
Signed-off-by: itowlson <[email protected]>
1 parent 7941f85 commit 7d9e69d

File tree

9 files changed

+119
-72
lines changed

9 files changed

+119
-72
lines changed

crates/common/src/paths.rs

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ pub const DEFAULT_MANIFEST_FILE: &str = "spin.toml";
1010

1111
/// Attempts to find a manifest. If a path is provided, that path is resolved
1212
/// using `resolve_manifest_file_path`; otherwise, a directory search is carried out
13-
/// using `search_upwards_for_manifest`. If a manifest is found, a boolean is
14-
/// also returned indicating if it was the default: this can be used to
15-
/// notify the user that a non-default manifest is being used.
16-
pub fn find_manifest_file_path(provided_path: Option<impl AsRef<Path>>) -> Result<(PathBuf, bool)> {
13+
/// using `search_upwards_for_manifest`. If we had to search, and a manifest is found,
14+
/// a (non-zero) usize is returned indicating how far above the current directory it
15+
/// was found. (A usize of 0 indicates that the manifest was provided or found
16+
/// in the current directory.) This can be used to notify the user that a
17+
/// non-default manifest is being used.
18+
pub fn find_manifest_file_path(
19+
provided_path: Option<impl AsRef<Path>>,
20+
) -> Result<(PathBuf, usize)> {
1721
match provided_path {
18-
Some(provided_path) => resolve_manifest_file_path(provided_path).map(|p| (p, true)),
22+
Some(provided_path) => resolve_manifest_file_path(provided_path).map(|p| (p, 0)),
1923
None => search_upwards_for_manifest()
2024
.ok_or_else(|| anyhow!("\"{}\" not found", DEFAULT_MANIFEST_FILE)),
2125
}
@@ -52,25 +56,38 @@ pub fn resolve_manifest_file_path(provided_path: impl AsRef<Path>) -> Result<Pat
5256
/// Starting from the current directory, searches upward through
5357
/// the directory tree for a manifest (that is, a file with the default
5458
/// manifest name `spin.toml`). If found, the path to the manifest
55-
/// is returned, with a boolean flag indicating if the found path was
56-
/// the default (i.e. `spin.toml` in the current directory).
59+
/// is returned, with a usize indicating how far above the current directory it
60+
/// was found. (A usize of 0 indicates that the manifest was provided or found
61+
/// in the current directory.) This can be used to notify the user that a
62+
/// non-default manifest is being used.
5763
/// If no matching file is found, the function returns None.
58-
pub fn search_upwards_for_manifest() -> Option<(PathBuf, bool)> {
59-
let mut inferred_dir = std::env::current_dir().unwrap();
60-
let mut is_default = true;
64+
///
65+
/// The search is abandoned if it reaches the root directory, or the
66+
/// root of a Git repository, without finding a 'spin.toml'.
67+
pub 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+
}
6173

62-
loop {
63-
let candidate = inferred_dir.join(DEFAULT_MANIFEST_FILE);
74+
for distance in 1..20 {
75+
let inferred_dir = PathBuf::from("../".repeat(distance));
76+
if !inferred_dir.is_dir() {
77+
return None;
78+
}
6479

80+
let candidate = inferred_dir.join(DEFAULT_MANIFEST_FILE);
6581
if candidate.is_file() {
66-
return Some((candidate, is_default));
82+
return Some((candidate, distance));
6783
}
6884

69-
is_default = false;
70-
let parent = inferred_dir.parent()?;
71-
72-
inferred_dir = parent.to_owned();
85+
if is_git_root(&inferred_dir) {
86+
return None;
87+
}
7388
}
89+
90+
None
7491
}
7592

7693
/// Resolves the parent directory of a path, returning an error if the path
@@ -86,6 +103,10 @@ pub fn parent_dir(path: impl AsRef<Path>) -> Result<PathBuf> {
86103
Ok(parent.into())
87104
}
88105

106+
fn is_git_root(dir: &Path) -> bool {
107+
dir.join(".git").is_dir()
108+
}
109+
89110
#[cfg(test)]
90111
mod tests {
91112
use super::*;

crates/terminal/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ macro_rules! ceprint {
128128
};
129129
}
130130

131+
#[macro_export]
132+
macro_rules! ceprintln {
133+
($color:expr, $($arg:tt)*) => {
134+
use std::io::Write;
135+
let mut out = $crate::ColorText::stderr($color);
136+
let _ = writeln!(out, $($arg)*);
137+
drop(out); // Reset colors
138+
};
139+
}
140+
131141
pub mod colors {
132142
use termcolor::{Color, ColorSpec};
133143

src/commands/build.rs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ use std::{ffi::OsString, path::PathBuf};
22

33
use anyhow::Result;
44
use clap::Parser;
5-
use spin_common::ui::quoted_path;
65

7-
use crate::opts::{APP_MANIFEST_FILE_OPT, BUILD_UP_OPT};
6+
use crate::{
7+
directory_rels::notify_if_nondefault_rel,
8+
opts::{APP_MANIFEST_FILE_OPT, BUILD_UP_OPT},
9+
};
810

911
use super::up::UpCommand;
1012

@@ -37,15 +39,9 @@ pub struct BuildCommand {
3739

3840
impl BuildCommand {
3941
pub async fn run(self) -> Result<()> {
40-
let (manifest_file, is_default) =
42+
let (manifest_file, distance) =
4143
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
42-
if !is_default {
43-
terminal::einfo!(
44-
"Using 'spin.toml' from parent directory:",
45-
"{}",
46-
quoted_path(&manifest_file)
47-
);
48-
}
44+
notify_if_nondefault_rel(&manifest_file, distance);
4945

5046
spin_build::build(&manifest_file, &self.component_id).await?;
5147

src/commands/doctor.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,29 @@ pub struct DoctorCommand {
1717
name = APP_MANIFEST_FILE_OPT,
1818
short = 'f',
1919
long = "from",
20-
alias = "file",
20+
alias = "file"
2121
)]
2222
pub app_source: Option<PathBuf>,
2323
}
2424

2525
impl DoctorCommand {
2626
pub async fn run(self) -> Result<()> {
27-
let (manifest_file, is_default) =
27+
let (manifest_file, distance) =
2828
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
29-
30-
println!("{icon}The Spin Doctor is in.", icon = Emoji("📟 ", ""));
31-
if is_default {
32-
println!(
33-
"{icon}Checking {}...",
34-
manifest_file.display(),
35-
icon = Emoji("🩺 ", "")
36-
);
37-
} else {
38-
println!(
39-
"{icon}Checking file from parent directory {}...",
40-
manifest_file.display(),
41-
icon = Emoji("🩺 ", "")
29+
if distance > 0 {
30+
anyhow::bail!(
31+
"No spin.toml in current directory - did you mean '--from {}'?",
32+
manifest_file.display()
4233
);
4334
}
4435

36+
println!("{icon}The Spin Doctor is in.", icon = Emoji("📟 ", ""));
37+
println!(
38+
"{icon}Checking {}...",
39+
manifest_file.display(),
40+
icon = Emoji("🩺 ", "")
41+
);
42+
4543
let mut checkup = spin_doctor::Checkup::new(manifest_file)?;
4644
let mut has_problems = false;
4745
while let Some(PatientDiagnosis { diagnosis, patient }) = checkup.next_diagnosis().await? {

src/commands/registry.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use crate::opts::*;
1+
use crate::{directory_rels::notify_if_nondefault_rel, opts::*};
22
use anyhow::{Context, Result};
33
use clap::{Parser, Subcommand};
44
use indicatif::{ProgressBar, ProgressStyle};
5-
use spin_common::{arg_parser::parse_kv, ui::quoted_path};
5+
use spin_common::arg_parser::parse_kv;
66
use spin_oci::{client::InferPredefinedAnnotations, Client};
77
use std::{io::Read, path::PathBuf, time::Duration};
88

@@ -70,15 +70,9 @@ pub struct Push {
7070

7171
impl Push {
7272
pub async fn run(self) -> Result<()> {
73-
let (app_file, is_default) =
73+
let (app_file, distance) =
7474
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
75-
if !is_default {
76-
terminal::einfo!(
77-
"Using 'spin.toml' from parent directory:",
78-
"{}",
79-
quoted_path(&app_file)
80-
);
81-
}
75+
notify_if_nondefault_rel(&app_file, distance);
8276

8377
if self.build {
8478
spin_build::build(&app_file, &[]).await?;

src/commands/up.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use spin_oci::OciLoader;
1818
use spin_trigger::cli::{LaunchMetadata, SPIN_LOCAL_APP_DIR, SPIN_LOCKED_URL, SPIN_WORKING_DIR};
1919
use tempfile::TempDir;
2020

21-
use crate::opts::*;
21+
use crate::{directory_rels::notify_if_nondefault_rel, opts::*};
2222

2323
use self::app_source::{AppSource, ResolvedAppSource};
2424

@@ -383,14 +383,8 @@ impl UpCommand {
383383
AppSource::Unresolvable(msg)
384384
} else {
385385
match spin_common::paths::search_upwards_for_manifest() {
386-
Some((manifest_path, is_default)) => {
387-
if !is_default {
388-
terminal::einfo!(
389-
"Using 'spin.toml' from parent directory:",
390-
"{}",
391-
quoted_path(&manifest_path)
392-
);
393-
}
386+
Some((manifest_path, distance)) => {
387+
notify_if_nondefault_rel(&manifest_path, distance);
394388
AppSource::File(manifest_path)
395389
}
396390
None => AppSource::None,

src/commands/watch.rs

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ use anyhow::{Context, Result};
88
use clap::Parser;
99
use itertools::Itertools;
1010
use path_absolutize::Absolutize;
11-
use spin_common::{paths::parent_dir, ui::quoted_path};
11+
use spin_common::paths::parent_dir;
1212
use uuid::Uuid;
1313
use watchexec::Watchexec;
1414

15-
use crate::opts::{
16-
APP_MANIFEST_FILE_OPT, WATCH_CLEAR_OPT, WATCH_DEBOUNCE_OPT, WATCH_SKIP_BUILD_OPT,
15+
use crate::{
16+
directory_rels::notify_if_nondefault_rel,
17+
opts::{APP_MANIFEST_FILE_OPT, WATCH_CLEAR_OPT, WATCH_DEBOUNCE_OPT, WATCH_SKIP_BUILD_OPT},
1718
};
1819

1920
mod buildifier;
@@ -91,15 +92,10 @@ impl WatchCommand {
9192
// has just done so. Subsequent asset changes _do_ clear the screen.
9293

9394
let spin_bin = std::env::current_exe()?;
94-
let (manifest_file, is_default) =
95+
let (manifest_file, distance) =
9596
spin_common::paths::find_manifest_file_path(self.app_source.as_ref())?;
96-
if !is_default {
97-
terminal::einfo!(
98-
"Using 'spin.toml' from parent directory:",
99-
"{}",
100-
quoted_path(&manifest_file)
101-
);
102-
}
97+
notify_if_nondefault_rel(&manifest_file, distance);
98+
10399
let manifest_file = manifest_file.absolutize()?.to_path_buf(); // or watchexec misses files in subdirectories
104100
let manifest_dir = parent_dir(&manifest_file)?;
105101

src/directory_rels.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Human-readable descriptions for directory relationships,
2+
//! and helpers for standard display.
3+
4+
use std::path::Path;
5+
6+
fn parent_rel(distance: usize) -> String {
7+
match distance {
8+
0 => "".to_owned(),
9+
1 => "parent".to_owned(),
10+
2 => "grandparent".to_owned(),
11+
_ => format!("{}grandparent", "great-".repeat(distance - 2)),
12+
}
13+
}
14+
15+
pub fn notify_if_nondefault_rel(manifest_file: &Path, distance: usize) {
16+
if distance > 0 {
17+
terminal::einfo!(
18+
"No 'spin.toml' in current directory.",
19+
"Using 'spin.toml' from {} directory ({})",
20+
parent_rel(distance),
21+
manifest_file.display(),
22+
);
23+
}
24+
}
25+
26+
#[cfg(test)]
27+
mod tests {
28+
use super::*;
29+
30+
#[test]
31+
fn ancestry_text_is_correct() {
32+
assert_eq!("parent", parent_rel(1));
33+
assert_eq!("grandparent", parent_rel(2));
34+
assert_eq!("great-grandparent", parent_rel(3));
35+
assert_eq!("great-great-great-grandparent", parent_rel(5)); // I hope you're happy Lann
36+
}
37+
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod build_info;
22
pub mod commands;
3+
mod directory_rels;
34
pub(crate) mod opts;
45
pub mod subprocess;
56

0 commit comments

Comments
 (0)