new: re-probe wizard defaults when packaging a different directory
The detection + probe pass ran against the cwd before the source-location question, so answering 'another directory' still offered the cwd's name, version, description, homepage and license sniff as defaults (only the orig origin followed the chosen tree). When the answer redirects the wizard to a different directory, the detection + probe now run again there, feeding every subsequent probe-derived default; the originally detected directory is not re-scanned and explicit flags keep winning.
This commit is contained in:
+137
-6
@@ -102,12 +102,8 @@ fn is_interactive() -> bool {
|
||||
/// answer is skipped (flag > detected/probe > default merge order).
|
||||
async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let detect_dir = cli.source.clone().unwrap_or_else(|| cwd.clone());
|
||||
let detection = detect::detect(&detect_dir);
|
||||
let probe = match &detection {
|
||||
Detection::Single(id) => templates::get(*id).and_then(|t| t.probe(&detect_dir)),
|
||||
_ => None,
|
||||
};
|
||||
let mut detect_dir = cli.source.clone().unwrap_or_else(|| cwd.clone());
|
||||
let (detection, mut probe) = detect_and_probe(&detect_dir);
|
||||
// Whether the flags imply a fresh skeleton (name given, no --source).
|
||||
let implied_skeleton = cli.source.is_none() && cli.name.is_some();
|
||||
// Whether the language question is skipped by a confident detection
|
||||
@@ -249,6 +245,26 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
||||
let packaged_dir = cli.source.clone();
|
||||
let mut origin = packaged_dir.as_deref().and_then(GitOrigin::detect);
|
||||
|
||||
// The probe data read above describes `detect_dir` — the cwd unless
|
||||
// `--source` pointed somewhere. When the source-location answer just
|
||||
// redirected the wizard to another directory, re-run the detection +
|
||||
// probe there (the same code path as the initial pass) so every
|
||||
// following probe-derived default — upstream version, description,
|
||||
// homepage, license, command, the pkg-config hint — describes the
|
||||
// project actually being packaged. The detected directory itself is
|
||||
// never re-probed (the data is already fresh and the scan is not free),
|
||||
// and explicit flags keep winning throughout: the probe only ever feeds
|
||||
// the defaults of questions without a flag answer.
|
||||
if let Some(chosen) = packaged_dir.as_deref()
|
||||
&& !same_directory(chosen, &detect_dir)
|
||||
{
|
||||
let (_, refreshed) = detect_and_probe(chosen);
|
||||
probe = refreshed;
|
||||
// The license sniff and the pkg-config hint read the same directory
|
||||
// the probe data now comes from.
|
||||
detect_dir = chosen.to_path_buf();
|
||||
}
|
||||
|
||||
// 4. Upstream version: probed project version, then the tag HEAD sits
|
||||
// on, then `<lasttag>+git<YYYYMMDD>.<hash>`, then 0.1.0. A raw probe
|
||||
// that fails `validate_upstream_version` (e.g. `1.0-2` or `v1.0`) is
|
||||
@@ -637,6 +653,33 @@ pub async fn offer_verification(
|
||||
}
|
||||
}
|
||||
|
||||
/// Detection + template probe of one candidate directory: the probe data
|
||||
/// exists only for a confidently detected single-template project. The one
|
||||
/// code path for both wizard passes — the initial sweep of the detected
|
||||
/// directory and the re-probe when the source-location answer redirects the
|
||||
/// wizard elsewhere.
|
||||
fn detect_and_probe(dir: &std::path::Path) -> (Detection, Option<ProbeResult>) {
|
||||
let detection = detect::detect(dir);
|
||||
let probe = match &detection {
|
||||
Detection::Single(id) => templates::get(*id).and_then(|t| t.probe(dir)),
|
||||
_ => None,
|
||||
};
|
||||
(detection, probe)
|
||||
}
|
||||
|
||||
/// Whether two paths name the same directory: lexical equality first, then
|
||||
/// (for spellings like `.` next to the absolute cwd) the canonicalized
|
||||
/// forms. A path that cannot be canonicalized only ever equals itself.
|
||||
fn same_directory(a: &std::path::Path, b: &std::path::Path) -> bool {
|
||||
if a == b {
|
||||
return true;
|
||||
}
|
||||
match (a.canonicalize(), b.canonicalize()) {
|
||||
(Ok(a), Ok(b)) => a == b,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// The preselected default of the pkg-config opt-in question: whether the
|
||||
/// project's build file hints at pkg-config usage (`dependency(` in
|
||||
/// meson.build, `pkg_check_modules` / `find_package(PkgConfig` in
|
||||
@@ -1520,4 +1563,92 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The probe helper is the same code path for the initial pass and the
|
||||
/// re-probe: pointed at directory A it reports A's project, pointed at
|
||||
/// directory B it reports B's. So when the source-location answer
|
||||
/// redirects the wizard from the detected directory to another one, the
|
||||
/// refreshed defaults (upstream version, description, homepage, license)
|
||||
/// describe the project actually being packaged — regression: they used
|
||||
/// to keep coming from the original directory.
|
||||
#[test]
|
||||
fn detect_and_probe_reports_the_directory_it_is_given() {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let manifest = |name: &str, version: &str, license: &str| {
|
||||
format!(
|
||||
"[package]\nname = \"{name}\"\nversion = \"{version}\"\n\
|
||||
edition = \"2021\"\ndescription = \"{name} does {name} \
|
||||
things\"\nhomepage = \"https://{name}.example.com\"\n\
|
||||
license = \"{license}\"\n"
|
||||
)
|
||||
};
|
||||
let dir_a = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
dir_a.path().join("Cargo.toml"),
|
||||
manifest("alpha", "0.1.0", "MIT"),
|
||||
)
|
||||
.unwrap();
|
||||
let dir_b = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
dir_b.path().join("Cargo.toml"),
|
||||
manifest("beta", "2.9.9", "GPL-3.0+"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// The initial pass over dir A.
|
||||
let (detection_a, probe_a) = detect_and_probe(dir_a.path());
|
||||
assert_eq!(detection_a, Detection::Single(Tid::Rust));
|
||||
let probe_a = probe_a.expect("dir A is a rust project");
|
||||
assert_eq!(probe_a.name.as_deref(), Some("alpha"));
|
||||
assert_eq!(probe_a.version.as_deref(), Some("0.1.0"));
|
||||
assert_eq!(probe_a.license.as_deref(), Some("MIT"));
|
||||
|
||||
// The user chose dir B instead: the refreshed probe comes from B,
|
||||
// never from A.
|
||||
let (detection_b, probe_b) = detect_and_probe(dir_b.path());
|
||||
assert_eq!(detection_b, Detection::Single(Tid::Rust));
|
||||
let probe_b = probe_b.expect("dir B is a rust project");
|
||||
assert_eq!(probe_b.name.as_deref(), Some("beta"));
|
||||
assert_eq!(probe_b.version.as_deref(), Some("2.9.9"));
|
||||
assert_eq!(
|
||||
probe_b.description.as_deref(),
|
||||
Some("beta does beta things")
|
||||
);
|
||||
assert_eq!(
|
||||
probe_b.homepage.as_deref(),
|
||||
Some("https://beta.example.com")
|
||||
);
|
||||
assert_eq!(probe_b.license.as_deref(), Some("GPL-3.0+"));
|
||||
|
||||
// A directory without project markers probes to nothing (the
|
||||
// questions fall back to their plain defaults).
|
||||
let dir_c = tempdir().unwrap();
|
||||
let (detection_c, probe_c) = detect_and_probe(dir_c.path());
|
||||
assert_eq!(detection_c, Detection::Empty);
|
||||
assert!(probe_c.is_none());
|
||||
}
|
||||
|
||||
/// The re-probe skip predicate: the detected/cwd directory never
|
||||
/// re-probes — lexically identical paths and spellings of the same
|
||||
/// directory (`.`, a trailing slash) all compare equal, different
|
||||
/// directories and non-existent paths do not.
|
||||
#[test]
|
||||
fn same_directory_compares_spellings_of_one_directory() {
|
||||
use tempfile::tempdir;
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
let other = tempdir().unwrap();
|
||||
assert!(same_directory(dir.path(), dir.path()));
|
||||
|
||||
// `.` names the current directory.
|
||||
assert!(same_directory(
|
||||
std::path::Path::new("."),
|
||||
&std::env::current_dir().unwrap()
|
||||
));
|
||||
|
||||
assert!(!same_directory(dir.path(), other.path()));
|
||||
// A path that cannot be canonicalized only ever equals itself.
|
||||
assert!(!same_directory(dir.path(), &dir.path().join("missing")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user