From c0ad6e67d2111e679459cb6c7a401c11991549f7 Mon Sep 17 00:00:00 2001 From: "Azat S." Date: Sun, 3 Nov 2024 20:43:18 +0300 Subject: [PATCH] feat: add csv output format --- Cargo.lock | 24 +++++++++- Cargo.toml | 1 + readme.md | 2 +- src/fs/copy_dir_recursive.rs | 11 +++-- src/fs/make_dir.rs | 21 ++++++++ src/fs/mod.rs | 2 + src/project/generate_output.rs | 88 ++++++++++++++++++++++++++++++---- src/project/parse_args.rs | 15 ++++++ src/types.rs | 1 + 9 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 src/fs/make_dir.rs create mode 100644 src/project/parse_args.rs diff --git a/Cargo.lock b/Cargo.lock index 914b643..26f0225 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -386,6 +386,27 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + [[package]] name = "ctrlc" version = "3.4.5" @@ -1651,6 +1672,7 @@ dependencies = [ "chrono", "clap 4.5.20", "cross", + "csv", "futures", "indicatif", "lazy_static", @@ -1833,7 +1855,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a155176..f7472ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" chrono = { version = "0.4.38", features = ["serde"] } clap = { version = "4.5.20", features = ["derive"] } cross = "0.2.5" +csv = "1.3.0" futures = "0.3.31" indicatif = "0.17.8" lazy_static = "1.5.0" diff --git a/readme.md b/readme.md index 1065ab5..248a1c3 100644 --- a/readme.md +++ b/readme.md @@ -193,7 +193,7 @@ todoctor --exclude-keywords WARNING --exclude-keywords DEPRECATED ### --output-format -You can specify the format of the report. Possible options are `html` and `json`. The default value is `html`. +You can specify the format of the report. Possible options are `html`, `json` and `csv`. The default value is `html`. Example: diff --git a/src/fs/copy_dir_recursive.rs b/src/fs/copy_dir_recursive.rs index 14c63b8..49ceb50 100644 --- a/src/fs/copy_dir_recursive.rs +++ b/src/fs/copy_dir_recursive.rs @@ -2,16 +2,19 @@ use futures::future::BoxFuture; use std::path::Path; use tokio::{fs, io}; -pub async fn copy_dir_recursive(src: &Path, dst: &Path) -> io::Result<()> { - if !dst.exists() { - fs::create_dir_all(dst).await?; +pub async fn copy_dir_recursive( + src: &Path, + output_dir: &Path, +) -> io::Result<()> { + if !output_dir.exists() { + fs::create_dir_all(output_dir).await?; } let mut entries = fs::read_dir(src).await?; while let Some(entry) = entries.next_entry().await? { let entry_path = entry.path(); let entry_name = entry.file_name(); - let dest_path = dst.join(entry_name); + let dest_path = output_dir.join(entry_name); if entry_path.is_dir() { copy_dir_recursive_boxed(&entry_path, &dest_path).await?; diff --git a/src/fs/make_dir.rs b/src/fs/make_dir.rs new file mode 100644 index 0000000..8e9822a --- /dev/null +++ b/src/fs/make_dir.rs @@ -0,0 +1,21 @@ +use tokio::fs; + +pub async fn make_dir(output_directory: &str) { + if fs::metadata(output_directory).await.is_ok() { + if let Err(e) = fs::remove_dir_all(output_directory).await { + eprintln!( + "Error removing output directory {}: {:?}", + output_directory, e + ); + return; + } + } + + if let Err(e) = fs::create_dir_all(output_directory).await { + eprintln!( + "Error creating output directory {}: {:?}", + output_directory, e + ); + return; + } +} diff --git a/src/fs/mod.rs b/src/fs/mod.rs index dcf44d9..c212e74 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -1,7 +1,9 @@ pub use self::copy_dir_recursive::copy_dir_recursive; pub use self::get_current_directory::get_current_directory; pub use self::get_dist_path::get_dist_path; +pub use self::make_dir::make_dir; pub mod copy_dir_recursive; pub mod get_current_directory; pub mod get_dist_path; +pub mod make_dir; diff --git a/src/project/generate_output.rs b/src/project/generate_output.rs index 3c71d17..b2d1841 100644 --- a/src/project/generate_output.rs +++ b/src/project/generate_output.rs @@ -1,12 +1,13 @@ -use crate::fs::{copy_dir_recursive, get_dist_path}; +use crate::fs::{copy_dir_recursive, get_dist_path, make_dir}; use crate::types::OutputFormat; use crate::utils::escape_json_values; +use csv::Writer; use open; use serde_json::Value; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; -use tokio::fs; +use tokio::{fs, task}; pub async fn generate_output( output_format: OutputFormat, @@ -18,6 +19,8 @@ pub async fn generate_output( let dist_path: PathBuf = get_dist_path() .expect("Error: Could not get current dist path."); + make_dir(output_directory).await; + copy_dir_recursive(&dist_path, Path::new(output_directory)) .await .expect("Error copying directory"); @@ -54,13 +57,7 @@ pub async fn generate_output( } } OutputFormat::Json => { - if let Err(e) = fs::create_dir_all(output_directory).await { - eprintln!( - "Error creating output directory {}: {:?}", - output_directory, e - ); - return; - } + make_dir(output_directory).await; let json_path = Path::new(output_directory).join("index.json"); let mut file = File::create(&json_path) @@ -71,5 +68,78 @@ pub async fn generate_output( file.write_all(formatted_json.as_bytes()) .expect("Failed to write JSON data"); } + OutputFormat::Csv => { + make_dir(output_directory).await; + + let csv_path = Path::new(output_directory).join("index.csv"); + + let json_data_clone = json_data.clone(); + + task::spawn_blocking(move || { + let file = File::create(&csv_path) + .expect("Failed to create CSV report file"); + + let mut wtr = Writer::from_writer(file); + + if let Some(data_array) = + json_data_clone.get("data").and_then(|d| d.as_array()) + { + wtr.write_record(&[ + "Path", + "Line", + "Kind", + "Comment", + "Author", + "Date", + "Commit Hash", + ]) + .expect("Failed to write CSV headers"); + + for item in data_array { + let path = item + .get("path") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let line = item + .get("line") + .and_then(|v| v.as_i64()) + .unwrap_or(0) + .to_string(); + let kind = item + .get("kind") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let comment = item + .get("comment") + .and_then(|v| v.as_str()) + .unwrap_or(""); + + let blame = item.get("blame").unwrap_or(&Value::Null); + let author = blame + .get("author") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let date = blame + .get("date") + .and_then(|v| v.as_str()) + .unwrap_or(""); + let hash = blame + .get("hash") + .and_then(|v| v.as_str()) + .unwrap_or(""); + + wtr.write_record(&[ + path, &line, kind, comment, author, date, hash, + ]) + .expect("Failed to write CSV record"); + } + } else { + eprintln!("No data found in json_data"); + } + wtr.flush().expect("Failed to flush CSV writer"); + }) + .await + .expect("Failed to write CSV data"); + } } } diff --git a/src/project/parse_args.rs b/src/project/parse_args.rs new file mode 100644 index 0000000..6a1ff88 --- /dev/null +++ b/src/project/parse_args.rs @@ -0,0 +1,15 @@ +fn parse_args(version: &str) -> Cli { + use clap::FromArgMatches; + + let version_static: &'static str = + Box::leak(version.to_string().into_boxed_str()); + + let mut cmd = Cli::command(); + cmd = cmd.version(version_static); + let matches = cmd.get_matches(); + + match Cli::from_arg_matches(&matches) { + Ok(cli) => cli, + Err(e) => e.exit(), + } +} diff --git a/src/types.rs b/src/types.rs index 8835819..8892d33 100644 --- a/src/types.rs +++ b/src/types.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; pub enum OutputFormat { Html, Json, + Csv, } #[derive(Debug, Serialize)]