new: add interactive wizard and remaining ecosystem templates

This commit is contained in:
2026-09-16 13:49:29 +02:00
parent d044f757e9
commit 9b98f5c7c3
17 changed files with 3930 additions and 99 deletions
+51 -12
View File
@@ -19,9 +19,8 @@ use crate::new::detect::{self, Detection};
/// Build systems / project kinds `pkh new` knows about.
///
/// The identifiers are stable CLI surface: `--lang` accepts every variant,
/// even the ones that have no template implementation yet (those fail at
/// scaffold time with a "not implemented yet" error instead of a parse
/// error, so scripts written today keep working once they land).
/// and every variant has a template implementation registered in
/// [`crate::new::templates`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TemplateId {
/// Rust project (`Cargo.toml`)
@@ -93,10 +92,27 @@ impl TemplateId {
})
}
/// Whether a template implementation (and therefore scaffolding) exists
/// for this id. The remaining templates land with the follow-up work.
pub fn implemented(&self) -> bool {
matches!(self, TemplateId::Shell | TemplateId::Empty)
/// Human-readable menu label of this template, as offered by the wizard
/// language question (and reused in the summary screen).
pub fn display_name(&self) -> &'static str {
match self {
TemplateId::Rust => "Rust (Cargo.toml)",
TemplateId::Python => "Python (pyproject.toml / setup.py)",
TemplateId::Meson => "C/C++ (Meson)",
TemplateId::Cmake => "C/C++ (CMake)",
TemplateId::Autotools => "C/C++ (Autotools)",
TemplateId::Go => "Go module",
TemplateId::Shell => "Shell script / single interpreted file",
TemplateId::Makefile => "Generic (Makefile)",
TemplateId::Empty => "Metapackage / empty base (no build system)",
}
}
/// The template whose menu label (or CLI identifier) is `label`.
pub fn from_label(label: &str) -> Option<TemplateId> {
TemplateId::all()
.into_iter()
.find(|id| id.display_name() == label || id.as_str() == label)
}
}
@@ -230,6 +246,17 @@ pub struct NewOptions {
pub native: bool,
/// Initialize a git repository (gitignores are written regardless).
pub git: bool,
/// Write the autopkgtest smoke test (`debian/tests/control` +
/// `debian/tests/smoke`) running `<command> --help`/`--version`.
/// Wizard-only extra, off by default.
pub autopkgtest: bool,
/// Add `pkg-config` to Build-Depends (the meson/cmake opt-in question;
/// most such projects resolve their dependencies through it).
/// Wizard-only extra, off by default.
pub pkg_config: bool,
/// Contents of a `debian/watch` release watcher (GitHub/GitLab tarball
/// template). Wizard-only extra, off by default.
pub watch: Option<String>,
}
impl NewOptions {
@@ -568,7 +595,8 @@ pub async fn resolve(cli: NewCli) -> Result<NewOptions, String> {
if !missing.is_empty() {
return Err(format!(
"Missing required answers (use --defaults to take every default, \
or answer interactively once the wizard lands):\n - {}",
or re-run pkh new on an interactive terminal to answer the \
wizard):\n - {}",
missing.join("\n - ")
));
}
@@ -647,6 +675,9 @@ pub async fn resolve(cli: NewCli) -> Result<NewOptions, String> {
depends,
native: cli.native,
git: cli.git,
autopkgtest: false,
pkg_config: false,
watch: None,
})
}
@@ -671,12 +702,17 @@ mod tests {
fn template_ids_roundtrip() {
for id in TemplateId::all() {
assert_eq!(TemplateId::parse(id.as_str()).unwrap(), id);
// Every id resolves from its menu label too, and labels are
// unique.
assert_eq!(TemplateId::from_label(id.display_name()), Some(id));
}
assert!(TemplateId::parse("cobol").is_err());
// Every id is accepted by the parser; only some are implemented.
assert!(!TemplateId::Rust.implemented());
assert!(TemplateId::Shell.implemented());
assert!(TemplateId::Empty.implemented());
assert_eq!(
TemplateId::from_label("Rust (Cargo.toml)"),
Some(TemplateId::Rust)
);
assert_eq!(TemplateId::from_label("rust"), Some(TemplateId::Rust));
assert_eq!(TemplateId::from_label("nope"), None);
}
#[test]
@@ -754,6 +790,9 @@ mod tests {
depends: Vec::new(),
native: false,
git: false,
autopkgtest: false,
pkg_config: false,
watch: None,
};
assert_eq!(opts.full_version(), "0.1.0-1");
assert_eq!(