From 3f209155457a32c27a2a0a313b2c3e31fe479b26 Mon Sep 17 00:00:00 2001 From: Mads Hougesen Date: Sun, 29 Oct 2023 22:10:37 +0100 Subject: [PATCH] feat(cli): allow running all directory entries (#22) --- hitt-cli/Cargo.toml | 1 + hitt-cli/src/config/mod.rs | 4 ++ hitt-cli/src/fs/mod.rs | 88 ++++++++++++++++++++++++++++++++ hitt-cli/src/main.rs | 49 ++++++++++-------- hitt-cli/src/printing/body.rs | 2 +- hitt-cli/src/printing/headers.rs | 4 +- hitt-cli/src/printing/mod.rs | 9 ++-- 7 files changed, 130 insertions(+), 27 deletions(-) create mode 100644 hitt-cli/src/fs/mod.rs diff --git a/hitt-cli/Cargo.toml b/hitt-cli/Cargo.toml index 7a65957..a5efdde 100644 --- a/hitt-cli/Cargo.toml +++ b/hitt-cli/Cargo.toml @@ -8,6 +8,7 @@ name = "hitt" path = "src/main.rs" [dependencies] +async-recursion = "1.0.5" clap = { version = "4.3.24", features = ["derive"] } hitt-parser = { path = "../hitt-parser" } hitt-request = { path = "../hitt-request" } diff --git a/hitt-cli/src/config/mod.rs b/hitt-cli/src/config/mod.rs index 8618ad3..9a8e6ce 100644 --- a/hitt-cli/src/config/mod.rs +++ b/hitt-cli/src/config/mod.rs @@ -24,4 +24,8 @@ pub(crate) struct CliArguments { /// Disable pretty printing of response body #[arg(long, default_value_t = false)] pub(crate) disable_formatting: bool, + + /// Enable to run directory recursively + #[arg(long, short, default_value_t = false)] + pub(crate) recursive: bool, } diff --git a/hitt-cli/src/fs/mod.rs b/hitt-cli/src/fs/mod.rs new file mode 100644 index 0000000..2ca775d --- /dev/null +++ b/hitt-cli/src/fs/mod.rs @@ -0,0 +1,88 @@ +use async_recursion::async_recursion; +use hitt_request::send_request; + +use crate::{ + config::CliArguments, + printing::{handle_response, print_error}, +}; + +async fn get_file_content(path: std::path::PathBuf) -> Result { + let buffr = tokio::fs::read(path).await?; + + Ok(String::from_utf8_lossy(&buffr).to_string()) +} + +pub(crate) async fn is_directory(path: &std::path::Path) -> Result { + tokio::fs::metadata(path).await.map(|m| m.is_dir()) +} + +pub(crate) async fn handle_file( + http_client: &reqwest::Client, + path: std::path::PathBuf, + args: &CliArguments, +) -> Result<(), std::io::Error> { + println!("hitt: running {path:?}"); + + let fcontent = get_file_content(path).await?; + + for req in hitt_parser::parse_requests(&fcontent).unwrap() { + match send_request(http_client, &req).await { + Ok(response) => handle_response(response, args), + Err(request_error) => { + print_error(format!( + "error sending request {} {}\n{request_error:#?}", + req.method, req.uri, + )); + std::process::exit(1); + } + } + } + + Ok(()) +} + +async fn handle_dir_entry( + http_client: &reqwest::Client, + entry: tokio::fs::DirEntry, + args: &CliArguments, +) -> Result<(), std::io::Error> { + let metadata = entry.metadata().await?; + + match metadata.is_dir() { + true => handle_dir(http_client, entry.path(), args).await, + false => { + let entry_path = entry.path(); + + if let Some(ext) = entry_path.extension() { + if ext == "http" { + return handle_file(http_client, entry_path, args).await; + } + } + + Ok(()) + } + } +} + +#[async_recursion] +pub(crate) async fn handle_dir( + http_client: &reqwest::Client, + path: std::path::PathBuf, + args: &CliArguments, +) -> Result<(), std::io::Error> { + if !args.recursive { + print_error(format!( + "{path:?} is a directory, but the recursive argument was not supplied" + )); + + std::process::exit(1); + } + + let mut read_dir_result = tokio::fs::read_dir(&path).await?; + + while let Some(entry) = read_dir_result.next_entry().await? { + handle_dir_entry(http_client, entry, args).await?; + } + + Ok(()) +} diff --git a/hitt-cli/src/main.rs b/hitt-cli/src/main.rs index fc9ee22..efa7a0e 100644 --- a/hitt-cli/src/main.rs +++ b/hitt-cli/src/main.rs @@ -2,39 +2,44 @@ use std::str::FromStr; use clap::Parser; use config::CliArguments; -use hitt_request::send_request; -use printing::print_response; +use fs::{handle_dir, handle_file, is_directory}; +use printing::print_error; mod config; +mod fs; mod printing; -async fn get_file_content(path: std::path::PathBuf) -> Result { - let buffr = tokio::fs::read(path).await?; - - Ok(String::from_utf8_lossy(&buffr).to_string()) +async fn run( + http_client: &reqwest::Client, + path: std::path::PathBuf, + args: &CliArguments, +) -> Result<(), std::io::Error> { + match is_directory(&path).await { + Ok(true) => handle_dir(http_client, path, args).await, + Ok(false) => handle_file(http_client, path, args).await, + Err(io_error) => { + print_error(format!( + "error checking if {path:?} is a directory\n{io_error:#?}" + )); + std::process::exit(1); + } + } } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), std::io::Error> { let args = CliArguments::parse(); let http_client = reqwest::Client::new(); - let path = std::path::PathBuf::from_str(&args.path)?; - - let fcontent = get_file_content(path).await?; - - for req in hitt_parser::parse_requests(&fcontent).unwrap() { - match send_request(&http_client, &req).await { - Ok(response) => { - print_response(response, &args); - } - Err(request_error) => panic!( - "Error sending request {} {}\n{:#?}", - req.method, req.uri, request_error, - ), + match std::path::PathBuf::from_str(&args.path) { + Ok(path) => run(&http_client, path, &args).await, + Err(parse_path_error) => { + print_error(format!( + "error parsing path {} as filepath\n{parse_path_error:#?}", + args.path + )); + std::process::exit(1); } } - - Ok(()) } diff --git a/hitt-cli/src/printing/body.rs b/hitt-cli/src/printing/body.rs index b2cf33f..d74c887 100644 --- a/hitt-cli/src/printing/body.rs +++ b/hitt-cli/src/printing/body.rs @@ -7,7 +7,7 @@ fn format_json(input: &str) -> String { #[inline] fn __print_body(body: &str) { - println!("{TEXT_YELLOW}{body}{TEXT_RESET}") + println!("\n{TEXT_YELLOW}{body}{TEXT_RESET}") } #[inline] diff --git a/hitt-cli/src/printing/headers.rs b/hitt-cli/src/printing/headers.rs index 2e5b25c..01f4277 100644 --- a/hitt-cli/src/printing/headers.rs +++ b/hitt-cli/src/printing/headers.rs @@ -1,12 +1,14 @@ use crate::printing::{TEXT_RESET, TEXT_YELLOW}; +use super::print_error; + #[inline] pub(crate) fn print_headers(headers: &reqwest::header::HeaderMap) { for (key, value) in headers { if let Ok(value) = value.to_str() { println!("{TEXT_YELLOW}{key}{TEXT_RESET}: {value}{TEXT_RESET}"); } else { - eprintln!("Error printing value for header: {key}"); + print_error(format!("error printing value for header: {key}")); } } } diff --git a/hitt-cli/src/printing/mod.rs b/hitt-cli/src/printing/mod.rs index 6bf7ea6..8cde942 100644 --- a/hitt-cli/src/printing/mod.rs +++ b/hitt-cli/src/printing/mod.rs @@ -23,7 +23,7 @@ pub const TEXT_YELLOW: &str = "\x1B[33m"; pub const TEXT_RESET: &str = "\x1B[39m"; -pub(crate) fn print_response(response: HittResponse, args: &CliArguments) { +pub(crate) fn handle_response(response: HittResponse, args: &CliArguments) { print_status( &response.http_version, &response.method, @@ -41,8 +41,6 @@ pub(crate) fn print_response(response: HittResponse, args: &CliArguments) { .get("content-type") .map(|x| x.to_str().expect("response content-type to be valid")); - println!(); - print_body(&response.body, content_type, args.disable_formatting); } @@ -53,3 +51,8 @@ pub(crate) fn print_response(response: HittResponse, args: &CliArguments) { std::process::exit(0); } } + +#[inline] +pub fn print_error(message: String) { + eprintln!("{TEXT_RED}hitt: {message}{TEXT_RESET}") +}