use std::fs;
use std::path::PathBuf;
use crate::cli::ToolNewArgs;
use crate::commands::scaffold_common::harn_identifier_with_prefix;
use crate::dispatch;
use crate::env_guard::ScopedEnvVar;
use crate::package::{
current_harn_range_example, generate_package_docs_impl, validate_package_alias, PackageError,
};
pub(crate) async fn run_new(args: &ToolNewArgs) -> Result<(), PackageError> {
validate_package_alias(&args.name)?;
let dest = args
.dir
.as_deref()
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from(&args.name));
if dest.exists() {
if !args.force {
return Err(format!(
"{} already exists. Pass --force to overwrite.",
dest.display()
)
.into());
}
if !dest.is_dir() {
return Err(format!("{} exists and is not a directory.", dest.display()).into());
}
} else {
fs::create_dir_all(&dest)
.map_err(|error| format!("failed to create {}: {error}", dest.display()))?;
}
let description = args
.description
.clone()
.unwrap_or_else(|| format!("Custom Harn tool package for {}.", args.name));
if description.contains('\n') || description.contains('\r') {
return Err("tool description must be a single line".to_string().into());
}
let ident = harn_identifier(&args.name)?;
let handler = format!("handle_{ident}");
dispatch_to_script(&args.name, &dest, &ident, &handler, &description).await?;
generate_package_docs_impl(Some(&dest), None, false)?;
println!(
"Scaffolded tool package '{}' at {}",
args.name,
dest.display()
);
println!(" harn test tests/");
println!(" harn package check");
println!(" harn package docs --check");
println!(" harn package pack --dry-run");
Ok(())
}
async fn dispatch_to_script(
name: &str,
dest: &std::path::Path,
ident: &str,
handler: &str,
description: &str,
) -> Result<(), PackageError> {
let dest_str = dest.display().to_string();
let harn_range = current_harn_range_example();
let _name_env = ScopedEnvVar::set("HARN_TOOL_NAME", name);
let _dest_env = ScopedEnvVar::set("HARN_TOOL_DEST", &dest_str);
let _ident_env = ScopedEnvVar::set("HARN_TOOL_IDENT", ident);
let _handler_env = ScopedEnvVar::set("HARN_TOOL_HANDLER", handler);
let _desc_env = ScopedEnvVar::set("HARN_TOOL_DESCRIPTION", description);
let _range_env = ScopedEnvVar::set("HARN_TOOL_HARN_RANGE", &harn_range);
let exit = dispatch::dispatch_to_embedded_script(
"scaffold/tool_new",
Vec::new(),
false,
)
.await;
if exit != 0 {
return Err(format!("tool new scaffolder exited with code {exit}").into());
}
Ok(())
}
fn harn_identifier(name: &str) -> Result<String, PackageError> {
harn_identifier_with_prefix(name, "tool")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn harn_identifier_normalizes_package_names() {
assert_eq!(harn_identifier("acme-tool").unwrap(), "acme_tool");
assert_eq!(harn_identifier("123-tool").unwrap(), "tool_123_tool");
}
}