use std::{
ffi::OsString,
path::{Path, PathBuf},
process::Command,
};
use anyhow::{Context, Result};
use walkdir::WalkDir;
pub fn prepare_out_dir(out_dir: impl AsRef<Path>) -> Result<()> {
fn inner(out_dir: &Path) -> Result<()> {
if out_dir.exists() {
fs_err::remove_dir_all(out_dir).with_context(|| {
format!(
"could not remove the output directory: {}",
out_dir.display()
)
})?;
}
fs_err::create_dir_all(out_dir).with_context(|| {
format!(
"could not create the output directory: {}",
out_dir.display()
)
})?;
Ok(())
}
inner(out_dir.as_ref())
}
#[allow(clippy::filetype_is_file)]
pub fn get_protos(input: impl AsRef<Path>, follow_links: bool) -> impl Iterator<Item = PathBuf> {
fn inner(input: &Path, follow_links: bool) -> impl Iterator<Item = PathBuf> {
WalkDir::new(input)
.follow_links(follow_links)
.into_iter()
.filter_map(|r| r.map_err(|err| println!("cargo:warning={err:?}")).ok())
.filter(|e| e.file_type().is_file())
.filter(|e| e.path().extension().is_some_and(|e| e == "proto"))
.map(|e| e.path().to_path_buf())
}
inner(input.as_ref(), follow_links)
}
pub fn refactor(output: impl AsRef<Path>) -> Result<()> {
fn inner(output: &Path) -> Result<()> {
let tree: crate::tree::Tree = fs_err::read_dir(output)?
.filter_map(|r| r.map_err(|err| println!("cargo:warning={err:?}")).ok())
.filter(|e| e.path().extension().is_some_and(|e| e == "rs"))
.filter(|e| !e.path().ends_with("mod.rs"))
.map(|e| e.path())
.collect();
tree.move_paths(output, OsString::new(), PathBuf::new())?;
fs_err::write(output.join("mod.rs"), tree.generate_module())?;
Command::new("rustfmt")
.arg(output.join("mod.rs"))
.spawn()
.context("failed to format the mod.rs output")?;
Ok(())
}
inner(output.as_ref())
}
#[cfg(test)]
mod test {
use super::refactor;
#[test]
fn refactor_test_moves_files_to_correct_place() {
let files = vec![
"root.pak.a1.rs",
"root.pak.a2.rs",
"root.pak.rs",
"root.now.deeply.nested.rs",
"root.rs",
"other.rs",
];
let temp_dir = tempfile::tempdir().unwrap();
let temp_dir_path = temp_dir.path().to_path_buf();
for file in &files {
let path = temp_dir_path.join(file);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::File::create(path.clone()).unwrap();
std::fs::write(path.clone(), format!("// {file} contents")).unwrap();
}
let expected_file_contents = vec![
("root/pak/a1.rs", vec!["// root.pak.a1.rs contents"]),
("root/pak/a2.rs", vec!["// root.pak.a2.rs content"]),
(
"root/pak.rs",
vec!["pub mod a1;", "pub mod a2;", "// root.pak.rs contents"],
),
("root/now.rs", vec!["pub mod deeply;"]),
("root/now/deeply.rs", vec!["pub mod nested;"]),
(
"root/now/deeply/nested.rs",
vec!["// root.now.deeply.nested.rs contents"],
),
(
"root.rs",
vec!["pub mod pak;", "pub mod now;", "// root.rs contents"],
),
("mod.rs", vec!["pub mod other;", "pub mod root;"]),
("other.rs", vec!["// other.rs contents"]),
];
refactor(&temp_dir_path).unwrap();
for (file, contents) in &expected_file_contents {
let path = temp_dir_path.join(file);
assert!(path.exists());
let content = std::fs::read_to_string(path).unwrap();
for line in contents {
assert!(content.contains(line), "{content} does not contain {line}");
}
}
}
}