Skip to content

Commit

Permalink
Merge pull request #82 from Programmierpraktikum-MVA/76-make-it-so-we…
Browse files Browse the repository at this point in the history
…-can-define-functions-in-generator-call-them-in-template

cleaned up generator and added camel_case_to_snake_case
  • Loading branch information
aikokal authored Jul 5, 2023
2 parents 94247d1 + 41a748c commit 30dd9f3
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 55 deletions.
71 changes: 16 additions & 55 deletions src/generator/common.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use gtmpl::Context;

use crate::generator::template_functions::TEMPLATE_FUNCTIONS;
use crate::{template_context::TemplateContext, utils};
use gtmpl::Context;
use std::{
ffi::OsStr,
fs,
path::{Path, PathBuf},
process::{Command, Output},
vec,
};

pub fn check_for_overwrite(output_path: &Path, project_title: &str) {
Expand Down Expand Up @@ -61,52 +60,6 @@ pub fn cargo_fix(path: &PathBuf) -> Output {
.expect("failed to cargo fix")
}

fn key_exists(args: &[gtmpl_value::Value]) -> Result<gtmpl_value::Value, gtmpl_value::FuncError> {
if args.is_empty() {
return Err(gtmpl_value::FuncError::AtLeastXArgs(
"Need at least 1 arg for key exists".to_string(),
1,
));
}
let map = args[0].clone();
if args.len() == 1 {
return Ok(gtmpl_value::Value::Bool(true));
}
let keys = args[1..].to_vec();
// check if keys is empty
let rest_keys: Vec<gtmpl_value::Value> = match keys.len() > 1 {
false => vec![],
_ => keys[1..].to_vec(),
};

// extract first key
if !keys.is_empty() {
let key = keys[0].clone();
match key {
gtmpl_value::Value::String(s) => {
let res: Result<gtmpl_value::Value, gtmpl_value::FuncError> = match map {
gtmpl_value::Value::Object(o) => {
// call again with rest of keys
key_exists(
vec![vec![o.get(&s).unwrap().clone()], rest_keys]
.concat()
.as_slice(),
)
}
_ => Ok(gtmpl_value::Value::Bool(false)),
};
return res;
}
_ => {
return Err(gtmpl_value::FuncError::Generic(
"keys need to be string".to_string(),
));
}
}
}
Ok(gtmpl::Value::Bool(true))
}

/// reads template from path renders it with context reference and writes to output file
pub fn template_render_write(
template_path: &PathBuf,
Expand All @@ -124,12 +77,7 @@ pub fn template_render_write(
std::process::exit(1);
}
};
let mut base_template = gtmpl::Template::default();
base_template.add_func("key_exists", key_exists);
base_template
.parse(&template)
.expect("failed to parse template");
let mut render = match base_template.render(&Context::from(context_ref.into())) {
let mut render = match render_template(&template, context_ref, TEMPLATE_FUNCTIONS) {
Ok(render) => render,
Err(e) => {
eprintln!("❌ Error rendering template: {}", e);
Expand All @@ -151,6 +99,19 @@ pub fn template_render_write(
}
}

/// parses templates, adds funcs so they can be executed from inside the template and renders templatey
/// just like `gtmpl::render` but supports adding template functions
fn render_template<C: Into<gtmpl::Value>, F: Into<String> + Clone>(
template_str: &str,
context: C,
template_functions: &[(F, gtmpl::Func)],
) -> Result<String, gtmpl::TemplateError> {
let mut tmpl = gtmpl::Template::default();
tmpl.add_funcs(template_functions);
tmpl.parse(template_str)?;
tmpl.render(&Context::from(context)).map_err(Into::into)
}

pub fn write_multiple_templates(
template_path: &Path,
context_ref: &TemplateContext,
Expand Down
1 change: 1 addition & 0 deletions src/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ pub use common::{
cargo_fix, cargo_fmt, cargo_generate_rustdoc, cargo_init_project, check_for_overwrite,
template_render_write, write_multiple_templates,
};
mod template_functions;
pub use model::generate_models_folder;
78 changes: 78 additions & 0 deletions src/generator/template_functions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use gtmpl::{gtmpl_fn, Func, FuncError};

pub static TEMPLATE_FUNCTIONS: &[(&str, Func)] = &[
("key_exists", key_exists as Func),
("campel_to_snake_case", camel_to_snake_case as Func),
];

/// TODO: descriptive comment needed about what function does!!
pub fn key_exists(
args: &[gtmpl_value::Value],
) -> Result<gtmpl_value::Value, gtmpl_value::FuncError> {
println!("{:?}", args);
if args.is_empty() {
return Err(gtmpl_value::FuncError::AtLeastXArgs(
"Need at least 1 arg for key exists".to_string(),
1,
));
}
let map = args[0].clone();
if args.len() == 1 {
return Ok(gtmpl_value::Value::Bool(true));
}
let keys = args[1..].to_vec();
// check if keys is empty
let rest_keys: Vec<gtmpl_value::Value> = match keys.len() > 1 {
false => vec![],
_ => keys[1..].to_vec(),
};

// extract first key
if !keys.is_empty() {
let key = keys[0].clone();
match key {
gtmpl_value::Value::String(s) => {
let res: Result<gtmpl_value::Value, gtmpl_value::FuncError> = match map {
gtmpl_value::Value::Object(o) => {
// call again with rest of keys
key_exists(
vec![vec![o.get(&s).unwrap().clone()], rest_keys]
.concat()
.as_slice(),
)
}
_ => Ok(gtmpl_value::Value::Bool(false)),
};
return res;
}
_ => {
return Err(gtmpl_value::FuncError::Generic(
"keys need to be string".to_string(),
));
}
}
}
Ok(gtmpl::Value::Bool(true))
}
gtmpl_fn!(
/// converts an `input` string from camelCase to snake_case
fn camel_to_snake_case(input: String) -> Result<String, FuncError> {
let mut snake_case = String::new();
let mut prev_char_lowercase = false;

for c in input.chars() {
if c.is_uppercase() {
if prev_char_lowercase {
snake_case.push('_');
}
snake_case.push(c.to_lowercase().next().unwrap());
prev_char_lowercase = false;
} else {
snake_case.push(c);
prev_char_lowercase = true;
}
}

Ok(snake_case)
}
);

0 comments on commit 30dd9f3

Please sign in to comment.