use std::path::Path;
use anyhow::{Context, Result};
use crate::runutil::{ManagedCommand, TimeoutClass, execute_checked_command};
use super::context::{TemplateContext, TemplateWarning};
pub fn detect_context_with_warnings(
target: Option<&str>,
repo_root: &Path,
needs_branch: bool,
) -> (TemplateContext, Vec<TemplateWarning>) {
let mut warnings = Vec::new();
let target_opt = target.map(|s| s.to_string());
let file = target_opt.as_ref().map(|t| {
Path::new(t)
.file_name()
.map(|n| n.to_string_lossy().to_string())
.unwrap_or_else(|| t.clone())
});
let module = target_opt.as_ref().map(|t| derive_module_name(t));
let branch = if needs_branch {
match detect_git_branch(repo_root) {
Ok(branch_opt) => branch_opt,
Err(e) => {
warnings.push(TemplateWarning::GitBranchDetectionFailed {
error: e.to_string(),
});
None
}
}
} else {
None
};
let context = TemplateContext {
target: target_opt,
file,
module,
branch,
};
(context, warnings)
}
pub fn detect_context(target: Option<&str>, repo_root: &Path) -> TemplateContext {
let (context, _) = detect_context_with_warnings(target, repo_root, true);
context
}
pub(super) fn derive_module_name(path: &str) -> String {
let path_obj = Path::new(path);
let file_stem = path_obj
.file_stem()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| path.to_string());
let mut components: Vec<String> = Vec::new();
for component in path_obj.components() {
let comp_str = component.as_os_str().to_string_lossy().to_string();
if comp_str == "src"
|| comp_str == "lib"
|| comp_str == "bin"
|| comp_str == "tests"
|| comp_str == "examples"
|| comp_str == "crates"
{
continue;
}
if comp_str
== path_obj
.file_name()
.map(|n| n.to_string_lossy())
.unwrap_or_default()
{
continue;
}
components.push(comp_str);
}
if !components.is_empty() {
components.push(file_stem);
components.join("::")
} else {
file_stem
}
}
fn detect_git_branch(repo_root: &Path) -> Result<Option<String>> {
let head_path = repo_root.join(".git/HEAD");
if !head_path.exists() {
let mut command = std::process::Command::new("git");
command
.arg("-c")
.arg("core.fsmonitor=false")
.arg("rev-parse")
.arg("--abbrev-ref")
.arg("HEAD")
.current_dir(repo_root);
let output = execute_checked_command(ManagedCommand::new(
command,
format!("git rev-parse --abbrev-ref HEAD in {}", repo_root.display()),
TimeoutClass::MetadataProbe,
))
.context("failed to detect template git branch")?;
let branch = output.stdout_lossy();
if branch != "HEAD" {
return Ok(Some(branch));
}
return Ok(None);
}
let head_content = std::fs::read_to_string(&head_path)
.with_context(|| format!("failed to read {:?}", head_path))?;
let head_ref = head_content.trim();
if head_ref.starts_with("ref: refs/heads/") {
let branch = head_ref
.strip_prefix("ref: refs/heads/")
.unwrap_or(head_ref)
.to_string();
Ok(Some(branch))
} else if head_ref.len() == 40 && head_ref.chars().all(|c| c.is_ascii_hexdigit()) {
Ok(None)
} else if head_ref.is_empty() {
Err(anyhow::anyhow!("HEAD file is empty"))
} else {
Err(anyhow::anyhow!("invalid HEAD content: {}", head_ref))
}
}