use clap::Parser; use std::{ fs, io::Write, sync::{Arc, Mutex}, time::Duration, }; mod pot; mod visitor; mod walker; /// Generate Gettext template files from Javascript/Typescript code. #[derive(Parser)] struct Cli { /// A list of patterns to exclude #[arg(long, num_args(0..), default_values_t = [ "/.git/".to_string(), "/node_modules/".to_string(), "/__tests__/".to_string(), ".test.".to_string(), "/__mocks__/".to_string(), ".mock.".to_string(), ".story.".to_string(), ".cy.".to_string() ])] exclude: Vec, /// The path to the file to read. Defaults to current folder #[arg(long)] path: Option, /// The folder where pot files will be written. Each domain will have its own file. #[arg(long)] output_folder: std::path::PathBuf, /// Which folder the references are relative to. Defaults to the output folder. #[arg(long)] references_relative_to: Option, /// The default domain to use for strings that don't have a domain specified. #[arg(long, default_value = "default")] default_domain: String, } fn main() { let args = Cli::parse(); run(args) } use indicatif::ProgressBar; fn run(args: Cli) { let default_domain = args.default_domain; let exclude = args.exclude; let path = args.path.unwrap_or(std::path::PathBuf::from(".")); let output_folder = args.output_folder; let references_relative_to = args.references_relative_to.unwrap_or(output_folder.clone()); let pot = Arc::new(Mutex::new(pot::POT::new(default_domain))); { let bar = ProgressBar::new_spinner(); bar.enable_steady_tick(Duration::from_millis(100)); bar.set_message("Reading files..."); match walker::find_ts_files(path, exclude) { Ok(entries) => { for entry in entries { bar.set_message(format!( "Reading {}", entry.path().to_str().unwrap_or("unknown") )); bar.inc(1); walker::parse_file( &entry.into_path(), Arc::clone(&pot), &references_relative_to, ); } } Err(e) => { panic!("Error reading path: {}", e); } } bar.finish_with_message("Done reading source files"); }; { let bar = ProgressBar::new_spinner(); bar.enable_steady_tick(Duration::from_millis(100)); bar.set_message(format!( "Writing POT files to {}", output_folder.to_str().unwrap_or("unknown") )); match fs::create_dir_all(&output_folder) { Ok(_) => {} Err(e) => { panic!("Error creating output folder: {}", e); } } let domains = &pot.lock().unwrap().domains; bar.set_length(domains.len() as u64); domains.iter().for_each(|(domain, pot_file)| { let file_path = output_folder.join(format!("{}.pot", domain)); bar.set_message(format!( "Writing {}", file_path.to_str().unwrap_or("unknown") )); let mut file = match std::fs::File::create(file_path) { Ok(file) => file, Err(e) => { panic!("Failed to create file: {}", e); } }; match file.write_all(pot_file.convert_to_string().as_bytes()) { Ok(_) => { bar.inc(1); } Err(e) => { panic!("Failed to write file: {}", e); } }; }); bar.finish_with_message("Done writing POT files"); }; } #[cfg(test)] mod tests { use clap::{CommandFactory, Parser}; use pretty_assertions::assert_eq; use walkdir::WalkDir; use crate::*; #[test] fn verify_cmd() { Cli::command().debug_assert(); } #[test] fn verify_snapshot() { let _ = fs::remove_dir_all("./tests/output/"); let args = Cli::parse_from([ "", "--path", "./tests/src/", "--output-folder", "./tests/output/", "--default-domain", "test", "--references-relative-to", "./tests", ]); run(args); for entry in WalkDir::new("./tests/output/") .into_iter() .filter_map(|entry| entry.ok()) .filter(|entry| { entry .metadata() .ok() .map(|metadata| metadata.is_file()) .unwrap_or(false) }) { println!("{:?}", entry.path()); let actual = fs::read_to_string(entry.path()).unwrap(); let expected_file = format!( "./tests/expected-output/{}", entry.file_name().to_str().unwrap() ); let expected = fs::read_to_string(&expected_file) .unwrap_or_else(|_| panic!("Missing expected output file: {}", &expected_file)); assert_eq!(actual, expected); } } }