Files
pkh/src/new/templates/shell.rs
T
vhaudiquet ffac4d6b57 new: port the shell, empty, makefile and go templates to manifests
Move the four templates' static file bodies into .tpl files under
data/templates/<id>/, referenced by their manifests' files: lists —
the shell skeleton script (executable, {command}-named) with its
skeleton-only debian/install mapping, the empty template's stub README,
the makefile hello.c/Makefile skeleton with its skeleton-only install
mapping, and go's go.mod/main.go skeleton (the go directive of go.mod
stays a literal: nothing about it is answer-derived).

The empty template ends up hookless — zero Rust, its registry entry
points at no hooks — and src/new/templates/empty.rs is deleted. The
shell and go hooks shrink to their probes (plus go's {go_import_path}
context value); the makefile hooks keep only the existing-tree hint
probing the packaged Makefile for a phony install: target, since that
heuristic reads the tree and cannot be data.
2026-09-18 14:58:13 +02:00

126 lines
4.7 KiB
Rust

//! The `shell` template: a single interpreted script installed to
//! `/usr/bin` with plain `dh $@` plumbing.
//!
//! Everything except the probe is manifest data
//! (`data/templates/shell/manifest.yml`: the skeleton script, its
//! skeleton-only `debian/install` mapping, the plain `dh $@` plumbing).
//! The logic half here pre-fills the wizard answers from the file name of
//! the single top-level script — the same heuristic [`crate::new::detect`]
//! bases its shell detection on.
use std::path::Path;
use super::{ProbeResult, TemplateHooks};
use crate::new::options;
/// The logic half of the shell template.
pub struct Hooks;
/// The shell template's hooks, registered in the template registry.
pub static HOOKS: Hooks = Hooks;
impl TemplateHooks for Hooks {
/// The file name of the single top-level script (sanitized) pre-fills the
/// package name and command questions.
fn probe(&self, dir: &Path) -> Option<ProbeResult> {
let path = crate::new::detect::single_script(dir)?;
let stem = path.file_stem()?.to_str()?;
let name = options::sanitize_name(stem)?;
Some(ProbeResult {
command: Some(name.clone()),
name: Some(name),
..Default::default()
})
}
}
#[cfg(test)]
mod tests {
use crate::new::options::{License, SourceDir, TemplateId};
use tempfile::tempdir;
fn opts() -> crate::new::options::NewOptions {
crate::new::options::NewOptions {
name: "mytool".into(),
template: TemplateId::SHELL,
source_dir: SourceDir::Skeleton,
upstream_version: "0.1.0".into(),
revision: 1,
summary: "A tool".into(),
long_description: "A tool".into(),
homepage: None,
license: License::Mit,
command: "mytool".into(),
maintainer: ("Jane".into(), "jane@example.com".into()),
dist: "ubuntu".into(),
series: "resolute".into(),
release: false,
depends: Vec::new(),
source_format: crate::new::options::SourceFormat::Quilt,
orig: Some(crate::new::options::OrigOrigin::Snapshot),
git: true,
autopkgtest: false,
pkg_config: false,
watch: None,
}
}
#[test]
fn shell_template_shape() {
let o = opts();
let template = super::super::get(TemplateId::SHELL).unwrap();
assert_eq!(template.architecture(&o), "all");
assert!(template.build_depends(&o).is_empty());
assert!(template.rules_extra(&o).is_empty());
// The skeleton bodies are manifest data now: the executable script
// and its skeleton-only install mapping.
let skeleton = template.skeleton(&o);
assert_eq!(skeleton.len(), 1);
assert_eq!(skeleton[0].path, "mytool.sh");
assert!(skeleton[0].executable);
assert!(skeleton[0].contents.starts_with("#!/bin/sh\n"));
assert!(skeleton[0].contents.contains("echo \"Hello from mytool!\""));
let debian = template.debian(&o);
assert_eq!(debian.len(), 1);
assert_eq!(debian[0].path, "debian/install");
assert_eq!(debian[0].contents, "mytool.sh usr/bin/mytool\n");
// Packaging an existing tree: no install mapping is generated (it
// would reference the non-existent skeleton script).
let existing = crate::new::options::NewOptions {
source_dir: SourceDir::Here,
..opts()
};
assert!(template.debian(&existing).is_empty());
}
#[test]
fn shell_probe_reads_script_file_name() {
let template = super::super::get(TemplateId::SHELL).unwrap();
// The .sh extension is stripped, the stem sanitized into a package
// name and command.
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("My Tool.sh"), "#!/bin/sh\n").unwrap();
let probe = template.probe(dir.path()).expect("probe result");
assert_eq!(probe.name.as_deref(), Some("my-tool"));
assert_eq!(probe.command.as_deref(), Some("my-tool"));
// A shebang file without extension works too.
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("runtool"), "#!/usr/bin/env bash\n").unwrap();
let probe = template.probe(dir.path()).expect("probe result");
assert_eq!(probe.name.as_deref(), Some("runtool"));
// Zero or several scripts: silent None.
let dir = tempdir().unwrap();
assert!(template.probe(dir.path()).is_none());
std::fs::write(dir.path().join("a.sh"), "#!/bin/sh\n").unwrap();
std::fs::write(dir.path().join("b.sh"), "#!/bin/sh\n").unwrap();
assert!(template.probe(dir.path()).is_none());
}
}