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