commit-wizard 0.0.2

A lightweight CLI assistant for Conventional Commits, semantic versioning, and changelog automation.
Documentation
use std::time::Instant;

use crate::{
    core::{Context, CoreResult},
    engine::{
        capabilities::config::{
            edit,
            show::{ConfigShowFormat, ConfigShowInput, ConfigTarget, config_show as show_config},
        },
        constants::{PROJECT_CONFIG_FILE_NAME, app_config_dir, resolve_new_project_config_path},
    },
};

pub fn config_unset(ctx: &Context, key: &str, global: bool) -> CoreResult<()> {
    let ui = ctx.ui();
    let dry_run = ctx.dry_run();

    let scope = if global { "global" } else { "project" };
    let target = if global {
        ConfigTarget::Global
    } else {
        ConfigTarget::Project
    };

    let project_config_path = ctx.project_config_path();
    let input = edit::ConfigUnsetInput {
        cwd: ctx.cwd(),
        target,
        key,
        dry_run,
        explicit_path: project_config_path.as_deref(),
    };

    let start = Instant::now();
    let output = edit::config_unset(&input)?;
    let duration_ms = start.elapsed().as_millis() as u64;

    let meta = ui
        .new_output_meta()
        .with_duration_ms(duration_ms)
        .with_timestamp(chrono::Utc::now().to_string())
        .with_command("config.unset".to_string())
        .with_scope(scope.to_string())
        .with_dry_run(dry_run);

    let content = ui
        .new_output_content()
        .title(format!("Unset config `{}` in {} scope", output.key, scope))
        .subtitle(format!(
            "Path: {}, Dry run: {}",
            output.path.display(),
            ctx.dry_run()
        ))
        .data("key", output.key)
        .data("removed", output.removed)
        .data("path", output.path.display().to_string());

    ui.print_with_meta(&content, Some(&meta), true)
}

pub fn config_set(ctx: &Context, key: &str, value: &str, global: bool) -> CoreResult<()> {
    let ui = ctx.ui();
    let dry_run = ctx.dry_run();
    let scope = if global { "global" } else { "project" };
    let target = if global {
        ConfigTarget::Global
    } else {
        ConfigTarget::Project
    };
    let project_config_path = ctx.project_config_path();
    let input = edit::ConfigSetInput {
        cwd: ctx.cwd(),
        target,
        key,
        value,
        dry_run,
        explicit_path: project_config_path.as_deref(),
    };

    let start = Instant::now();
    let output = edit::config_set(&input)?;
    let duration_ms = start.elapsed().as_millis() as u64;

    let meta = ui
        .new_output_meta()
        .with_duration_ms(duration_ms)
        .with_timestamp(chrono::Utc::now().to_string())
        .with_command("config.set".to_string())
        .with_scope(scope.to_string())
        .with_dry_run(dry_run);

    let content = ui
        .new_output_content()
        .title(format!("Set config `{}` in {} scope", output.key, scope))
        .subtitle(format!(
            "Path: {}, Dry run: {}",
            output.path.display(),
            ctx.dry_run()
        ))
        .data("key", output.key)
        .data("value", output.value)
        .data("path", output.path.display().to_string());

    ui.print_with_meta(&content, Some(&meta), true)
}

pub fn config_path(ctx: &Context, global: bool) -> CoreResult<()> {
    let ui = ctx.ui();
    let scope = if global { "global" } else { "project" };

    let path = if global {
        app_config_dir()?.join(PROJECT_CONFIG_FILE_NAME)
    } else {
        let project_config_path = ctx.project_config_path();
        ctx.project_config_path().unwrap_or_else(|| {
            resolve_new_project_config_path(
                ctx.cwd(),
                Some(ctx.repo_root().as_path()),
                ctx.in_git_repo(),
                project_config_path.as_deref(),
                true,
            )
        })
    };

    let meta = ui
        .new_output_meta()
        .with_timestamp(chrono::Utc::now().to_string())
        .with_command("config.path".to_string())
        .with_scope(scope.to_string());

    let content = ui
        .new_output_content()
        .title(format!("Config path for {} scope", scope))
        .data("path", path.display().to_string());

    ui.print_with_meta(&content, Some(&meta), true)
}

pub fn config_show(ctx: &Context, global: bool) -> CoreResult<()> {
    let ui = ctx.ui();
    let scope = if global { "global" } else { "project" };
    let target = if global {
        ConfigTarget::Global
    } else {
        ConfigTarget::Project
    };
    let project_config_path = ctx.project_config_path();
    let input = ConfigShowInput {
        cwd: ctx.cwd(),
        target,
        explicit_path: project_config_path.as_deref(),
        format: ConfigShowFormat::Human,
    };

    let start = Instant::now();
    let output = show_config(&input)?;
    let duration_ms = start.elapsed().as_millis() as u64;

    let meta = ui
        .new_output_meta()
        .with_duration_ms(duration_ms)
        .with_timestamp(chrono::Utc::now().to_string())
        .with_command("config.show".to_string())
        .with_scope(scope.to_string());

    let content = ui
        .new_output_content()
        .title(format!("Config for {} scope", scope))
        .subtitle(format!("Path: {}", output.path.display()))
        .data("path", output.path.display().to_string())
        .data("exists", output.exists)
        .data("content", output.content);

    ui.print_with_meta(&content, Some(&meta), true)
}

pub fn config_get(ctx: &Context, key: &str, global: bool) -> CoreResult<()> {
    let ui = ctx.ui();
    let scope = if global { "global" } else { "project" };
    let target = if global {
        ConfigTarget::Global
    } else {
        ConfigTarget::Project
    };
    let project_config_path = ctx.project_config_path();
    let input = edit::ConfigGetInput {
        cwd: ctx.cwd(),
        target,
        key,
        explicit_path: project_config_path.as_deref(),
    };

    let start = Instant::now();
    let output = edit::config_get(&input)?;
    let duration_ms = start.elapsed().as_millis() as u64;

    let meta = ui
        .new_output_meta()
        .with_duration_ms(duration_ms)
        .with_timestamp(chrono::Utc::now().to_string())
        .with_command("config.get".to_string())
        .with_scope(scope.to_string());

    let content = ui
        .new_output_content()
        .title(format!("Config `{}` in {} scope", output.key, scope))
        .data("key", output.key)
        .data("value", output.value.to_string())
        .data("path", output.path.display().to_string());

    ui.print_with_meta(&content, Some(&meta), true)
}