1use std::ffi::OsStr;
2use std::path::{Path, PathBuf};
3
4use clap::Parser;
5
6#[derive(Debug, Default, Clone, Parser)]
7#[clap(rename_all = "kebab-case")]
8pub struct Config {
9 #[clap(short, long)]
11 follow_symlinks: bool,
12
13 #[clap(short, long)]
15 leave_empty_dirs: bool,
16
17 #[clap(short, long)]
21 no_special_treatment_for_tests_dir: bool,
22}
23
24pub async fn find_mod_named_modules(
25 path: impl AsRef<Path>,
26 config: &Config,
27) -> anyhow::Result<Vec<PathBuf>> {
28 let path = path.as_ref();
29
30 let mut dirs_to_process = vec![path.to_owned()];
31 let mut results = vec![];
32
33 while let Some(dir) = dirs_to_process.pop() {
34 let mut read_dir = tokio::fs::read_dir(dir).await?;
35
36 while let Some(entry) = read_dir.next_entry().await? {
37 let file_type = entry.file_type().await?;
38
39 if file_type.is_dir() {
40 dirs_to_process.push(entry.path());
41 }
42
43 if config.follow_symlinks && file_type.is_symlink() {
44 dirs_to_process.push(entry.path());
45 }
46
47 if file_type.is_file() {
48 let p = entry.path();
49
50 if p.file_name() == Some(OsStr::new("mod.rs")) {
51 results.push(p);
52 }
53 }
54 }
55 }
56
57 Ok(results)
58}
59
60pub async fn move_mod_rs_outside_of_dir(
61 mod_files: Vec<PathBuf>,
62 config: &Config,
63) -> anyhow::Result<()> {
64 for mod_file in mod_files {
65 let parent_dir = mod_file
66 .parent()
67 .ok_or_else(|| anyhow::anyhow!("Missing parent"))?;
68
69 if !config.no_special_treatment_for_tests_dir {
70 let parent_of_parent = parent_dir
71 .parent()
72 .ok_or_else(|| anyhow::anyhow!("Missing parent"))?;
73
74 if parent_of_parent.file_name() == Some(OsStr::new("tests")) {
75 continue;
76 }
77 }
78
79 let new_path = parent_dir.with_extension("rs");
80
81 move_file(&mod_file, new_path).await?;
82
83 if !config.leave_empty_dirs && is_dir_empty(parent_dir).await? {
84 tokio::fs::remove_dir(parent_dir).await?;
85 }
86 }
87
88 Ok(())
89}
90
91async fn is_dir_empty(dir: impl AsRef<Path>) -> anyhow::Result<bool> {
92 let mut read_dir = tokio::fs::read_dir(dir).await?;
93
94 Ok(read_dir.next_entry().await?.is_none())
95}
96
97async fn move_file(
98 source_path: impl AsRef<Path>,
99 target_path: impl AsRef<Path>,
100) -> anyhow::Result<()> {
101 let source_path = source_path.as_ref();
102
103 tokio::fs::copy(source_path, target_path).await?;
104
105 tokio::fs::remove_file(source_path).await?;
106
107 Ok(())
108}