Skip to content

Commit c0ad6e6

Browse files
committed
feat: add csv output format
1 parent f15a43b commit c0ad6e6

File tree

9 files changed

+150
-15
lines changed

9 files changed

+150
-15
lines changed

Cargo.lock

Lines changed: 23 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ edition = "2021"
77
chrono = { version = "0.4.38", features = ["serde"] }
88
clap = { version = "4.5.20", features = ["derive"] }
99
cross = "0.2.5"
10+
csv = "1.3.0"
1011
futures = "0.3.31"
1112
indicatif = "0.17.8"
1213
lazy_static = "1.5.0"

readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ todoctor --exclude-keywords WARNING --exclude-keywords DEPRECATED
193193

194194
### --output-format
195195

196-
You can specify the format of the report. Possible options are `html` and `json`. The default value is `html`.
196+
You can specify the format of the report. Possible options are `html`, `json` and `csv`. The default value is `html`.
197197

198198
Example:
199199

src/fs/copy_dir_recursive.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,19 @@ use futures::future::BoxFuture;
22
use std::path::Path;
33
use tokio::{fs, io};
44

5-
pub async fn copy_dir_recursive(src: &Path, dst: &Path) -> io::Result<()> {
6-
if !dst.exists() {
7-
fs::create_dir_all(dst).await?;
5+
pub async fn copy_dir_recursive(
6+
src: &Path,
7+
output_dir: &Path,
8+
) -> io::Result<()> {
9+
if !output_dir.exists() {
10+
fs::create_dir_all(output_dir).await?;
811
}
912

1013
let mut entries = fs::read_dir(src).await?;
1114
while let Some(entry) = entries.next_entry().await? {
1215
let entry_path = entry.path();
1316
let entry_name = entry.file_name();
14-
let dest_path = dst.join(entry_name);
17+
let dest_path = output_dir.join(entry_name);
1518

1619
if entry_path.is_dir() {
1720
copy_dir_recursive_boxed(&entry_path, &dest_path).await?;

src/fs/make_dir.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use tokio::fs;
2+
3+
pub async fn make_dir(output_directory: &str) {
4+
if fs::metadata(output_directory).await.is_ok() {
5+
if let Err(e) = fs::remove_dir_all(output_directory).await {
6+
eprintln!(
7+
"Error removing output directory {}: {:?}",
8+
output_directory, e
9+
);
10+
return;
11+
}
12+
}
13+
14+
if let Err(e) = fs::create_dir_all(output_directory).await {
15+
eprintln!(
16+
"Error creating output directory {}: {:?}",
17+
output_directory, e
18+
);
19+
return;
20+
}
21+
}

src/fs/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
pub use self::copy_dir_recursive::copy_dir_recursive;
22
pub use self::get_current_directory::get_current_directory;
33
pub use self::get_dist_path::get_dist_path;
4+
pub use self::make_dir::make_dir;
45

56
pub mod copy_dir_recursive;
67
pub mod get_current_directory;
78
pub mod get_dist_path;
9+
pub mod make_dir;

src/project/generate_output.rs

Lines changed: 79 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
use crate::fs::{copy_dir_recursive, get_dist_path};
1+
use crate::fs::{copy_dir_recursive, get_dist_path, make_dir};
22
use crate::types::OutputFormat;
33
use crate::utils::escape_json_values;
4+
use csv::Writer;
45
use open;
56
use serde_json::Value;
67
use std::fs::File;
78
use std::io::Write;
89
use std::path::{Path, PathBuf};
9-
use tokio::fs;
10+
use tokio::{fs, task};
1011

1112
pub async fn generate_output(
1213
output_format: OutputFormat,
@@ -18,6 +19,8 @@ pub async fn generate_output(
1819
let dist_path: PathBuf = get_dist_path()
1920
.expect("Error: Could not get current dist path.");
2021

22+
make_dir(output_directory).await;
23+
2124
copy_dir_recursive(&dist_path, Path::new(output_directory))
2225
.await
2326
.expect("Error copying directory");
@@ -54,13 +57,7 @@ pub async fn generate_output(
5457
}
5558
}
5659
OutputFormat::Json => {
57-
if let Err(e) = fs::create_dir_all(output_directory).await {
58-
eprintln!(
59-
"Error creating output directory {}: {:?}",
60-
output_directory, e
61-
);
62-
return;
63-
}
60+
make_dir(output_directory).await;
6461

6562
let json_path = Path::new(output_directory).join("index.json");
6663
let mut file = File::create(&json_path)
@@ -71,5 +68,78 @@ pub async fn generate_output(
7168
file.write_all(formatted_json.as_bytes())
7269
.expect("Failed to write JSON data");
7370
}
71+
OutputFormat::Csv => {
72+
make_dir(output_directory).await;
73+
74+
let csv_path = Path::new(output_directory).join("index.csv");
75+
76+
let json_data_clone = json_data.clone();
77+
78+
task::spawn_blocking(move || {
79+
let file = File::create(&csv_path)
80+
.expect("Failed to create CSV report file");
81+
82+
let mut wtr = Writer::from_writer(file);
83+
84+
if let Some(data_array) =
85+
json_data_clone.get("data").and_then(|d| d.as_array())
86+
{
87+
wtr.write_record(&[
88+
"Path",
89+
"Line",
90+
"Kind",
91+
"Comment",
92+
"Author",
93+
"Date",
94+
"Commit Hash",
95+
])
96+
.expect("Failed to write CSV headers");
97+
98+
for item in data_array {
99+
let path = item
100+
.get("path")
101+
.and_then(|v| v.as_str())
102+
.unwrap_or("");
103+
let line = item
104+
.get("line")
105+
.and_then(|v| v.as_i64())
106+
.unwrap_or(0)
107+
.to_string();
108+
let kind = item
109+
.get("kind")
110+
.and_then(|v| v.as_str())
111+
.unwrap_or("");
112+
let comment = item
113+
.get("comment")
114+
.and_then(|v| v.as_str())
115+
.unwrap_or("");
116+
117+
let blame = item.get("blame").unwrap_or(&Value::Null);
118+
let author = blame
119+
.get("author")
120+
.and_then(|v| v.as_str())
121+
.unwrap_or("");
122+
let date = blame
123+
.get("date")
124+
.and_then(|v| v.as_str())
125+
.unwrap_or("");
126+
let hash = blame
127+
.get("hash")
128+
.and_then(|v| v.as_str())
129+
.unwrap_or("");
130+
131+
wtr.write_record(&[
132+
path, &line, kind, comment, author, date, hash,
133+
])
134+
.expect("Failed to write CSV record");
135+
}
136+
} else {
137+
eprintln!("No data found in json_data");
138+
}
139+
wtr.flush().expect("Failed to flush CSV writer");
140+
})
141+
.await
142+
.expect("Failed to write CSV data");
143+
}
74144
}
75145
}

src/project/parse_args.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
fn parse_args(version: &str) -> Cli {
2+
use clap::FromArgMatches;
3+
4+
let version_static: &'static str =
5+
Box::leak(version.to_string().into_boxed_str());
6+
7+
let mut cmd = Cli::command();
8+
cmd = cmd.version(version_static);
9+
let matches = cmd.get_matches();
10+
11+
match Cli::from_arg_matches(&matches) {
12+
Ok(cli) => cli,
13+
Err(e) => e.exit(),
14+
}
15+
}

src/types.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
66
pub enum OutputFormat {
77
Html,
88
Json,
9+
Csv,
910
}
1011

1112
#[derive(Debug, Serialize)]

0 commit comments

Comments
 (0)