//! 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 { 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()); } }