new: template manifests and registry infrastructure
Split the Template trait into a data half and a logic half. Every
template is now declared by a manifest under data/templates/<id>/
(CLI id, wizard label, detection markers, Build-Depends, architecture,
rules dh line, rules-extra body, control source fields, gitignore
entries and static file bodies with {placeholder} substitution),
embedded through the TEMPLATE_SOURCES index and parsed once into the
registry; the order of the index is the wizard menu order and the
detection priority at once. The logic half is the slim TemplateHooks
trait (probe, post_write, file-body overrides merged over the manifest
bodies by path shadowing, Build-Depends/architecture amendments and
extra context values), registered per template as a HOOKS static: a
template without hooks needs zero Rust.
- TemplateId becomes a Copy wrapper of the stable CLI string; the
enum, its all/as_str/display_name/from_label matches and the old
statics array collapse into the registry accessors.
- rust's rules overrides move to data/templates/rust/rules.extra.tpl
with {locked}/{artifact} hook context; python's backend table,
meson/cmake's pkg-config opt-in, autotools' gettext and python's
C-extension hints become hook amendments over the manifest baseline.
- detect.rs drops its hardcoded marker cascade: the manifests'
detect.files drive detection in registry order, with the shell
single-script heuristic and the never-detected empty template kept
as the code special cases they are. License sniffing is untouched.
- The template tests port to manifest validation: registry coverage
and stable order, placeholder presence in the rendering context,
rules composition, the Build-Depends/architecture/dh-line table now
asserted against the manifest data, and the hook shadowing merge.
The static skeleton bodies of the shell/empty/makefile/go templates
stay in their Rust hooks for now; the next commit moves them into
their manifests.
This commit is contained in:
+35
-39
@@ -4,13 +4,16 @@
|
||||
//! The rule set is deliberately simple and table-driven (highest precedence
|
||||
//! first):
|
||||
//!
|
||||
//! 1. well-known build-system marker files at the top level of the
|
||||
//! directory (`Cargo.toml`, `pyproject.toml`/`setup.py`/`setup.cfg`,
|
||||
//! `meson.build`, `CMakeLists.txt`, `configure.ac`, `go.mod`,
|
||||
//! `Makefile`) — more than one distinct template matching is
|
||||
//! [`Detection::Ambiguous`],
|
||||
//! 1. the `detect.files` marker files declared by the template manifests
|
||||
//! (`data/templates/<id>/manifest.yml`, in registry order: `Cargo.toml`,
|
||||
//! `pyproject.toml`/`setup.py`/`setup.cfg`, `meson.build`,
|
||||
//! `CMakeLists.txt`, `configure.ac`, `go.mod`, `Makefile`) looked for at
|
||||
//! the top level of the directory — more than one distinct template
|
||||
//! matching is [`Detection::Ambiguous`]; templates without markers
|
||||
//! (shell: the single-script heuristic below; empty: never detected)
|
||||
//! declare none,
|
||||
//! 2. otherwise, exactly one top-level script (a `*.sh` file, or a file
|
||||
//! whose first line is a `#!` shebang) → [`TemplateId::Shell`],
|
||||
//! whose first line is a `#!` shebang) → [`TemplateId::SHELL`],
|
||||
//! several scripts or none → nothing,
|
||||
//! 3. otherwise [`Detection::Empty`].
|
||||
//!
|
||||
@@ -25,6 +28,7 @@ use regex::Regex;
|
||||
|
||||
use super::licenses;
|
||||
use super::options::TemplateId;
|
||||
use super::templates;
|
||||
|
||||
/// Outcome of the detection.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -38,26 +42,18 @@ pub enum Detection {
|
||||
Empty,
|
||||
}
|
||||
|
||||
/// Marker files per template, in precedence order (see the module docs).
|
||||
const MARKERS: [(TemplateId, &[&str]); 7] = [
|
||||
(TemplateId::Rust, &["Cargo.toml"]),
|
||||
(
|
||||
TemplateId::Python,
|
||||
&["pyproject.toml", "setup.py", "setup.cfg"],
|
||||
),
|
||||
(TemplateId::Meson, &["meson.build"]),
|
||||
(TemplateId::Cmake, &["CMakeLists.txt"]),
|
||||
(TemplateId::Autotools, &["configure.ac"]),
|
||||
(TemplateId::Go, &["go.mod"]),
|
||||
(TemplateId::Makefile, &["Makefile"]),
|
||||
];
|
||||
|
||||
/// Detect the template matching the project in `dir`.
|
||||
/// Detect the template matching the project in `dir`: the manifests'
|
||||
/// marker files in registry order (the detection priority), then the
|
||||
/// shell single-script heuristic.
|
||||
pub fn detect(dir: &Path) -> Detection {
|
||||
let mut hits: Vec<TemplateId> = Vec::new();
|
||||
for (id, markers) in MARKERS {
|
||||
if markers.iter().any(|marker| dir.join(marker).exists()) && !hits.contains(&id) {
|
||||
hits.push(id);
|
||||
for template in templates::all() {
|
||||
let markers = template.detect_files();
|
||||
if !markers.is_empty()
|
||||
&& markers.iter().any(|marker| dir.join(marker).exists())
|
||||
&& !hits.contains(&template.id())
|
||||
{
|
||||
hits.push(template.id());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +64,7 @@ pub fn detect(dir: &Path) -> Detection {
|
||||
}
|
||||
|
||||
if single_script(dir).is_some() {
|
||||
Detection::Single(TemplateId::Shell)
|
||||
Detection::Single(TemplateId::SHELL)
|
||||
} else {
|
||||
Detection::Empty
|
||||
}
|
||||
@@ -176,15 +172,15 @@ mod tests {
|
||||
#[test]
|
||||
fn marker_files_map_to_templates() {
|
||||
let cases = [
|
||||
("Cargo.toml", TemplateId::Rust),
|
||||
("pyproject.toml", TemplateId::Python),
|
||||
("setup.py", TemplateId::Python),
|
||||
("setup.cfg", TemplateId::Python),
|
||||
("meson.build", TemplateId::Meson),
|
||||
("CMakeLists.txt", TemplateId::Cmake),
|
||||
("configure.ac", TemplateId::Autotools),
|
||||
("go.mod", TemplateId::Go),
|
||||
("Makefile", TemplateId::Makefile),
|
||||
("Cargo.toml", TemplateId::RUST),
|
||||
("pyproject.toml", TemplateId::PYTHON),
|
||||
("setup.py", TemplateId::PYTHON),
|
||||
("setup.cfg", TemplateId::PYTHON),
|
||||
("meson.build", TemplateId::MESON),
|
||||
("CMakeLists.txt", TemplateId::CMAKE),
|
||||
("configure.ac", TemplateId::AUTOTOOLS),
|
||||
("go.mod", TemplateId::GO),
|
||||
("Makefile", TemplateId::MAKEFILE),
|
||||
];
|
||||
for (marker, expected) in cases {
|
||||
let dir = tempdir().unwrap();
|
||||
@@ -200,21 +196,21 @@ mod tests {
|
||||
touch(dir.path(), "Makefile");
|
||||
assert_eq!(
|
||||
detect(dir.path()),
|
||||
Detection::Ambiguous(vec![TemplateId::Rust, TemplateId::Makefile])
|
||||
Detection::Ambiguous(vec![TemplateId::RUST, TemplateId::MAKEFILE])
|
||||
);
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
touch(dir.path(), "pyproject.toml");
|
||||
touch(dir.path(), "setup.py");
|
||||
// Both markers map to the same template: one hit, not ambiguous.
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::Python));
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::PYTHON));
|
||||
|
||||
let dir = tempdir().unwrap();
|
||||
touch(dir.path(), "meson.build");
|
||||
touch(dir.path(), "CMakeLists.txt");
|
||||
assert_eq!(
|
||||
detect(dir.path()),
|
||||
Detection::Ambiguous(vec![TemplateId::Meson, TemplateId::Cmake])
|
||||
Detection::Ambiguous(vec![TemplateId::MESON, TemplateId::CMAKE])
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,12 +219,12 @@ mod tests {
|
||||
// .sh extension.
|
||||
let dir = tempdir().unwrap();
|
||||
touch(dir.path(), "run.sh");
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::Shell));
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::SHELL));
|
||||
|
||||
// Shebang without extension.
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("run"), "#!/usr/bin/env python3\n").unwrap();
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::Shell));
|
||||
assert_eq!(detect(dir.path()), Detection::Single(TemplateId::SHELL));
|
||||
|
||||
// Two scripts: not exactly one, nothing recognized.
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user