rustyphoenixreplace 1.4.1

This project aims to provide a simple a powerfull multilines replace via a toml configuration
/***************************************
	Auteur : Pierre Aubert
	Mail : pierre.aubert@lapp.in2p3.fr
	Licence : CeCILL-C
****************************************/

use std::fs;

use clap::Parser;

use std::path::Path;
use glob::glob;

mod configuration;
use crate::configuration::{ReplaceConfig, FullReplace, load_configuration};


///Arguments passed to the program
#[derive(Debug, Parser)]
#[command(version, about)]
struct Args {
	///toml configuration file of the replace
	#[arg(short = 'i', long = "input")]
	input: String,
	#[arg(short = 'r', long = "rundir")]
	///Directory where to run the recursive replace
	rundir: String,
}

///Replace the given pattern in the file
/// - `file_counter` : counter of the treated files
/// - `filename` : name of the file to be updated
/// - `vec_replace_config` : vector of ReplaceConfig to be used to update the file
fn replace_file_pattern(file_counter: &mut u64, filename: &Path, vec_replace_config: &Vec<ReplaceConfig>){
	println!("replace_file_pattern : ({}) update file '{:?}'", file_counter, filename);
	//Let's read the file
	let mut content: String = match fs::read_to_string(filename) {
		Ok(str) => str,
		Err(err) => panic!("replace_file_pattern : cannot read file '{:?}', error: {}", filename, err)
	};
	//Do all the replace
	for replace_config in vec_replace_config.iter() {
		let tmp = content.replace(&replace_config.pattern, &replace_config.replace);
		content = tmp.clone();
	}
	//Then save the file
	match fs::write(&filename, &content){
		Ok(_) => {},
		Err(err) => panic!("replace_file_pattern : cannot write file '{:?}', error: {}", filename, err)
	};
	//We where able to perform the replace, so we count it
	*file_counter += 1;
}

///Replace all file in the rundir by respect to the given configuration
/// # Parameters
/// - `replace_config` : replace configuration
/// - `rundir` : directory where to run the replace
fn replace_all(replace_config: &FullReplace, rundir: &String){
	let mut file_counter: u64 = 0;
	for pattern in replace_config.vec_file_pattern.iter() {
		//Create the expression with ** to get files recursively
		let search_pattern = format!("{}/**/{}", rundir, pattern);
		match glob(&search_pattern) {
			Ok(fileiter) => for filename in fileiter.into_iter() {
				match filename {
					Ok(working_file) => replace_file_pattern(&mut file_counter, working_file.as_path(), &replace_config.replace),
					Err(err) => println!("Cannot use file, error '{}'", err)
				}
			},
			Err(_) => println!("Pattern '{}' not found", search_pattern)
		};
	}
}


fn main() {
	println!("Replace String");
	// Parse CLI arguments
	let args = Args::parse();
	let replace_config: FullReplace = match load_configuration(&args.input){
		Ok(config) => config,
		Err(error) => panic!("{}", error)
	};
	replace_all(&replace_config, &args.rundir);
}

#[cfg(test)]
mod tests {
	use super::*;
	
	///Test the replace all function
	#[test]
	fn test_replace_all() {
		let output_dir = "generated/";
		let textfile = format!("{}/file.txt", output_dir);
		fs::create_dir_all(output_dir).unwrap();
		fs::write(&textfile, "contents\nto be modified\nand\nother values\n\n").unwrap();
		
		let replace_config: FullReplace = FullReplace{replace: vec![
			ReplaceConfig{pattern: String::from("modified\nand"), replace: String::from("changed\nto some")}
		], vec_file_pattern: vec![String::from("*.txt")]};
		
		replace_all(&replace_config, &output_dir.to_string());
		
		assert_eq!(fs::read_to_string(&textfile).unwrap(), "contents\nto be changed\nto some\nother values\n\n");
	}
}