Files
pkh/src/new/templates/go.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

161 lines
5.4 KiB
Rust

//! The `go` template: a Go module built through dh-golang.
//!
//! The skeleton bodies (`go.mod`, `main.go`) are manifest data
//! (`data/templates/go/manifest.yml`); the source stanza carries
//! `XS-Go-Import-Path`, declared as the `{go_import_path}` placeholder by
//! the manifest and filled here from the `module` line of `go.mod` when
//! the packaged tree has one, defaulting to the package name (fresh
//! skeletons embed the package name in their own `go.mod`). The module
//! line is also the probe of an existing project.
use std::path::Path;
use super::{ProbeResult, TemplateHooks, source_dir_of};
use crate::new::options::NewOptions;
/// The logic half of the go template.
pub struct Hooks;
/// The go template's hooks, registered in the template registry.
pub static HOOKS: Hooks = Hooks;
impl TemplateHooks for Hooks {
/// The `{go_import_path}` value of the manifest's `XS-Go-Import-Path`
/// source field (see [`import_path`]).
fn context(&self, opts: &NewOptions) -> Vec<(String, String)> {
vec![("go_import_path".to_string(), import_path(opts))]
}
/// Name (and default command) from the `module` line of `go.mod`: the
/// last path segment is the conventional binary/package name.
fn probe(&self, dir: &Path) -> Option<ProbeResult> {
let module = read_module_line(dir)?;
let name = module.rsplit('/').next()?.to_string();
if name.is_empty() {
return None;
}
Some(ProbeResult {
name: Some(name.clone()),
command: Some(name),
..Default::default()
})
}
}
/// The `XS-Go-Import-Path` value: probed from `go.mod` when the packaged
/// tree has one, the package name otherwise (skeleton mode embeds the name
/// in the generated `go.mod` anyway).
fn import_path(opts: &NewOptions) -> String {
source_dir_of(opts)
.as_deref()
.and_then(read_module_line)
.unwrap_or_else(|| opts.name.clone())
}
/// The `module <path>` line of `dir/go.mod`, when present.
fn read_module_line(dir: &Path) -> Option<String> {
let content = std::fs::read_to_string(dir.join("go.mod")).ok()?;
for line in content.lines() {
let line = line.trim();
if let Some(rest) = line.strip_prefix("module ") {
let module = rest.trim();
if !module.is_empty() {
return Some(module.to_string());
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::new::options::{License, SourceDir, TemplateId};
use tempfile::tempdir;
fn opts(source_dir: SourceDir) -> NewOptions {
NewOptions {
name: "mytool".into(),
template: TemplateId::GO,
source_dir,
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 go_template_shape() {
let o = opts(SourceDir::Skeleton);
let template = super::super::get(TemplateId::GO).unwrap();
assert_eq!(template.architecture(&o), "any");
assert_eq!(
template.build_depends(&o),
vec!["golang-any".to_string(), "dh-golang".to_string()]
);
assert_eq!(template.rules_dh_line(), "dh $@ --buildsystem=golang");
assert!(template.debian(&o).is_empty());
// The skeleton bodies are manifest data now.
let skeleton = template.skeleton(&o);
assert!(
skeleton
.iter()
.any(|f| f.path == "go.mod" && f.contents.starts_with("module mytool\n"))
);
assert!(skeleton.iter().any(|f| f.path == "main.go"));
// Skeleton mode: no go.mod to probe, the import path is the name.
assert_eq!(
template.source_fields(&o),
vec![("XS-Go-Import-Path".to_string(), "mytool".to_string())]
);
}
#[test]
fn go_probe_reads_module_line() {
let dir = tempdir().unwrap();
std::fs::write(
dir.path().join("go.mod"),
"module example.com/org/mytool\n\ngo 1.21\n",
)
.unwrap();
let template = super::super::get(TemplateId::GO).unwrap();
let probe = template.probe(dir.path()).expect("probe result");
assert_eq!(probe.name.as_deref(), Some("mytool"));
assert_eq!(probe.command.as_deref(), Some("mytool"));
// The probed module line wins over the package name for the import
// path when packaging an existing tree.
let o = opts(SourceDir::Path(dir.path().to_path_buf()));
assert_eq!(
template.source_fields(&o),
vec![(
"XS-Go-Import-Path".to_string(),
"example.com/org/mytool".to_string()
)]
);
// No go.mod: no probe result.
let empty = tempdir().unwrap();
assert!(template.probe(empty.path()).is_none());
}
}