new: scaffold new Debian source packages (non-interactive core)

This commit is contained in:
2026-09-16 12:14:09 +02:00
parent 9c3394750d
commit d044f757e9
12 changed files with 3243 additions and 1 deletions
+164
View File
@@ -0,0 +1,164 @@
//! Per-ecosystem template registry for `pkh new`.
//!
//! Every template implements [`Template`]: it renders the upstream-side
//! skeleton files, the extra `debian/` files beyond the common set, and (in
//! the future) probes an existing project for metadata. Rendering is plain
//! `format!` composition — no template engine, matching the codebase style.
pub mod empty;
pub mod shell;
use std::path::Path;
use super::options::{NewOptions, TemplateId};
/// One generated file, rendered in memory before anything touches the disk.
#[derive(Debug, Clone)]
pub struct OutputFile {
/// Path relative to the package tree root (e.g. `debian/control`).
pub path: String,
/// Full file contents.
pub contents: String,
/// Whether the file carries the executable bit (mode 0755).
pub executable: bool,
}
impl OutputFile {
/// A regular (non-executable) file.
pub fn new(path: impl Into<String>, contents: impl Into<String>) -> OutputFile {
OutputFile {
path: path.into(),
contents: contents.into(),
executable: false,
}
}
/// An executable file (mode 0755).
pub fn executable(path: impl Into<String>, contents: impl Into<String>) -> OutputFile {
OutputFile {
executable: true,
..OutputFile::new(path, contents)
}
}
}
/// Metadata extracted from an existing project by [`Template::probe`], used
/// by the interactive wizard to pre-fill its answers (explicit flags always
/// win). The per-template extraction is follow-up work; the hook already
/// exists so templates can grow it independently.
#[derive(Debug, Clone, Default)]
pub struct ProbeResult {
/// Project name (e.g. the `name` key of `Cargo.toml`).
pub name: Option<String>,
/// Project version.
pub version: Option<String>,
/// Project description.
pub description: Option<String>,
/// Project homepage.
pub homepage: Option<String>,
/// Project license (SPDX identifier).
pub license: Option<String>,
}
/// A package template: one supported ecosystem / build system.
///
/// `Sync` is required so templates can live in the static registry.
pub trait Template: Sync {
/// Identifier of this template.
fn id(&self) -> TemplateId;
/// Upstream-side files for the skeleton mode (e.g. `Cargo.toml`,
/// `src/main.rs`). Only called when packaging a fresh skeleton.
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile>;
/// Extra `debian/` files beyond the common set rendered by
/// [`super::debian`] (e.g. `debian/install`).
fn debian(&self, opts: &NewOptions) -> Vec<OutputFile>;
/// Build-Depends beyond `debhelper-compat (= 13)`.
fn build_depends(&self, _opts: &NewOptions) -> Vec<String> {
Vec::new()
}
/// Architecture of the binary package (`all` or `any`).
fn architecture(&self) -> &'static str {
"all"
}
/// Lines appended to `debian/rules` after the default `dh $@` stanza.
fn rules_extra(&self) -> String {
String::new()
}
/// Extra defaults derived from project metadata in `dir` (detect.rs);
/// `None` when the project carries nothing this template can read.
fn probe(&self, _dir: &Path) -> Option<ProbeResult> {
None
}
}
/// Static instance of the shell template.
pub static SHELL: shell::Shell = shell::Shell;
/// Static instance of the empty/metapackage template.
pub static EMPTY: empty::Empty = empty::Empty;
/// Every implemented template (the wizard language menu lists
/// [`TemplateId::all()`] and greys the rest out).
static TEMPLATES: &[&dyn Template] = &[&SHELL, &EMPTY];
/// Look up the template implementation for `id`; `None` for the ids whose
/// template is not implemented yet (callers turn this into the friendly
/// "not implemented yet" error).
pub fn get(id: TemplateId) -> Option<&'static dyn Template> {
match id {
TemplateId::Shell => Some(&SHELL),
TemplateId::Empty => Some(&EMPTY),
TemplateId::Rust
| TemplateId::Python
| TemplateId::Meson
| TemplateId::Cmake
| TemplateId::Autotools
| TemplateId::Go
| TemplateId::Makefile => None,
}
}
/// Every implemented template.
pub fn all() -> &'static [&'static dyn Template] {
TEMPLATES
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_covers_implemented_templates() {
for id in [TemplateId::Shell, TemplateId::Empty] {
assert!(get(id).is_some(), "{id} must be registered");
assert_eq!(get(id).unwrap().id(), id);
}
for id in [
TemplateId::Rust,
TemplateId::Python,
TemplateId::Meson,
TemplateId::Cmake,
TemplateId::Autotools,
TemplateId::Go,
TemplateId::Makefile,
] {
assert!(get(id).is_none(), "{id} must not pretend to be implemented");
}
assert_eq!(all().len(), 2);
}
#[test]
fn probe_defaults_to_none() {
assert!(
get(TemplateId::Shell)
.unwrap()
.probe(Path::new("/"))
.is_none()
);
}
}