new: let an explicit --lang win over build-system detection
The wizard overwrote cli.lang with the detected ecosystem even when the user passed --lang, and re-asked the language question in the ambiguous and skeleton cases despite the documented 'flag > detected > default' merge order. The flag now short-circuits the language step entirely (detection stays informational); behavior without the flag is unchanged.
This commit is contained in:
+123
-20
@@ -122,10 +122,30 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
|||||||
cli.name = Some(answer);
|
cli.name = Some(answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Language / build system.
|
// 2. Language / build system. An explicit `--lang` wins over any
|
||||||
let mut preselected: Option<TemplateId> = None;
|
// detection (flag > detected/probe > default): it is never
|
||||||
match &detection {
|
// overwritten and the question is never re-asked — the detection is
|
||||||
Detection::Single(id) if detection_decides => {
|
// only logged for information.
|
||||||
|
match language_choice(cli.lang.as_deref(), &detection, detection_decides) {
|
||||||
|
LanguageChoice::Flag => match &detection {
|
||||||
|
Detection::Single(id) => log::info!(
|
||||||
|
"Detected: {} project in {}; --lang takes precedence",
|
||||||
|
id.display_name(),
|
||||||
|
detect_dir.display()
|
||||||
|
),
|
||||||
|
Detection::Ambiguous(candidates) => log::info!(
|
||||||
|
"Several build systems found in {} ({}); --lang takes \
|
||||||
|
precedence",
|
||||||
|
detect_dir.display(),
|
||||||
|
candidates
|
||||||
|
.iter()
|
||||||
|
.map(|id| id.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
),
|
||||||
|
Detection::Empty => {}
|
||||||
|
},
|
||||||
|
LanguageChoice::Detected(id) => {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Detected: {} project in {}",
|
"Detected: {} project in {}",
|
||||||
id.display_name(),
|
id.display_name(),
|
||||||
@@ -133,12 +153,16 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
|||||||
);
|
);
|
||||||
cli.lang = Some(id.as_str().to_string());
|
cli.lang = Some(id.as_str().to_string());
|
||||||
}
|
}
|
||||||
Detection::Single(id) => {
|
LanguageChoice::Ask(preselected) => {
|
||||||
// A skeleton was asked for: still ask, preselecting the
|
let menu = language_menu(&[]);
|
||||||
// detected ecosystem.
|
let default = preselected
|
||||||
preselected = Some(*id);
|
.unwrap_or(TemplateId::Empty)
|
||||||
|
.display_name()
|
||||||
|
.to_string();
|
||||||
|
let id = select_template(&menu, &default)?;
|
||||||
|
cli.lang = Some(id.as_str().to_string());
|
||||||
}
|
}
|
||||||
Detection::Ambiguous(candidates) => {
|
LanguageChoice::Ambiguous(candidates) => {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Several build systems found in {} ({}): candidates listed \
|
"Several build systems found in {} ({}): candidates listed \
|
||||||
first, the highest-precedence one preselected",
|
first, the highest-precedence one preselected",
|
||||||
@@ -149,20 +173,10 @@ async fn run_wizard(mut cli: NewCli) -> Result<NewOptions, Box<dyn Error>> {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(", ")
|
.join(", ")
|
||||||
);
|
);
|
||||||
let menu = language_menu(candidates);
|
let menu = language_menu(&candidates);
|
||||||
let id = select_template(&menu, &menu[0])?;
|
let id = select_template(&menu, &menu[0])?;
|
||||||
cli.lang = Some(id.as_str().to_string());
|
cli.lang = Some(id.as_str().to_string());
|
||||||
}
|
}
|
||||||
Detection::Empty => {}
|
|
||||||
}
|
|
||||||
if cli.lang.is_none() {
|
|
||||||
let menu = language_menu(&[]);
|
|
||||||
let default = preselected
|
|
||||||
.unwrap_or(TemplateId::Empty)
|
|
||||||
.display_name()
|
|
||||||
.to_string();
|
|
||||||
let id = select_template(&menu, &default)?;
|
|
||||||
cli.lang = Some(id.as_str().to_string());
|
|
||||||
}
|
}
|
||||||
let template = TemplateId::parse(cli.lang.as_deref().unwrap_or_default())?;
|
let template = TemplateId::parse(cli.lang.as_deref().unwrap_or_default())?;
|
||||||
|
|
||||||
@@ -690,6 +704,42 @@ fn language_menu(candidates: &[TemplateId]) -> Vec<String> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What the language step does with the detection result: how an explicit
|
||||||
|
/// `--lang` flag combines with it (flag > detected/probe > default).
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
enum LanguageChoice {
|
||||||
|
/// An explicit `--lang` wins: it is kept as-is, the question is never
|
||||||
|
/// re-asked and the detection stays informational.
|
||||||
|
Flag,
|
||||||
|
/// No flag, confident detection, packaging the detected directory: the
|
||||||
|
/// detection decides.
|
||||||
|
Detected(TemplateId),
|
||||||
|
/// Ask the question, preselecting the detected ecosystem (a skeleton
|
||||||
|
/// was asked for); `None` when nothing was detected.
|
||||||
|
Ask(Option<TemplateId>),
|
||||||
|
/// Ask the question with the ambiguous candidates listed first (the
|
||||||
|
/// highest-precedence one preselected).
|
||||||
|
Ambiguous(Vec<TemplateId>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The language step's decision for one wizard run. With the flag absent
|
||||||
|
/// this mirrors the historical behavior exactly; the flag always wins.
|
||||||
|
fn language_choice(
|
||||||
|
flag: Option<&str>,
|
||||||
|
detection: &Detection,
|
||||||
|
detection_decides: bool,
|
||||||
|
) -> LanguageChoice {
|
||||||
|
if flag.is_some() {
|
||||||
|
return LanguageChoice::Flag;
|
||||||
|
}
|
||||||
|
match detection {
|
||||||
|
Detection::Single(id) if detection_decides => LanguageChoice::Detected(*id),
|
||||||
|
Detection::Single(id) => LanguageChoice::Ask(Some(*id)),
|
||||||
|
Detection::Ambiguous(candidates) => LanguageChoice::Ambiguous(candidates.clone()),
|
||||||
|
Detection::Empty => LanguageChoice::Ask(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The license menu: the curated SPDX list plus the free-text entry.
|
/// The license menu: the curated SPDX list plus the free-text entry.
|
||||||
fn license_menu() -> Vec<String> {
|
fn license_menu() -> Vec<String> {
|
||||||
KNOWN_LICENSES
|
KNOWN_LICENSES
|
||||||
@@ -1043,6 +1093,59 @@ mod tests {
|
|||||||
assert_eq!(unique.len(), menu.len());
|
assert_eq!(unique.len(), menu.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An explicit `--lang` wins over any detection (the flag outranks the
|
||||||
|
/// detection and the defaults): the flag is kept and the question is
|
||||||
|
/// never re-asked, whatever the detection found (regression: a
|
||||||
|
/// confident detection used to overwrite the flag and an ambiguous one
|
||||||
|
/// re-asked the question).
|
||||||
|
#[test]
|
||||||
|
fn language_choice_flag_wins_over_detection() {
|
||||||
|
let detections = [
|
||||||
|
Detection::Single(Tid::Rust),
|
||||||
|
Detection::Ambiguous(vec![Tid::Rust, Tid::Python]),
|
||||||
|
Detection::Empty,
|
||||||
|
];
|
||||||
|
for detection in &detections {
|
||||||
|
for decides in [false, true] {
|
||||||
|
assert_eq!(
|
||||||
|
language_choice(Some("python"), detection, decides),
|
||||||
|
LanguageChoice::Flag,
|
||||||
|
"detection {detection:?}, decides {decides}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Without the flag the detection behaves exactly as before: it decides
|
||||||
|
/// for a confident detection (packaging the detected directory), asks
|
||||||
|
/// with a preselection for a skeleton run, lists the candidates first
|
||||||
|
/// when ambiguous, and falls back to the plain menu otherwise.
|
||||||
|
#[test]
|
||||||
|
fn language_choice_without_flag_follows_detection() {
|
||||||
|
assert_eq!(
|
||||||
|
language_choice(None, &Detection::Single(Tid::Rust), true),
|
||||||
|
LanguageChoice::Detected(Tid::Rust)
|
||||||
|
);
|
||||||
|
// Skeleton run: ask, preselecting the detected ecosystem.
|
||||||
|
assert_eq!(
|
||||||
|
language_choice(None, &Detection::Single(Tid::Rust), false),
|
||||||
|
LanguageChoice::Ask(Some(Tid::Rust))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
language_choice(
|
||||||
|
None,
|
||||||
|
&Detection::Ambiguous(vec![Tid::Go, Tid::Python]),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
LanguageChoice::Ambiguous(vec![Tid::Go, Tid::Python])
|
||||||
|
);
|
||||||
|
// Nothing detected: plain menu, the empty template preselected.
|
||||||
|
assert_eq!(
|
||||||
|
language_choice(None, &Detection::Empty, false),
|
||||||
|
LanguageChoice::Ask(None)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn license_menu_and_defaults() {
|
fn license_menu_and_defaults() {
|
||||||
let menu = license_menu();
|
let menu = license_menu();
|
||||||
|
|||||||
Reference in New Issue
Block a user