From 28840b09921e78e188d41f90907a539b00fed31c Mon Sep 17 00:00:00 2001 From: Nanne Baars Date: Wed, 17 May 2023 08:40:23 +0200 Subject: [PATCH] feat: add handlebars template to generate Java code. Refs: #555 --- cli/Cargo.toml | 2 +- cli/src/challenge.rs | 110 ++++++++++++++++++++----------- cli/src/enums.rs | 2 +- cli/src/main.rs | 32 +++++---- src/main/resources/challenge.hbs | 5 +- 5 files changed, 95 insertions(+), 56 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1d2859b8d..3e49a1b7c 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -6,5 +6,5 @@ edition = "2021" [dependencies] clap = { version = "4.2.5", features = ["derive"] } walkdir = "2" -regex = "1.8.1" handlebars = "3" + diff --git a/cli/src/challenge.rs b/cli/src/challenge.rs index 21332921b..b9656230f 100644 --- a/cli/src/challenge.rs +++ b/cli/src/challenge.rs @@ -8,73 +8,103 @@ use walkdir::WalkDir; use crate::{Difficulty, Platform, Technology}; +#[derive(Debug)] pub struct Challenge { pub number: u8, pub technology: Technology, pub difficulty: Difficulty, - pub project_directory: PathBuf, pub platform: Platform, } -impl std::fmt::Display for Challenge { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "Technology: {}, Difficulty: {}", self.technology, self.difficulty) +impl Challenge { + fn create_java_sources(&self, project_directory: &PathBuf) { + let challenge_source_path = + project_directory + .join("src/main/java/org/owasp/wrongsecrets/challenges"); + + let (handlebars, data) = self.init_handlebars(project_directory); + let challenge_source_content = handlebars + .render("challenge", &data) + .expect("Unable to render challenge template"); + let mut class_file = File::create( + challenge_source_path + .join(self.platform.to_string()) + .join(format!("Challenge{}.java", &self.number.to_string())) + ) + .expect("Unable to create challenge source file"); + class_file + .write(challenge_source_content.as_bytes()) + .expect("Unable to write challenge source file"); } -} -pub fn create_challenge(challenge: &Challenge) { - let challenge_number = &challenge.number.to_string(); - let challenge_name = String::from("Challenge") + challenge_number + ".java"; - let challenge_exists = check_challenge_exists(challenge); + fn init_handlebars(&self, project_directory: &PathBuf) -> (Handlebars, BTreeMap) { + const CHALLENGE_TEMPLATE: &str = "src/main/resources/challenge.hbs"; + let mut handlebars = Handlebars::new(); + handlebars + .register_template_file( + "challenge", + project_directory.join(CHALLENGE_TEMPLATE), + ) + .unwrap(); + let mut data: BTreeMap = BTreeMap::new(); + data.insert("challenge_number".to_string(), self.number.to_string()); + data.insert("platform".to_string(), self.platform.to_string().to_lowercase()); + data.insert("difficulty".to_string(), self.difficulty.to_string().to_uppercase()); + data.insert("technology".to_string(), self.technology.to_string().to_uppercase()); - if challenge_exists { - panic!("{:?} already exists", challenge_name); + (handlebars, data) } - println!("Creating challenge {} in {}", challenge_number, challenge.project_directory.display()); - create_challenge_class_file(challenge, challenge_number, challenge_name); - create_documentation_files(challenge, challenge_number); -} - -fn create_documentation_files(challenge: &Challenge, challenge_number: &String) { - let challenge_documentation_path = challenge.project_directory.join("src/main/resources/explanations/"); - create_documentation_file(challenge_documentation_path.join(format!("challenge{}.adoc", challenge_number))); - create_documentation_file(challenge_documentation_path.join(format!("challenge{}_hint.adoc", challenge_number))); - create_documentation_file(challenge_documentation_path.join(format!("challenge{}_explanation.adoc", challenge_number))); + fn create_documentation(&self, project_directory: &PathBuf) { + let challenge_documentation_path = + project_directory + .join("src/main/resources/explanations/"); + create_documentation_file( + challenge_documentation_path.join(format!("challenge{}.adoc", self.number.to_string())), + ); + create_documentation_file( + challenge_documentation_path.join(format!("challenge{}_hint.adoc", self.number.to_string())), + ); + create_documentation_file( + challenge_documentation_path + .join(format!("challenge{}_explanation.adoc", self.number.to_string())), + ); + } } fn create_documentation_file(filename: PathBuf) { File::create(filename).expect("Unable to create challenge documentation file"); } -fn create_challenge_class_file(challenge: &Challenge, challenge_number: &String, challenge_name: String) { - const CHALLENGE_TEMPLATE: &str = "src/main/resources/challenge.hbs"; - let challenge_source_path = challenge.project_directory.join("src/main/java/org/owasp/wrongsecrets/challenges"); +pub fn create_challenge(challenge: &Challenge, project_directory: &PathBuf) { + let challenge_exists = check_challenge_exists(challenge, &project_directory); - let mut handlebars = Handlebars::new(); - handlebars.register_template_file("challenge", challenge.project_directory.join(CHALLENGE_TEMPLATE)).unwrap(); - let mut data = BTreeMap::new(); - data.insert("challenge_number".to_string(), challenge_number); - let challenge_source_content = handlebars.render("challenge", &data).expect("Unable to render challenge template"); - let mut class_file = File::create(challenge_source_path.join(challenge.platform.to_string()).join(challenge_name)).expect("Unable to create challenge source file"); - class_file.write(challenge_source_content.as_bytes()).expect("Unable to write challenge source file"); + if challenge_exists { + panic!("{:?} already exists", &challenge); + } + + println!( + "Creating {:?}", + &challenge + ); + challenge.create_java_sources(project_directory); + challenge.create_documentation(project_directory); } + //File API has `create_new` but it is still experimental in the nightly build, let loop and check if it exists for now -fn check_challenge_exists(challenge: &Challenge) -> bool { - let challenges_directory = challenge.project_directory.join("src/main/java/org/owasp/wrongsecrets/challenges"); +fn check_challenge_exists(challenge: &Challenge, project_directory: &PathBuf) -> bool { + let challenges_directory = + project_directory + .join("src/main/java/org/owasp/wrongsecrets/challenges"); let challenge_name = String::from("Challenge") + &challenge.number.to_string() + ".java"; let challenge_exists = WalkDir::new(challenges_directory) .into_iter() .filter_map(|e| e.ok()) - .any(|e| { - match e.file_name().to_str() { - None => { false } - Some(name) => { - name.eq(challenge_name.as_str()) - } - } + .any(|e| match e.file_name().to_str() { + None => false, + Some(name) => name.eq(challenge_name.as_str()), }); challenge_exists } diff --git a/cli/src/enums.rs b/cli/src/enums.rs index 6c202bbb4..7256cca09 100644 --- a/cli/src/enums.rs +++ b/cli/src/enums.rs @@ -40,7 +40,7 @@ pub enum Difficulty { pub enum Platform { Cloud, Docker, - Kubernetes + Kubernetes, } impl fmt::Display for Difficulty { diff --git a/cli/src/main.rs b/cli/src/main.rs index d8b96901d..99058bfa5 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,13 +1,13 @@ use std::path::PathBuf; -use clap::arg; use clap::{Parser, Subcommand}; +use clap::arg; use crate::challenge::Challenge; use crate::enums::{Difficulty, Platform, Technology}; -mod enums; mod challenge; +mod enums; #[derive(Debug, Parser)] #[command(name = "cli")] @@ -19,13 +19,14 @@ struct Cli { #[derive(Debug, Subcommand)] enum Commands { - #[command(arg_required_else_help = true, name = "challenge", about = "Create a new challenge")] + #[command( + arg_required_else_help = true, + name = "challenge", + about = "Create a new challenge" + )] ChallengeCommand { //We could infer this from the directory structure but another PR could already have added the challenge with this number - #[arg( - long, - short, - value_name = "NUMBER")] + #[arg(long, short, value_name = "NUMBER")] number: u8, #[arg( long, @@ -57,7 +58,7 @@ enum Commands { platform: Platform, #[arg(required = true)] project_directory: PathBuf, - } + }, } fn main() { @@ -68,11 +69,18 @@ fn main() { difficulty, technology, platform, - project_directory + project_directory, } => { - project_directory.try_exists().expect("Unable to find project directory"); - let challenge = Challenge { number, difficulty, technology, platform, project_directory }; - challenge::create_challenge(&challenge); + project_directory + .try_exists() + .expect("Unable to find project directory"); + let challenge = Challenge { + number, + difficulty, + technology, + platform, + }; + challenge::create_challenge(&challenge, &project_directory); } } } diff --git a/src/main/resources/challenge.hbs b/src/main/resources/challenge.hbs index 76b459c3a..28772ef6a 100644 --- a/src/main/resources/challenge.hbs +++ b/src/main/resources/challenge.hbs @@ -3,6 +3,7 @@ package org.owasp.wrongsecrets.challenges.{{platform}}; import org.owasp.wrongsecrets.RuntimeEnvironment; import org.owasp.wrongsecrets.ScoreCard; import org.owasp.wrongsecrets.challenges.Challenge; +import org.owasp.wrongsecrets.challenges.ChallengeTechnology; import org.owasp.wrongsecrets.challenges.Difficulty; import org.owasp.wrongsecrets.challenges.Spoiler; import org.springframework.core.annotation.Order; @@ -43,7 +44,7 @@ public class Challenge{{challenge_number}} extends Challenge { * {@inheritDoc} */ public List supportedRuntimeEnvironments() { - return List.of(DOCKER); + return List.of(RuntimeEnvironment.Environment.{{environment}}); } /** @@ -56,7 +57,7 @@ public class Challenge{{challenge_number}} extends Challenge { @Override public String getTech() { - return ChallengeTechnology.Tech{{technology}}.id; + return ChallengeTechnology.Tech.{{technology}}.id; } @Override