use super::parser::Parser;
use anyhow::{Context, Result};
use chrono::Utc;
use regex::Regex;
use serde_json;
use std::fs;
use tera;
use uuid::Uuid;
const NAMESPACE_OID: Uuid = Uuid::from_u128(0x6ba7b812_9dad_11d1_80b4_00c04fd430c8);
fn generate_uuid(
value: &tera::Value,
_: &std::collections::HashMap<String, tera::Value>,
) -> tera::Result<tera::Value> {
let uuid: Uuid = match value {
tera::Value::String(s) => Uuid::new_v5(&NAMESPACE_OID, s.as_bytes()),
_ => return Err(tera::Error::msg("Invalid value")),
};
Ok(tera::to_value(uuid.hyphenated().to_string()).unwrap())
}
pub fn generate_using_tera<'a>(parser: &'a Parser, template: &'a str) -> String {
let mut tera = tera::Tera::default();
tera.register_filter("generateUUID", generate_uuid);
let json_data: tera::Value = serde_json::to_value(&parser).unwrap();
let mut context = tera::Context::new();
for (key, value) in json_data.as_object().unwrap() {
context.insert(key, value);
}
let result = tera.render_str(&template, &context);
match result {
Ok(value) => value,
Err(error) => panic!("Error: {}", error),
}
}
pub fn generate_json<'a>(parser: &'a Parser, filepath: &'a String) -> Result<()> {
let file = fs::File::create(filepath)
.with_context(|| format!("failed to create file `{}`", filepath))?;
serde_json::to_writer(file, parser)?;
Ok(())
}
pub fn merge_with_manual_sections(rendered: &str, old_gen: &str) -> String {
let regex = Regex::new(r"(?s)MANUAL SECTION: ([a-f0-9-]+).*?MANUAL SECTION END").unwrap();
let merged = regex.replace_all(&rendered, |captures: ®ex::Captures<'_>| {
let uuid = &captures[1];
let manual_content = Regex::new(&format!(
"(?s)MANUAL SECTION: {}.*?MANUAL SECTION END",
uuid
))
.unwrap();
manual_content
.find(old_gen)
.map_or(captures[0].to_string(), |m| m.as_str().to_string())
});
merged.into_owned()
}
pub fn generate<'a>(parser: &'a Parser, template: &'a str, sourcename: &'a str) -> String {
let mut output = String::from(template);
if template.contains("@incs@") {
let re = Regex::new(r"@incs@(?P<fmt>[\S\s]*)@end-incs@").unwrap();
for cap in re.captures_iter(template) {
let mut tmpstr = String::new();
for entry in &parser.incs {
let fmtstr = cap
.name("fmt")
.unwrap()
.as_str()
.replace("@captured@", &entry.captured);
tmpstr.push_str(&fmtstr);
}
output = re.replace(&output, tmpstr.as_str()).into_owned();
}
}
if template.contains("@static-vars@") {
let re = Regex::new(r"@static-vars@(?P<fmt>[\S\s]*)@end-static-vars@").unwrap();
for cap in re.captures_iter(template) {
let mut tmpstr = String::new();
for entry in &parser.static_vars {
let fmtstr = cap
.name("fmt")
.unwrap()
.as_str()
.replace("@captured@", &entry.captured)
.replace("@name@", &entry.name)
.replace("@name-expr@", &entry.name_expr)
.replace("@dtype@", &entry.dtype);
tmpstr.push_str(&fmtstr);
}
output = re.replace(&output, tmpstr.as_str()).into_owned();
}
}
if template.contains("@static-global-vars@") {
let re =
Regex::new(r"@static-global-vars@(?P<fmt>[\S\s]*)@end-static-global-vars@").unwrap();
for cap in re.captures_iter(template) {
let mut tmpstr = String::new();
for entry in &parser.static_vars {
if !entry.is_local {
let fmtstr = cap
.name("fmt")
.unwrap()
.as_str()
.replace("@captured@", &entry.captured)
.replace("@name@", &entry.name)
.replace("@name-expr@", &entry.name_expr)
.replace("@dtype@", &entry.dtype);
tmpstr.push_str(&fmtstr);
}
}
output = re.replace(&output, tmpstr.as_str()).into_owned();
}
}
if template.contains("@static-local-vars@") {
let re = Regex::new(r"@static-local-vars@(?P<fmt>[\S\s]*)@end-static-local-vars@").unwrap();
for cap in re.captures_iter(template) {
let mut tmpstr = String::new();
for entry in &parser.static_vars {
if entry.is_local {
let fmtstr = cap
.name("fmt")
.unwrap()
.as_str()
.replace("@captured@", &entry.captured)
.replace("@name@", &entry.name)
.replace("@name-expr@", &entry.name_expr)
.replace("@func-name@", &entry.func_name)
.replace("@dtype@", &entry.dtype);
tmpstr.push_str(&fmtstr);
}
}
output = re.replace(&output, tmpstr.as_str()).into_owned();
}
}
let fncs_tags = vec!["fncs", "fncs0"];
for tag in fncs_tags {
let regstr = format!("@{}@{}@end-{}@", tag, r"(?P<fmt>[\S\s]*)", tag);
let re = Regex::new(®str).unwrap();
for cap in re.captures_iter(template) {
let mut tmpstr = String::new();
for entry in &parser.fncs {
let fmtstr = cap
.name("fmt")
.unwrap()
.as_str()
.replace("@captured@", &entry.captured)
.replace("@name@", &entry.name)
.replace("@rtype@", &entry.rtype)
.replace("@args@", &entry.args)
.replace("@atypes@", &entry.atypes);
tmpstr.push_str(&fmtstr);
}
output = re.replace(&output, tmpstr.as_str()).into_owned();
}
}
if output.contains("@local-fncs@") {
let re = Regex::new(r"@local-fncs@(?P<fmt>[\S\s]*)@end-local-fncs@").unwrap();
for cap in re.captures_iter(template) {
let mut tmpstr = String::new();
for entry in &parser.fncs {
if entry.is_local {
let fmtstr = cap
.name("fmt")
.unwrap()
.as_str()
.replace("@captured@", &entry.captured)
.replace("@name@", &entry.name)
.replace("@rtype@", &entry.rtype)
.replace("@args@", &entry.args)
.replace("@atypes@", &entry.atypes);
tmpstr.push_str(&fmtstr);
}
}
output = re.replace(&output, tmpstr.as_str()).into_owned();
}
}
let ncls_tags = vec!["ncls", "ncls-once"];
for tag in ncls_tags {
let regstr = format!("@{}@{}@end-{}@", tag, r"(?P<fmt>[\S\s]*)", tag);
let re = Regex::new(®str).unwrap();
for cap in re.captures_iter(template) {
let mut tmpstr = String::new();
let mut callee_list: Vec<String> = Vec::new();
for entry in &parser.ncls {
if tag == "ncls-once" {
if callee_list.contains(&entry.callee.name) {
continue;
}
callee_list.push(entry.callee.name.to_string());
}
let mut fmtstr = cap
.name("fmt")
.unwrap()
.as_str()
.replace("@callee.name@", &entry.callee.name)
.replace("@callee.rtype@", &entry.callee.rtype)
.replace("@callee.args@", &entry.callee.args)
.replace("@callee.atypes@", &entry.callee.atypes)
.replace("@caller.name@", &entry.caller.name)
.replace("@caller.rtype@", &entry.caller.rtype)
.replace("@caller.args@", &entry.caller.args)
.replace("@caller.atypes@", &entry.caller.atypes);
let re4change = Regex::new(
r"@callee.rtype.change\((?P<from>[a-z|A-Z|0-9|_]+)=(?P<to>[a-z|A-Z|0-9|_]+)\)@",
)
.unwrap();
for cap in re4change.captures_iter(fmtstr.clone().as_str()) {
if cap.name("from").unwrap().as_str() == entry.callee.rtype.as_str() {
let to = cap.name("to").unwrap().as_str();
fmtstr = re4change.replace(&fmtstr, to).into_owned();
} else {
fmtstr = re4change.replace(&fmtstr, &entry.callee.rtype).into_owned();
}
}
let remove_tags = vec!["callee.rtype.remove", "callee.rtype.remove0"];
for tag in remove_tags {
let regstr = format!(r"@{}\((?P<text>[^)]+)\)@", tag);
let re = Regex::new(®str).unwrap();
for cap in re.captures_iter(fmtstr.clone().as_str()) {
if entry.callee.rtype.as_str() == "void" {
fmtstr = re.replace(&fmtstr, "").into_owned();
} else {
let text = cap.name("text").unwrap().as_str();
fmtstr = re.replace(&fmtstr, text).into_owned();
}
}
}
let remove_tag = "callee.args.remove";
let regstr = format!(r"@{}\((?P<text>[^)]+)\)@", remove_tag);
let re = Regex::new(®str).unwrap();
for cap in re.captures_iter(fmtstr.clone().as_str()) {
if entry.callee.args.as_str() == "void" || entry.callee.args.as_str() == "" {
fmtstr = re.replace(&fmtstr, "").into_owned();
} else {
let text = cap.name("text").unwrap().as_str();
fmtstr = re.replace(&fmtstr, text).into_owned();
}
}
tmpstr.push_str(&fmtstr);
}
output = re.replace(&output, tmpstr.as_str()).into_owned();
}
}
output.replace("@sourcename@", sourcename).replace(
"@date@",
Utc::now().format("%a %b %e %T %Y").to_string().as_str(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_incs() {
let sourcename = "test";
let code = "\
#include <header1.h>
#include <header2.h>
";
let temp = "\
// include
@incs@@captured@
@end-incs@
";
let expected = "\
// include
#include <header1.h>
#include <header2.h>
";
let parser = Parser::parse(code);
let generated = generate(&parser, temp, sourcename);
assert_eq!(generated, expected);
}
#[test]
fn test_generate_static_vars() {
let sourcename = "test";
let code = "\
static int a[10];
int b;
static char *c;
void func1(void)
{
static int local_var;
}
";
let temp = "\
// static variables
@static-vars@@dtype@ @name-expr@;
@end-static-vars@
// static global variables
@static-global-vars@@dtype@ @name@;
@end-static-global-vars@
// static local variables
@static-local-vars@@dtype@ @name@;
@end-static-local-vars@
";
let expected = "\
// static variables
int a[10];
char * c;
int local_var;
// static global variables
int a;
char * c;
// static local variables
int local_var;
";
let parser = Parser::parse(code);
let generated = generate(&parser, temp, sourcename);
assert_eq!(generated, expected);
}
#[test]
fn test_generate_fncs() {
let sourcename = "test";
let code = "\
// functions
int func1()
{
return 0;
}
void func2(int const * a)
{
}
";
let temp = "\
// functions
@fncs@@rtype@ @name@(@args@);
@atypes@
@end-fncs@
";
let expected = "\
// functions
int func1();
void func2(int const * a);
const int *
";
let parser = Parser::parse(code);
let generated = generate(&parser, temp, sourcename);
assert_eq!(generated, expected);
}
#[test]
fn test_generate_ncls() {
let sourcename = "test";
let code = "\
// functions
void func1()
{
return;
}
int func2(int a)
{
return func1();
}
";
let temp = "\
@ncls@- @caller.name@ -> @callee.name@
- return @callee.rtype.remove(0)@;
- (int dummy@callee.args.remove(, )@@callee.args@);
@end-ncls@
";
let expected = "\
- func2 -> func1
- return ;
- (int dummy);
";
let parser = Parser::parse(code);
let generated = generate(&parser, temp, sourcename);
assert_eq!(generated, expected);
}
#[test]
fn test_generate_ncls_once() {
let sourcename = "test";
let code = "\
// functions
int func1()
{
return 0;
}
void func2(int a)
{
func1();
}
void func3(int a)
{
func1();
}
";
let temp = "\
@ncls-once@- @callee.name@
@end-ncls-once@
";
let expected = "\
- func1
";
let parser = Parser::parse(code);
let generated = generate(&parser, temp, sourcename);
assert_eq!(generated, expected);
}
}