Skip to main content

git_iris/
common.rs

1use crate::config::Config;
2use crate::instruction_presets::{PresetType, get_instruction_preset_library};
3use crate::providers::{Provider, ProviderConfig};
4use anyhow::Result;
5use clap::Args;
6
7#[derive(Args, Clone, Default, Debug)]
8pub struct CommonParams {
9    /// Override default LLM provider
10    #[arg(long, help = "Override default LLM provider", value_parser = available_providers_parser)]
11    pub provider: Option<String>,
12
13    /// Override model for this operation
14    #[arg(long, help = "Override model for this operation")]
15    pub model: Option<String>,
16
17    /// Custom instructions for this operation
18    #[arg(short, long, help = "Custom instructions for this operation")]
19    pub instructions: Option<String>,
20
21    /// Select an instruction preset
22    #[arg(
23        long,
24        help = "Select an instruction preset (use 'git-iris list-presets' to see available presets for commits and reviews)"
25    )]
26    pub preset: Option<String>,
27
28    /// Enable Gitmoji (default: true)
29    #[arg(
30        long = "gitmoji",
31        help = "Enable Gitmoji",
32        conflicts_with = "no_gitmoji",
33        action = clap::ArgAction::SetTrue
34    )]
35    pub gitmoji_flag: bool,
36
37    /// Disable Gitmoji
38    #[arg(long = "no-gitmoji", help = "Disable Gitmoji", action = clap::ArgAction::SetTrue)]
39    pub no_gitmoji: bool,
40
41    /// Internal: resolved gitmoji value (Some(true), Some(false), or None)
42    #[arg(skip)]
43    pub gitmoji: Option<bool>,
44
45    /// Repository URL to use instead of local repository
46    #[arg(
47        short = 'r',
48        long = "repo",
49        help = "Repository URL to use instead of local repository"
50    )]
51    pub repository_url: Option<String>,
52}
53
54impl CommonParams {
55    /// Get the resolved gitmoji value from CLI flags
56    /// Returns Some(true) for --gitmoji, Some(false) for --no-gitmoji, None if neither specified
57    #[must_use]
58    pub fn resolved_gitmoji(&self) -> Option<bool> {
59        if self.gitmoji_flag {
60            Some(true)
61        } else if self.no_gitmoji {
62            Some(false)
63        } else {
64            self.gitmoji // fallback to any programmatically set value
65        }
66    }
67
68    ///
69    /// # Errors
70    ///
71    /// Returns an error when the requested provider is invalid.
72    pub fn apply_to_config(&self, config: &mut Config) -> Result<bool> {
73        let mut changes_made = false;
74
75        if let Some(provider_str) = &self.provider {
76            // Parse and validate provider
77            let provider: Provider = provider_str.parse()?;
78            let provider_name = provider.name().to_string();
79
80            // Check if we need to update the default provider
81            if config.default_provider != provider_name {
82                // Ensure the provider exists in the providers HashMap
83                if !config.providers.contains_key(&provider_name) {
84                    config.providers.insert(
85                        provider_name.clone(),
86                        ProviderConfig::with_defaults(provider),
87                    );
88                }
89
90                config.default_provider = provider_name;
91                changes_made = true;
92            }
93        }
94
95        // Apply model override if specified
96        if let Some(model) = &self.model {
97            let provider_name = config.default_provider.clone();
98            if let Some(provider_config) = config.providers.get_mut(&provider_name) {
99                provider_config.model.clone_from(model);
100                changes_made = true;
101            }
102        }
103
104        if let Some(instructions) = &self.instructions {
105            config.set_temp_instructions(Some(instructions.clone()));
106        }
107
108        if let Some(preset) = &self.preset {
109            config.set_temp_preset(Some(preset.clone()));
110        }
111
112        // Track whether gitmoji was explicitly set via CLI (for style detection)
113        if let Some(use_gitmoji) = self.resolved_gitmoji() {
114            config.gitmoji_override = Some(use_gitmoji);
115            if config.use_gitmoji != use_gitmoji {
116                config.use_gitmoji = use_gitmoji;
117                changes_made = true;
118            }
119        }
120
121        Ok(changes_made)
122    }
123
124    /// Check if the provided preset is valid for the specified preset type
125    #[must_use]
126    pub fn is_valid_preset_for_type(&self, preset_type: PresetType) -> bool {
127        if let Some(preset_name) = &self.preset {
128            let library = get_instruction_preset_library();
129            let valid_presets = library.list_valid_presets_for_command(preset_type);
130            return valid_presets.iter().any(|(key, _)| *key == preset_name);
131        }
132        true // No preset specified is always valid
133    }
134}
135
136/// Validates that a provider name is available in the system
137///
138/// # Errors
139///
140/// Returns an error string when the provider name is not supported.
141pub fn available_providers_parser(s: &str) -> Result<String, String> {
142    match s.parse::<Provider>() {
143        Ok(provider) => Ok(provider.name().to_string()),
144        Err(_) => Err(format!(
145            "Invalid provider '{}'. Available providers: {}",
146            s,
147            Provider::all_names().join(", ")
148        )),
149    }
150}