use crate::prelude::*;
use regex::Regex;
use std::fs;
use std::fs::OpenOptions;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::string;
static DOCTEST_INCLUDE: &str = "#include \"doctest.h\"";
static TROMPELOEIL_INCLUDE: &str = "#include \"trompeloeil.hpp\"";
static FORMAT_OFF: &str = "// clang-format off";
static FORMAT_ON: &str = "// clang-format on";
static GEN_STRING_START: &str = "/* generated mock code */";
static GEN_STRING_END: &str = "/* generated mock code end */";
pub fn generate_mock_string(function: &str) -> Result<String> {
let re = Regex::new(r"\(([^()]*)\)").unwrap();
let mut arguments = re.find(&function).unwrap().as_str();
let mut args: Vec<&str> = arguments.split([',']).collect();
let parts: Vec<&str> = function.split([' ', '(']).collect();
let mut return_param: String = parts[0].to_owned();
let mut func_name: String = String::new();
func_name = parts[1].trim().to_owned();
if func_name.contains("*") {
func_name = func_name.replace("*", "");
return_param = return_param + "*";
} else if func_name.contains("&") {
func_name = func_name.replace("&", "");
return_param = return_param + "&";
}
let mut arg_count = 0;
let mut arg_vec: Vec<String> = Vec::new();
let mut edited_string: String = String::new();
for typename in args {
if typename.contains("void") || typename.contains("()") {
break;
}
arg_count += 1;
let mut trimmed_string = typename;
if let Some((m, _)) = typename.split_once(" )") {
trimmed_string = m;
}
let new_string = trimmed_string;
let mut arg: Vec<&str> = new_string.split(" ").collect();
if arg.len() == 1 {
println!("missing parameter name for {}", func_name);
}
let typename = arg.pop().unwrap_or("");
let arg_string = arg.join(" ");
let mut arg_string = arg_string.to_owned();
edited_string = arg_string.replace('(', "");
edited_string = edited_string.replace(')', "");
if typename.contains("**") {
edited_string = edited_string + "**";
} else if typename.contains("*") {
edited_string = edited_string + "*";
}
if typename.contains("&") {
edited_string = edited_string + "&";
}
arg_vec.push(edited_string);
}
let mut conststring = "";
let args_string = arg_vec.join(", ");
if function.contains(") const") {
conststring = "_CONST";
}
let mock_string = format!(
"\tMAKE{}_MOCK{}({}, {}({}), override);",
conststring, arg_count, func_name, return_param, args_string
);
Ok(mock_string)
}
pub fn generate_mock_file(
output_path: &str,
file_name: &str,
classname: &str,
inheritance: &str,
funcs: &Vec<String>,
) -> Result<()> {
let mut output_path_owned = output_path.to_owned();
if !output_path.contains("./") {
let string_name = &output_path.to_owned();
output_path_owned = "./".to_owned() + string_name;
}
if !fs::metadata(&output_path_owned).is_ok() {
fs::create_dir(&output_path_owned)?;
}
let mut output_lines: Vec<String> = Vec::new();
let mut file_exists = false;
let (_, filename) = file_name.rsplit_once(['\\', '/']).unwrap();
let file_path = output_path.to_owned() + "\\mock_" + filename;
let mut lines: Vec<String> = Vec::new();
let mut index = 0;
if (Path::new(&file_path).exists()) {
file_exists = true;
lines = BufReader::new(
OpenOptions::new()
.read(true)
.open(file_path.as_str())
.unwrap(),
)
.lines()
.map(|line| line.unwrap())
.collect::<Vec<String>>();
index = lines.iter().position(|r| r == GEN_STRING_START).unwrap();
loop {
if (lines[index] == GEN_STRING_END) {
lines.remove(index);
break;
}
lines.remove(index);
}
};
for (idx, line) in lines.iter().enumerate() {
if (idx >= index) {
output_lines.insert(index, GEN_STRING_START.to_owned());
break;
}
output_lines.push(line.to_string());
}
for func in funcs.iter() {
output_lines.push(generate_mock_string(func)?);
}
let class_name_string = "class mock_".to_owned() + classname;
let include_string = format!("#include \"{}\"", filename);
output_lines.insert(index, "{".to_owned());
output_lines.insert(index, class_name_string.to_owned() + inheritance);
output_lines.insert(index, "".to_owned());
output_lines.insert(index, "".to_owned());
output_lines.insert(index, include_string.to_owned());
output_lines.insert(index, TROMPELOEIL_INCLUDE.to_owned());
output_lines.insert(index, DOCTEST_INCLUDE.to_owned());
output_lines.insert(index, "".to_owned());
output_lines.insert(index, "".to_owned());
output_lines.insert(index, GEN_STRING_START.to_owned());
if (!file_exists) {
output_lines.insert(
0,
"#ifndef _MOCK_".to_owned() + &filename.to_ascii_uppercase().replace(".", "_"),
);
output_lines.insert(
1,
"#define _MOCK_".to_owned() + &filename.to_ascii_uppercase().replace(".", "_"),
);
}
output_lines.push("};".to_owned());
output_lines.push(GEN_STRING_END.to_owned());
if (!file_exists) {
output_lines.push("#endif".to_owned());
} else {
for line in lines[index..].iter() {
output_lines.push(line.to_string());
}
}
if Path::new(&file_path).exists() {
fs::remove_file(&file_path).unwrap();
}
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(file_path)
.unwrap();
for line in output_lines {
writeln!(file, "{}", line).unwrap();
}
Ok(())
}
pub fn generate_c_wrapper(output_path: &str, file_name: &str, funcs: &Vec<String>) -> Result<()> {
let mut output_path_owned = output_path.to_owned();
if !output_path.contains("./") {
let string_name = &output_path.to_owned();
output_path_owned = "./".to_owned() + string_name;
}
if !fs::metadata(&output_path_owned).is_ok() {
fs::create_dir(&output_path_owned)?;
}
let (_, filename) = file_name.rsplit_once(['\\', '/']).unwrap();
let mock_path = "mock_".to_owned() + filename;
let file_path = output_path_owned + "\\" + filename;
let mock_name = "mock_".to_owned() + filename.trim_end_matches(".h") + "_c";
let mut lines: Vec<String> = Vec::new();
if (Path::new(&file_path).exists()) {
lines = BufReader::new(
OpenOptions::new()
.read(true)
.open(file_path.as_str())
.unwrap(),
)
.lines()
.map(|line| line.unwrap())
.collect::<Vec<String>>();
let index = lines.iter().position(|r| r == GEN_STRING_START).unwrap();
loop {
lines.remove(index);
if (lines[index] == GEN_STRING_END) {
lines.remove(index);
break;
}
}
};
if Path::new(&file_path).exists() {
fs::remove_file(&file_path).unwrap();
}
let mut output_lines: Vec<String> = Vec::new();
let mut index = lines.len();
for line in lines {
output_lines.push(line.to_string());
}
let include_string = format!("#include \"{}\"", mock_path);
output_lines.push(GEN_STRING_START.to_owned());
output_lines.push(include_string);
output_lines.push("".to_owned());
output_lines.push(mock_name + " api_mock;");
output_lines.push("".to_owned());
output_lines.push("extern \"C\"{".to_owned());
for func in funcs.iter() {
let func_name = func.trim_end_matches(";");
output_lines.push("".to_owned());
output_lines.push("\t".to_owned() + func_name);
output_lines.push("\t{".to_owned());
let func_name: Vec<&str> = func.split(" ").collect();
let line = func_name.join(" ");
let regex = Regex::new(r"\(([^()]*)\)").unwrap();
let mut args: Vec<String> = Vec::new();
for params in regex.captures_iter(&line) {
let mut params = params.get(0).unwrap().as_str().to_string();
params = params.replace(")", "");
params = params.replace("(", "");
for split in params.split(',') {
let mut trimmed_string = split.trim();
if let Some(argument_split) = trimmed_string.rsplit_once(' ') {
trimmed_string = argument_split.1;
} else {
continue;
}
let name = trimmed_string;
let mut name_string = name.to_owned();
name_string = name_string.replace("*", "");
name_string = name_string.replace("&", "");
args.push(name_string.to_owned())
}
}
let mut func_name: Vec<&str> = func.split("(").collect();
let arg_string = args.join(", ");
let args_call = "( ".to_owned() + arg_string.as_str() + " );";
let func_call = func_name[0];
let func_name: Vec<&str> = func_call.split(" ").collect();
let name = func_name[1];
let module_call = "\t\treturn api_mock.".to_owned() + name + args_call.as_str();
output_lines.push(module_call);
output_lines.push("\t}".to_owned());
output_lines.push("".to_owned());
}
output_lines.push("}".to_owned());
output_lines.push(GEN_STRING_END.to_owned());
let mut file = OpenOptions::new()
.write(true)
.create_new(true)
.open(file_path)
.unwrap();
for line in output_lines {
writeln!(file, "{}", line).unwrap();
}
Ok(())
}
pub fn generate_cmock_file(output_path: &str, path: &str, funcs: &Vec<String>) -> Result<()> {
let (_, filename) = path.rsplit_once(['\\', '/']).unwrap();
let mock_name = filename.trim_end_matches(".h").to_owned() + "_c";
generate_mock_file(output_path, path, &mock_name, "", funcs);
generate_c_wrapper(output_path, path, funcs);
Ok(())
}