Initial upload: GitPulse - Developer Productivity Analyzer CLI tool
This commit is contained in:
158
src/commands/export.rs
Normal file
158
src/commands/export.rs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
use crate::cli::export::ExportArgs;
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::git::Repository;
|
||||||
|
use crate::models::AnalysisResult;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub mod export_args {
|
||||||
|
use clap::{Args, Parser};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
pub struct ExportArgs {
|
||||||
|
#[arg(long, value_name = "FORMAT")]
|
||||||
|
#[arg(help = "Export format: json, csv")]
|
||||||
|
pub format: String,
|
||||||
|
|
||||||
|
#[arg(short, long, value_name = "PATH")]
|
||||||
|
#[arg(help = "Output file path")]
|
||||||
|
pub output: Option<PathBuf>,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
#[arg(help = "Include commit history")]
|
||||||
|
pub history: bool,
|
||||||
|
|
||||||
|
#[arg(long)]
|
||||||
|
#[arg(help = "Include refactoring data")]
|
||||||
|
pub refactor: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use export_args::ExportArgs;
|
||||||
|
|
||||||
|
pub fn run(
|
||||||
|
repo_path: Option<PathBuf>,
|
||||||
|
args: ExportArgs,
|
||||||
|
config: &Config,
|
||||||
|
verbose: bool,
|
||||||
|
) -> Result<()> {
|
||||||
|
let repo = Repository::new(repo_path)?;
|
||||||
|
|
||||||
|
if verbose {
|
||||||
|
eprintln!("Exporting from repository at: {}", repo.path().display());
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = crate::commands::analyze::run(
|
||||||
|
Some(repo.path().clone()),
|
||||||
|
crate::commands::analyze::AnalyzeArgs {
|
||||||
|
since: None,
|
||||||
|
until: None,
|
||||||
|
commits: None,
|
||||||
|
include_merges: false,
|
||||||
|
no_churn: false,
|
||||||
|
no_refactor: !args.refactor,
|
||||||
|
json: true,
|
||||||
|
history: args.history,
|
||||||
|
top: None,
|
||||||
|
output: None,
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
verbose,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
match args.format.to_lowercase().as_str() {
|
||||||
|
"json" | "j" => {
|
||||||
|
if let Some(output) = args.output {
|
||||||
|
export::export_json_to_file(&result, &output)?;
|
||||||
|
if verbose {
|
||||||
|
eprintln!("Exported to {}", output.display());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
export::export_json(&result, None)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"csv" | "c" => {
|
||||||
|
if let Some(output) = args.output {
|
||||||
|
export::export_csv_to_file(&result, &output)?;
|
||||||
|
if verbose {
|
||||||
|
eprintln!("Exported to {}", output.display());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("CSV export requires an output file. Use --output <file.csv>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
anyhow::bail!("Unsupported format: {}. Use 'json' or 'csv'.", args.format);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod export {
|
||||||
|
use crate::models::AnalysisResult;
|
||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde_json;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub fn export_json(result: &AnalysisResult, output: Option<PathBuf>) -> Result<()> {
|
||||||
|
let json = serde_json::to_string_pretty(result)
|
||||||
|
.context("Failed to serialize to JSON")?;
|
||||||
|
|
||||||
|
if let Some(path) = output {
|
||||||
|
let mut file = File::create(&path).context("Failed to create output file")?;
|
||||||
|
file.write_all(json.as_bytes())
|
||||||
|
.context("Failed to write to output file")?;
|
||||||
|
} else {
|
||||||
|
println!("{}", json);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_json_to_file(result: &AnalysisResult, path: &PathBuf) -> Result<()> {
|
||||||
|
export_json(result, Some(path.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_csv(result: &AnalysisResult, _output: &PathBuf) -> Result<()> {
|
||||||
|
println!("Contributors CSV:");
|
||||||
|
println!("Name,Email,Commits,Lines Added,Lines Removed,Net Change");
|
||||||
|
|
||||||
|
for author in &result.contributors.top_contributors {
|
||||||
|
println!(
|
||||||
|
"{},{},{},{},{},{}",
|
||||||
|
author.name,
|
||||||
|
author.email,
|
||||||
|
author.commits,
|
||||||
|
author.lines_added,
|
||||||
|
author.lines_removed,
|
||||||
|
author.net_change
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_csv_to_file(result: &AnalysisResult, path: &PathBuf) -> Result<()> {
|
||||||
|
let mut wtr = csv::Writer::from_path(path).context("Failed to create CSV writer")?;
|
||||||
|
|
||||||
|
wtr.write_record(&[
|
||||||
|
"Name", "Email", "Commits", "Lines Added", "Lines Removed", "Net Change",
|
||||||
|
])?;
|
||||||
|
|
||||||
|
for author in &result.contributors.top_contributors {
|
||||||
|
wtr.write_record(&[
|
||||||
|
&author.name,
|
||||||
|
&author.email,
|
||||||
|
&author.commits.to_string(),
|
||||||
|
&author.lines_added.to_string(),
|
||||||
|
&author.lines_removed.to_string(),
|
||||||
|
&author.net_change.to_string(),
|
||||||
|
])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
wtr.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user