Skip to content

Commit

Permalink
feat: add csv output format
Browse files Browse the repository at this point in the history
  • Loading branch information
azat-io committed Nov 3, 2024
1 parent f15a43b commit c0ad6e6
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 15 deletions.
24 changes: 23 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
11 changes: 7 additions & 4 deletions src/fs/copy_dir_recursive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand Down
21 changes: 21 additions & 0 deletions src/fs/make_dir.rs
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 2 additions & 0 deletions src/fs/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
88 changes: 79 additions & 9 deletions src/project/generate_output.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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");
Expand Down Expand Up @@ -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)
Expand All @@ -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");
}
}
}
15 changes: 15 additions & 0 deletions src/project/parse_args.rs
Original file line number Diff line number Diff line change
@@ -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(),
}
}
1 change: 1 addition & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
pub enum OutputFormat {
Html,
Json,
Csv,
}

#[derive(Debug, Serialize)]
Expand Down

0 comments on commit c0ad6e6

Please sign in to comment.