From 5678fea4cc11b7a792c47b5fbc3435f827c01dd0 Mon Sep 17 00:00:00 2001 From: Jumaru Date: Mon, 30 Oct 2023 00:51:18 +0200 Subject: [PATCH 1/2] style(cli): :technologist: Apply fmt --- tests/circuit_command_tests.rs | 8 ++------ tests/compile_command_tests.rs | 15 +++++---------- tests/version_command_tests.rs | 15 +-------------- 3 files changed, 8 insertions(+), 30 deletions(-) diff --git a/tests/circuit_command_tests.rs b/tests/circuit_command_tests.rs index 38f4ddf..e138b1c 100644 --- a/tests/circuit_command_tests.rs +++ b/tests/circuit_command_tests.rs @@ -1,11 +1,9 @@ #[cfg(test)] -/// Module for testing the circuit command functionality. mod tests { use assert_cmd::Command; use std::fs; use std::path::Path; - /// Test to ensure the circuit command creates the circuit file with correct content #[test] fn test_circuit_command() { // Arrange @@ -13,10 +11,10 @@ mod tests { let circuit_path = current_dir.join("circuit.circom"); let expected_content = include_bytes!("../templates/circuit.circom"); - // Act: Execute the circuit command + // Act execute_circuit_command(); - // Assert: Verify the content of the created file + // Assert let actual_content = fs::read(&circuit_path).unwrap(); assert_files_match(expected_content, &actual_content, &circuit_path); @@ -24,13 +22,11 @@ mod tests { fs::remove_file(circuit_path).unwrap(); } - // Function to execute the circuit command fn execute_circuit_command() { let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); cmd.arg("circuit").assert().success(); } - // Function to compare the content of the files fn assert_files_match(expected_content: &[u8], actual_content: &[u8], file_path: &Path) { assert!(file_path.exists()); assert_eq!(expected_content, actual_content); diff --git a/tests/compile_command_tests.rs b/tests/compile_command_tests.rs index 63fbce3..5ebcb67 100644 --- a/tests/compile_command_tests.rs +++ b/tests/compile_command_tests.rs @@ -1,39 +1,35 @@ #[cfg(test)] -/// Module for testing the compile command functionality. mod tests { use assert_cmd::Command; use std::fs; use std::path::Path; - /// Test to ensure the compile command compiles the circuit file and produces the expected files #[test] fn test_compile_command() { - // Arrange: Create the circuit file + // Arrange let current_dir = std::env::current_dir().unwrap(); create_circuit_file(¤t_dir); - // Act: Execute the compile command + // Act execute_compile_command(¤t_dir); - // Assert: Verify the compiled files exist + // Assert assert_compiled_files_exist(¤t_dir); - // Clean up: Remove the created and compiled files + // Clean up clean_up_files(¤t_dir); } - // Function to create a circuit file in the specified directory fn create_circuit_file(dir: &Path) { let circuit_path = dir.join("circuit.circom"); fs::write(&circuit_path, include_bytes!("../templates/circuit.circom")).unwrap(); } - // Function to execute the compile command fn execute_compile_command(dir: &Path) { let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); cmd.current_dir(dir).arg("compile").assert().success(); } - // Function to assert that the compiled files exist + fn assert_compiled_files_exist(dir: &Path) { let r1cs_path = dir.join("circuit.r1cs"); let sym_path = dir.join("circuit.sym"); @@ -43,7 +39,6 @@ mod tests { assert!(wasm_dir_path.exists()); } - // Function to clean up the created and compiled files fn clean_up_files(dir: &Path) { fs::remove_file(dir.join("circuit.circom")).unwrap(); fs::remove_file(dir.join("circuit.r1cs")).unwrap(); diff --git a/tests/version_command_tests.rs b/tests/version_command_tests.rs index 9dd113e..3b22cd2 100644 --- a/tests/version_command_tests.rs +++ b/tests/version_command_tests.rs @@ -1,29 +1,16 @@ -// Conditional compilation directive for running code only in test context #[cfg(test)] -/// Module for testing the version command functionality. mod tests { - // Importing the necessary library for command assertion + // use super::*; use assert_cmd::Command; - /// Test function to check the version command output #[test] fn test_version_command() { - // Creating a command object for the binary let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - - // Executing the version command with --version flag - // and asserting the command runs successfully and outputs the correct version cmd.arg("--version") .assert() .success() .stdout(predicates::str::contains(env!("CARGO_PKG_VERSION"))); - // Re-creating the command object for executing another command - // (as Command object takes ownership of its arguments) - let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); - - // Executing the version command with --v flag - // and asserting the command runs successfully and outputs the correct version cmd.arg("--v") .assert() .success() From 09d03e0de6648f463b7006806fd49a09d1b22b2f Mon Sep 17 00:00:00 2001 From: Jumaru Date: Mon, 30 Oct 2023 00:55:14 +0200 Subject: [PATCH 2/2] feat(cli): :building_construction: Migrate CLI Argument Parsing to Clap Migrate the CLI argument parsing to use the clap library, enhancing the CLI's usability and maintainability. This change introduces a structured way of handling command-line arguments and subcommands, making it easier to extend the CLI with new features in the future. Additionally, this migration facilitates better error handling and provides users with more informative help messages. - Implement `Cli` struct and `SubCommand` enum for structured argument parsing - Update `main` function to use clap for argument handling - Replace manual argument parsing with clap's automatic parsing - Improve error messages and help output by leveraging clap's built-in functionalities --- Cargo.lock | 173 ++++++++++++++++++++++++++++ Cargo.toml | 3 +- src/main.rs | 84 +++++++------- tests/clap_general_commands_test.rs | 40 +++++++ 4 files changed, 255 insertions(+), 45 deletions(-) create mode 100644 tests/clap_general_commands_test.rs diff --git a/Cargo.lock b/Cargo.lock index a86d9e0..e1a0b31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,12 +11,54 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "assert_cmd" version = "2.0.12" @@ -49,6 +91,52 @@ dependencies = [ "serde", ] +[[package]] +name = "clap" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "difflib" version = "0.4.0" @@ -76,6 +164,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "itertools" version = "0.11.0" @@ -210,6 +304,12 @@ dependencies = [ "syn", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.38" @@ -233,6 +333,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -242,10 +348,77 @@ dependencies = [ "libc", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "zk_whitelist" version = "1.2.1" dependencies = [ "assert_cmd", + "clap", "predicates", ] diff --git a/Cargo.toml b/Cargo.toml index 2301448..5a4c26b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +clap = { version= "4.4.7", features = ["derive"] } [dev-dependencies] assert_cmd = "2.0.12" -predicates = "3.0.4" \ No newline at end of file +predicates = "3.0.4" diff --git a/src/main.rs b/src/main.rs index 1d76440..66789aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,77 +1,73 @@ +use clap::{Parser, Subcommand}; use std::env; use std::fs::File; use std::io::{self, Write}; use std::path::Path; use std::process::Command; -/// Entry point of the application. -/// -/// This function processes command line arguments to determine which action to perform. +/// Represents the command line interface for the Zero Knowledge Whitelist Tool. +/// Deriving `Parser` from clap allows for automatic parsing of command line arguments. +#[derive(Parser)] +#[clap( + name = "Zero Knowledge Whitelist Tool", + version = env!("CARGO_PKG_VERSION"), + author = "Nikos Koumbakis ", + about = "This tool orchestrates the management of an address whitelist using Zero-Knowledge (ZK) proofs.\nSimply input the addresses, and it will generate the corresponding Solidity code.\nIt streamlines the process of maintaining a secure and efficient whitelist for your decentralized application." +)] +struct Cli { + /// The subcommand to be executed, parsed from the command line arguments. + #[clap(subcommand)] + subcmd: SubCommand, +} + +/// Enumerates the available subcommands. +/// Deriving `Subcommand` from clap provides automatic subcommand handling. +#[derive(Subcommand)] +enum SubCommand { + /// The `circuit` subcommand copies a circuit file template to the current directory. + Circuit, + /// The `compile` subcommand compiles the circuit file. + Compile, +} +/// The entry point of the application. +/// Parses command line arguments and executes the corresponding subcommand. fn main() -> io::Result<()> { - // Collect command line arguments - let args: Vec = env::args().collect(); - // Check for version flag and display version if present - if args.contains(&"--version".to_string()) || args.contains(&"--v".to_string()) { - display_version(); - } - // Check for circuit command and copy circuit file if present - else if args.contains(&"circuit".to_string()) { - copy_circuit_file()?; - } - // Check for compile command and compile circuit if present - else if args.contains(&"compile".to_string()) { - compile_circuit()?; + let args = Cli::parse(); + + match args.subcmd { + SubCommand::Circuit => copy_circuit_file()?, + SubCommand::Compile => compile_circuit()?, } Ok(()) } -/// Displays the version of the tool to the console. -fn display_version() { - println!( - "Zero Knowledge Whitelist Tool, version {}", - env!("CARGO_PKG_VERSION") - ); -} - -/// Copies the circuit file template to the current directory. -/// -/// # Errors -/// -/// Returns an error if there is a problem accessing the current directory or writing the file. +/// Copies a circuit file template to the current directory. +/// This function is called when the `circuit` subcommand is used. fn copy_circuit_file() -> io::Result<()> { - // Getting the current directory let current_dir = env::current_dir()?; - // Defining the path for the circuit file let circuit_path = Path::new(¤t_dir).join("circuit.circom"); - // Creating and writing to the circuit file let mut file = File::create(circuit_path)?; file.write_all(include_bytes!("../templates/circuit.circom"))?; Ok(()) } /// Compiles the circuit file using the circom compiler. -/// -/// # Errors -/// -/// Returns an error if the compilation fails. +/// This function is called when the `compile` subcommand is used. fn compile_circuit() -> io::Result<()> { - // Getting the current directory let current_dir = env::current_dir()?; - // Executing the circom compiler with required arguments + eprintln!("Working directory: {:?}", current_dir); let output = Command::new("circom") .current_dir(¤t_dir) .arg("circuit.circom") .args(&["--r1cs", "--sym", "--wasm"]) .output()?; - // Checking for compilation success + eprintln!("circom stdout: {}", String::from_utf8_lossy(&output.stdout)); + eprintln!("circom stderr: {}", String::from_utf8_lossy(&output.stderr)); + if !output.status.success() { - let error_message = format!( - "Compilation failed with error: {}", - String::from_utf8_lossy(&output.stderr) - ); - return Err(io::Error::new(io::ErrorKind::Other, error_message)); + return Err(io::Error::new(io::ErrorKind::Other, "Compilation failed")); } Ok(()) diff --git a/tests/clap_general_commands_test.rs b/tests/clap_general_commands_test.rs new file mode 100644 index 0000000..449cf27 --- /dev/null +++ b/tests/clap_general_commands_test.rs @@ -0,0 +1,40 @@ +/// Module for CLI-related tests. +/// +/// This module contains tests that verify the behavior of the command-line +/// interface, including argument parsing and error handling. +#[cfg(test)] +mod tests { + use assert_cmd::Command; + use predicates::str::contains; + + /// Tests the output of the `--help` flag. + /// + /// This test verifies that the `--help` flag produces the expected output, + /// including a portion of the about message and the names of the available + /// subcommands. + #[test] + fn test_help_flag() { + let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); + cmd.arg("--help") + .assert() + .success() + .stdout(contains( + "orchestrates the management of an address whitelist", + )) + .stdout(contains("circuit")) + .stdout(contains("compile")); + } + + /// Tests the error message for an unrecognized subcommand. + /// + /// This test verifies that using an unrecognized subcommand results in an + /// error message indicating that the subcommand is not recognized. + #[test] + fn test_unrecognized_subcommand() { + let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).unwrap(); + cmd.arg("unrecognized") + .assert() + .failure() + .stderr(contains("error: unrecognized subcommand 'unrecognized'")); + } +}