gitory-cli 0.1.0

Build a story for your project based on your git history
mod cli;
mod git;
mod llms;
mod utils;

use anyhow::Result;
use clap::Parser;
use dotenvy;
use git2::Repository;
use std::path::PathBuf;
use toml;

use crate::llms::gemini;

#[derive(Debug)]
struct AppConfig {
    max_commit_depth: usize,
    output_dir: PathBuf,
}

#[tokio::main]
async fn main() -> Result<()> {
    dotenvy::dotenv()?;

    let cli_args = cli::CliOptions::parse();
    match cli_args.choice {
        cli::Choice::Generate { config_file } => {
            let repo = Repository::open(&cli_args.project_path)?;
            let (app_config, llm_config) = utils::parse_gitory_config_file(&config_file)?;

            let app_config = {
                let output_dir = match app_config.get("output_dir") {
                    Some(output_dir) => {
                        let dir = &cli_args.project_path;
                        dir.join(output_dir.as_str().unwrap()).to_owned()
                    }
                    None => {
                        let dir = &cli_args.project_path;
                        dir.join("gitory").to_owned()
                    }
                };

                let max_commit_depth = if output_dir.exists() {
                    match app_config.get("max_commit_depth") {
                        Some(max_commit_depth) => match max_commit_depth.as_integer() {
                            Some(max_commit_depth) => max_commit_depth as usize,
                            None => 2,
                        },
                        None => 2,
                    }
                } else {
                    std::fs::create_dir_all(&output_dir)?;

                    // TODO: Uncomment the following line once chaining is implemented, for now defaulting to 5
                    // git::get_total_commit_count(&repo)?
                    10
                };

                AppConfig {
                    max_commit_depth,
                    output_dir,
                }
            };

            let (api_config, generation_config) =
                if llm_config["provider"] == toml::Value::String("google_gemini".to_string()) {
                    let api_config = &llm_config["api_config"];
                    let generation_config = &llm_config.get("generation_config");

                    utils::load_llm_configs_for_gemini(api_config, generation_config)?
                } else {
                    return Err(anyhow::anyhow!("Unsupported `provider`"));
                };

            let (parent_oid, merged_oid) = git::get_commit_oid(&repo, app_config.max_commit_depth)?;

            let commit_diff = git::get_commit_diff(&repo, &parent_oid, &merged_oid)?;

            let (from_timestamp, to_timestamp) =
                git::get_commit_timestamps(&repo, &parent_oid, &merged_oid)?;

            let response =
                gemini::sdk::generate_text_from_text(&commit_diff, &api_config, generation_config)
                    .await?;

            // println!("{:#?}", response);

            write_response_to_file(
                response,
                from_timestamp,
                to_timestamp,
                &app_config.output_dir,
            )?;
        }
    };

    Ok(())
}

fn write_response_to_file(
    response: Vec<String>,
    from: chrono::DateTime<chrono::Utc>,
    to: chrono::DateTime<chrono::Utc>,
    output_dir: &PathBuf,
) -> Result<()> {
    let file_name = format!(
        "{}_to_{}.md",
        from.format("%Y-%m-%d"),
        to.format("%Y-%m-%d")
    );

    let title_string = format!("## {} - {}", from.to_string(), to.to_string());
    let response_string = response.join("\n");

    let data = format!("{}\n\n{}", title_string, response_string);
    std::fs::write(output_dir.join(file_name), data).expect("Unable to write file");
    Ok(())
}