解析 CSV 文件,并使用 rg 查找所有匹配的文件再更新文件内容 use anyhow::Result; use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; use std::process::Command; static CSV: &'static str = "xxx.csv"; static PROJECT: &'static str = "/workspace/target-project-path"; #[tokio::main] async fn main() { let i18n_data = get_all_i18n_data(PROJECT).await.unwrap(); match handle_all_files(&i18n_data).await { Ok(_) => { println!("Complete"); }, Err(err) => { println!("Err {:?}", err); } }; } async fn handle_all_files(data_items: &Vec<DataItem>) -> Result<String> { for item in data_items.iter() { for file in item.files.iter() { replace_file(file.as_str(), item.key.as_str(), &item.new_key.as_str()).await?; } } Ok(String::from("")) } async fn replace_file(file_path: &str, key: &str, new_key: &str) -> Result<String> { let mut file = File::open(file_path)?; let mut source_content = String::new(); file.read_to_string(&mut source_content)?; let new_content = source_content.as_str().replace(key, new_key); std::fs::write(file_path, new_content.as_str())?; println!("更新文件:{}: {} -> {}", file_path, key, new_key); Ok(String::from("")) } // 获取Key源数据 async fn get_all_i18n_data(project: &str) -> Result<Vec<DataItem>>{ let mut file = File::open(CSV)?; let mut csv = String::new(); file.read_to_string(&mut csv)?; let mut data: Vec<DataItem> = Vec::new(); let csv_lines = csv.split("\n").collect::<Vec<&str>>(); for line in csv_lines.into_iter() { let line_items = line.split(",").collect::<Vec<&str>>(); let key = line_items.get(2); let new_key = line_items.get(3); if key.is_some() && new_key.is_some() { let key = key.unwrap(); let new_key = new_key.unwrap(); if !key.eq(&"Key") { let files = find_files_by_project_path(project, *key).await; data.push(DataItem { key: String::from(*key), new_key: String::from(*new_key), files, }); } } } Ok(data) } // 使用 max-depth=0 先查找根目录 // 再查找所有合法目录 pub async fn find_files_by_project_path(project_path: &str, i18n_key: &str) -> Vec<String> { // 查找根目录 let mut files = find_files(i18n_key, project_path, true).await.unwrap(); // 查找子目录 if let Ok(entries) = std::fs::read_dir(project_path) { for entry in entries { if let Ok(entry) = entry { let path = entry.path(); let path_str = path.to_str().unwrap(); // 不包含配置文件排除的目录 if path.is_dir() && !is_file_need_exclude(path_str).await { let current_files = find_files(i18n_key, path_str, false).await.unwrap(); files = [files, current_files].concat(); } } } } files } /// /// 查找所有内容匹配的文件 /// async fn find_files(i18n_key: &str, project_path: &str, is_root: bool) -> Result<Vec<String>> { let search = i18n_key; let mut search_path = PathBuf::from(project_path.trim_end_matches("/")); if !search_path.has_root() { search_path = PathBuf::from(project_path); if let Ok(item) = search_path.canonicalize() { search_path = item; } } let mut rg_cmd = format!( "rg -0 -s -e \"{}\" {}", search, search_path.to_str().unwrap() ); // 根目录只查找一层 if is_root { rg_cmd = format!( "rg -0 -s --max-depth=0 -e \"{}\" {}/*", search, search_path.to_str().unwrap() ); } let output = if cfg!(target_os = "windows") { Command::new("cmd").args(["/C", &rg_cmd]).output()? } else { Command::new("sh").arg("-c").arg(&rg_cmd).output()? }; let stdout = String::from_utf8(output.stdout)?; let arr: Vec<&str> = stdout.split("\n").collect(); let valid_str_arr: Vec<&str> = arr .into_iter() .filter(|item| { let target = *item; if target.eq("") { return false; } return true; }) .collect(); let mut data: Vec<String> = Vec::new(); for item in valid_str_arr.iter() { let item_arr: Vec<&str> = item.split("\0").collect(); let file = *item_arr.get(0).unwrap(); let file_item = String::from(file); // 排除指定文件 if is_file_need_exclude(file_item.as_str()).await { continue; } // 重复的文件 if !data.contains(&file_item) { data.push(file_item); } } Ok(data) } /// /// 检测是否是需要排除的文件 /// async fn is_file_need_exclude(filename: &str) -> bool { if filename.contains("binary file matches") { return true; } let exclude_files_names: Vec<&str> = vec![ "node_modules", "config", ".node", "packages", "package.json", "package-lock.json", ]; if exclude_files_names.is_empty() { return false; } let target = exclude_files_names.into_iter().find(|item| { return filename.ends_with(*item); }); if let Some(_) = target { return true; } false } #[derive(Debug)] pub struct DataItem { pub key: String, pub new_key: String, pub files: Vec<String>, }