use crate::error::{RailResult, ResultExt};
use std::fs;
use std::path::{Path, PathBuf};
pub struct AuxiliaryFiles {
files: Vec<AuxiliaryFile>,
}
#[derive(Debug, Clone)]
struct AuxiliaryFile {
source_path: PathBuf,
target_path: PathBuf,
}
pub struct ProjectFiles {
files: Vec<AuxiliaryFile>,
}
impl AuxiliaryFiles {
pub fn discover(workspace_root: &Path) -> RailResult<Self> {
let mut files = Vec::new();
let candidates = vec![
("rust-toolchain.toml", "rust-toolchain.toml"),
("rust-toolchain", "rust-toolchain"),
("rustfmt.toml", "rustfmt.toml"),
(".rustfmt.toml", ".rustfmt.toml"),
(".cargo/config.toml", ".cargo/config.toml"),
(".cargo/config", ".cargo/config"),
("deny.toml", "deny.toml"),
(".editorconfig", ".editorconfig"),
];
for (source_rel, target_rel) in candidates {
let source_path = workspace_root.join(source_rel);
if source_path.exists() && source_path.is_file() {
files.push(AuxiliaryFile {
source_path,
target_path: PathBuf::from(target_rel),
});
}
}
Ok(Self { files })
}
pub fn copy_to_split(&self, _workspace_root: &Path, target_repo_root: &Path) -> RailResult<()> {
if self.files.is_empty() {
return Ok(());
}
for file in &self.files {
let target_path = target_repo_root.join(&file.target_path);
if let Some(parent) = target_path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("Failed to create directory for {}", target_path.display()))?;
}
fs::copy(&file.source_path, &target_path).with_context(|| {
format!(
"Failed to copy {} to {}",
file.source_path.display(),
target_path.display()
)
})?;
}
Ok(())
}
pub fn count(&self) -> usize {
self.files.len()
}
pub fn is_empty(&self) -> bool {
self.files.is_empty()
}
}
impl ProjectFiles {
pub fn discover(workspace_root: &Path, crate_paths: &[PathBuf]) -> RailResult<Self> {
let mut files = Vec::new();
let candidates = vec!["README.md", "LICENSE", "LICENSE-MIT", "LICENSE-APACHE"];
for filename in candidates {
let crate_file = crate_paths
.iter()
.map(|crate_path| workspace_root.join(crate_path).join(filename))
.find(|path| path.exists() && path.is_file());
let workspace_file = workspace_root.join(filename);
let source_path = if let Some(crate_file) = crate_file {
crate_file
} else if workspace_file.exists() && workspace_file.is_file() {
workspace_file
} else {
continue; };
files.push(AuxiliaryFile {
source_path,
target_path: PathBuf::from(filename),
});
}
Ok(Self { files })
}
pub fn copy_to_split(&self, _workspace_root: &Path, target_repo_root: &Path) -> RailResult<()> {
if self.files.is_empty() {
return Ok(());
}
for file in &self.files {
let target_path = target_repo_root.join(&file.target_path);
fs::copy(&file.source_path, &target_path).with_context(|| {
format!(
"Failed to copy {} to {}",
file.source_path.display(),
target_path.display()
)
})?;
}
Ok(())
}
pub fn count(&self) -> usize {
self.files.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_discover_finds_rust_toolchain() {
let temp = TempDir::new().unwrap();
let workspace_root = temp.path();
fs::write(
workspace_root.join("rust-toolchain.toml"),
"[toolchain]\nchannel = \"stable\"\n",
)
.unwrap();
let aux_files = AuxiliaryFiles::discover(workspace_root).unwrap();
assert_eq!(aux_files.count(), 1);
assert!(!aux_files.is_empty());
}
#[test]
fn test_discover_finds_multiple_files() {
let temp = TempDir::new().unwrap();
let workspace_root = temp.path();
fs::write(workspace_root.join("rust-toolchain.toml"), "channel = \"stable\"").unwrap();
fs::write(workspace_root.join("rustfmt.toml"), "max_width = 100").unwrap();
fs::create_dir_all(workspace_root.join(".cargo")).unwrap();
fs::write(workspace_root.join(".cargo/config.toml"), "[build]\nrustflags = []").unwrap();
let aux_files = AuxiliaryFiles::discover(workspace_root).unwrap();
assert_eq!(aux_files.count(), 3);
}
#[test]
fn test_copy_to_split() {
let temp = TempDir::new().unwrap();
let workspace_root = temp.path().join("workspace");
let split_root = temp.path().join("split");
fs::create_dir(&workspace_root).unwrap();
fs::create_dir(&split_root).unwrap();
fs::write(workspace_root.join("rust-toolchain.toml"), "channel = \"stable\"").unwrap();
let aux_files = AuxiliaryFiles::discover(&workspace_root).unwrap();
aux_files.copy_to_split(&workspace_root, &split_root).unwrap();
assert!(split_root.join("rust-toolchain.toml").exists());
let content = fs::read_to_string(split_root.join("rust-toolchain.toml")).unwrap();
assert_eq!(content, "channel = \"stable\"");
}
#[test]
fn test_copy_creates_directories() {
let temp = TempDir::new().unwrap();
let workspace_root = temp.path().join("workspace");
let split_root = temp.path().join("split");
fs::create_dir(&workspace_root).unwrap();
fs::create_dir(&split_root).unwrap();
fs::create_dir_all(workspace_root.join(".cargo")).unwrap();
fs::write(workspace_root.join(".cargo/config.toml"), "[build]\nrustflags = []").unwrap();
let aux_files = AuxiliaryFiles::discover(&workspace_root).unwrap();
aux_files.copy_to_split(&workspace_root, &split_root).unwrap();
assert!(split_root.join(".cargo").exists());
assert!(split_root.join(".cargo/config.toml").exists());
}
}