Files
pkh/src/new/templates/go.rs
T

203 lines
6.3 KiB
Rust

//! The `go` template: a Go module built through dh-golang.
//!
//! The source stanza carries `XS-Go-Import-Path`, probed 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`).
use std::path::Path;
use super::{OutputFile, Template, source_dir_of};
use crate::new::options::{NewOptions, TemplateId};
/// Go module (`go.mod`).
pub struct Go;
impl Template for Go {
fn id(&self) -> TemplateId {
TemplateId::Go
}
/// A stdlib-only `main.go` (no archive dependencies needed to build) and
/// the matching `go.mod` whose module path is the package name.
fn skeleton(&self, opts: &NewOptions) -> Vec<OutputFile> {
vec![
OutputFile::new(
"go.mod",
format!(
"module {name}\n\
\n\
go 1.21\n",
name = opts.name,
),
),
OutputFile::new(
"main.go",
format!(
"// Placeholder for {name}, generated by `pkh new`.\n\
package main\n\
\n\
import \"fmt\"\n\
\n\
func main() {{\n\
\tfmt.Println(\"Hello from {command}!\")\n\
}}\n",
name = opts.name,
command = opts.command,
),
),
]
}
/// No extra debian/ files: dh-golang drives the build.
fn debian(&self, _opts: &NewOptions) -> Vec<OutputFile> {
Vec::new()
}
fn build_depends(&self, _opts: &NewOptions) -> Vec<String> {
vec!["golang-any".to_string(), "dh-golang".to_string()]
}
fn architecture(&self, _opts: &NewOptions) -> &'static str {
"any"
}
fn rules_dh_line(&self) -> String {
"dh $@ --buildsystem=golang".to_string()
}
fn source_fields(&self, opts: &NewOptions) -> Vec<(String, String)> {
vec![("XS-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<super::ProbeResult> {
let module = read_module_line(dir)?;
let name = module.rsplit('/').next()?.to_string();
if name.is_empty() {
return None;
}
Some(super::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};
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(),
native: false,
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());
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());
}
}