use crate::models::{Error, Result};
use crate::Transpiler;
use std::fs;
use std::path::{Path, PathBuf};
pub fn auto_transpile<P: AsRef<Path>>(
input_dir: P,
output_dir: P,
permissions: u32,
) -> Result<usize> {
let input_path = input_dir.as_ref();
let output_path = output_dir.as_ref();
if !input_path.exists() {
return Err(Error::ValidationError(format!(
"Input directory does not exist: {}",
input_path.display()
)));
}
let jobs = discover_sources(input_path, "**/*.rs")?;
let count = jobs.len();
for job in jobs {
println!("cargo:rerun-if-changed={}", job.input.display());
let relative = job
.input
.strip_prefix(input_path)
.map_err(|e| Error::Internal(format!("Failed to compute relative path: {}", e)))?;
let output_file = output_path.join(relative).with_extension("sh");
Transpiler::new()
.input(&job.input)
.output(&output_file)
.permissions(permissions)
.config(job.config)
.transpile()?;
}
Ok(count)
}
pub fn discover_sources<P: AsRef<Path>>(input_dir: P, pattern: &str) -> Result<Vec<TranspileJob>> {
let input_path = input_dir.as_ref();
if !input_path.exists() {
return Err(Error::ValidationError(format!(
"Input directory does not exist: {}",
input_path.display()
)));
}
let mut jobs = Vec::new();
walk_dir(input_path, &mut |path| {
if path.extension().and_then(|s| s.to_str()) == Some("rs") {
if pattern.ends_with("*.rs") || pattern.contains("**/*.rs") {
jobs.push(TranspileJob {
input: path.to_path_buf(),
config: crate::Config::default(),
});
}
}
})?;
Ok(jobs)
}
#[derive(Debug, Clone)]
pub struct TranspileJob {
pub input: PathBuf,
pub config: crate::Config,
}
impl TranspileJob {
pub fn transpile_to<P: AsRef<Path>>(&self, output: P, permissions: u32) -> Result<()> {
Transpiler::new()
.input(&self.input)
.output(output.as_ref())
.permissions(permissions)
.config(self.config.clone())
.transpile()
}
pub fn transpile(&self) -> Result<()> {
let output = self.input.with_extension("sh");
self.transpile_to(output, 0o755)
}
}
fn walk_dir<P: AsRef<Path>, F>(dir: P, callback: &mut F) -> Result<()>
where
F: FnMut(&Path),
{
let dir_path = dir.as_ref();
if !dir_path.is_dir() {
return Ok(());
}
let entries = fs::read_dir(dir_path).map_err(|e| {
Error::Io(std::io::Error::new(
e.kind(),
format!("Failed to read directory {}: {}", dir_path.display(), e),
))
})?;
for entry in entries {
let entry = entry.map_err(Error::Io)?;
let path = entry.path();
if path.is_dir() {
walk_dir(&path, callback)?;
} else if path.is_file() {
callback(&path);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_XTASK_002_discover_sources_finds_rust_files() {
let temp_dir = TempDir::new().unwrap();
let src_dir = temp_dir.path().join("src");
fs::create_dir(&src_dir).unwrap();
fs::write(src_dir.join("main.rs"), "fn main() {}").unwrap();
fs::write(src_dir.join("lib.rs"), "pub fn hello() {}").unwrap();
fs::write(src_dir.join("README.md"), "# Docs").unwrap();
let jobs = discover_sources(&src_dir, "**/*.rs").unwrap();
assert_eq!(jobs.len(), 2);
assert!(jobs.iter().any(|j| j.input.ends_with("main.rs")));
assert!(jobs.iter().any(|j| j.input.ends_with("lib.rs")));
}
#[test]
fn test_XTASK_002_discover_sources_recursive() {
let temp_dir = TempDir::new().unwrap();
let base = temp_dir.path();
fs::create_dir_all(base.join("hooks/pre")).unwrap();
fs::write(base.join("hooks/pre-commit.rs"), "fn main() {}").unwrap();
fs::write(base.join("hooks/pre/push.rs"), "fn main() {}").unwrap();
let jobs = discover_sources(base.join("hooks"), "**/*.rs").unwrap();
assert_eq!(jobs.len(), 2);
}
#[test]
fn test_XTASK_002_discover_sources_empty_directory() {
let temp_dir = TempDir::new().unwrap();
let jobs = discover_sources(temp_dir.path(), "**/*.rs").unwrap();
assert_eq!(jobs.len(), 0);
}
#[test]
fn test_XTASK_002_discover_sources_nonexistent_directory() {
let result = discover_sources("/nonexistent/path/12345", "**/*.rs");
assert!(result.is_err());
match result {
Err(Error::ValidationError(msg)) => {
assert!(msg.contains("does not exist"));
}
_ => panic!("Expected ValidationError"),
}
}
#[test]
fn test_XTASK_002_transpile_job_basic() {
let temp_dir = TempDir::new().unwrap();
let input = temp_dir.path().join("input.rs");
let output = temp_dir.path().join("output.sh");
fs::write(&input, "fn main() { let x = 1; }").unwrap();
let job = TranspileJob {
input: input.clone(),
config: crate::Config::default(),
};
let result = job.transpile_to(&output, 0o755);
assert!(result.is_ok());
assert!(output.exists());
}
#[test]
fn test_XTASK_002_auto_transpile_basic() {
let temp_dir = TempDir::new().unwrap();
let input_dir = temp_dir.path().join("src");
let output_dir = temp_dir.path().join("dist");
fs::create_dir(&input_dir).unwrap();
fs::write(input_dir.join("install.rs"), "fn main() {}").unwrap();
let count = auto_transpile(&input_dir, &output_dir, 0o755).unwrap();
assert_eq!(count, 1);
assert!(output_dir.join("install.sh").exists());
}
#[test]
fn test_XTASK_002_auto_transpile_preserves_structure() {
let temp_dir = TempDir::new().unwrap();
let input_dir = temp_dir.path().join("hooks");
let output_dir = temp_dir.path().join(".git/hooks");
fs::create_dir_all(input_dir.join("pre")).unwrap();
fs::write(input_dir.join("pre-commit.rs"), "fn main() {}").unwrap();
fs::write(input_dir.join("pre/push.rs"), "fn main() {}").unwrap();
let count = auto_transpile(&input_dir, &output_dir, 0o755).unwrap();
assert_eq!(count, 2);
assert!(output_dir.join("pre-commit.sh").exists());
assert!(output_dir.join("pre/push.sh").exists());
}
#[test]
#[cfg(unix)]
fn test_XTASK_002_auto_transpile_sets_permissions() {
use std::os::unix::fs::PermissionsExt;
let temp_dir = TempDir::new().unwrap();
let input_dir = temp_dir.path().join("src");
let output_dir = temp_dir.path().join("dist");
fs::create_dir(&input_dir).unwrap();
fs::write(input_dir.join("script.rs"), "fn main() {}").unwrap();
auto_transpile(&input_dir, &output_dir, 0o755).unwrap();
let metadata = fs::metadata(output_dir.join("script.sh")).unwrap();
let mode = metadata.permissions().mode() & 0o777;
assert_eq!(mode, 0o755);
}
#[test]
fn test_XTASK_002_auto_transpile_nonexistent_input() {
let result = auto_transpile("/nonexistent/12345", "/tmp/output", 0o755);
assert!(result.is_err());
match result {
Err(Error::ValidationError(msg)) => {
assert!(msg.contains("does not exist"));
}
_ => panic!("Expected ValidationError"),
}
}
#[test]
fn test_XTASK_002_walk_dir_basic() {
let temp_dir = TempDir::new().unwrap();
fs::write(temp_dir.path().join("file1.txt"), "test").unwrap();
fs::write(temp_dir.path().join("file2.txt"), "test").unwrap();
let mut count = 0;
walk_dir(temp_dir.path(), &mut |_| {
count += 1;
})
.unwrap();
assert_eq!(count, 2);
}
#[test]
fn test_XTASK_002_walk_dir_recursive() {
let temp_dir = TempDir::new().unwrap();
fs::create_dir_all(temp_dir.path().join("a/b/c")).unwrap();
fs::write(temp_dir.path().join("a/file1.txt"), "test").unwrap();
fs::write(temp_dir.path().join("a/b/file2.txt"), "test").unwrap();
fs::write(temp_dir.path().join("a/b/c/file3.txt"), "test").unwrap();
let mut count = 0;
walk_dir(temp_dir.path(), &mut |_| {
count += 1;
})
.unwrap();
assert_eq!(count, 3);
}
}