new: scaffold new Debian source packages (non-interactive core)
This commit is contained in:
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user