git_iris/
context.rs

1use crate::token_optimizer::TokenOptimizer;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use std::fmt;
5use textwrap::wrap;
6
7#[derive(Serialize, Debug, Clone)]
8pub struct CommitContext {
9    pub branch: String,
10    pub recent_commits: Vec<RecentCommit>,
11    pub staged_files: Vec<StagedFile>,
12    pub project_metadata: ProjectMetadata,
13    pub user_name: String,
14    pub user_email: String,
15}
16
17#[derive(Serialize, Debug, Clone)]
18pub struct RecentCommit {
19    pub hash: String,
20    pub message: String,
21    pub author: String,
22    pub timestamp: String,
23}
24
25#[derive(Serialize, Debug, Clone)]
26pub struct StagedFile {
27    pub path: String,
28    pub change_type: ChangeType,
29    pub diff: String,
30    pub analysis: Vec<String>,
31    pub content: Option<String>,
32    pub content_excluded: bool,
33}
34
35#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
36pub struct GeneratedMessage {
37    pub emoji: Option<String>,
38    pub title: String,
39    pub message: String,
40}
41
42impl From<String> for GeneratedMessage {
43    #[allow(clippy::unwrap_used)] // todo: handle error maybe replace with try_from
44    fn from(value: String) -> Self {
45        serde_json::from_str(&value).unwrap()
46    }
47}
48
49#[derive(Serialize, Debug, Clone, PartialEq, Eq)]
50pub enum ChangeType {
51    Added,
52    Modified,
53    Deleted,
54}
55
56impl fmt::Display for ChangeType {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        match self {
59            Self::Added => write!(f, "Added"),
60            Self::Modified => write!(f, "Modified"),
61            Self::Deleted => write!(f, "Deleted"),
62        }
63    }
64}
65
66#[derive(Serialize, Debug, Clone, Default)]
67pub struct ProjectMetadata {
68    pub language: Option<String>,
69    pub framework: Option<String>,
70    pub dependencies: Vec<String>,
71    pub version: Option<String>,
72    pub build_system: Option<String>,
73    pub test_framework: Option<String>,
74    pub plugins: Vec<String>,
75}
76
77impl ProjectMetadata {
78    pub fn merge(&mut self, new: ProjectMetadata) {
79        if let Some(new_lang) = new.language {
80            match &mut self.language {
81                Some(lang) if !lang.contains(&new_lang) => {
82                    lang.push_str(", ");
83                    lang.push_str(&new_lang);
84                }
85                None => self.language = Some(new_lang),
86                _ => {}
87            }
88        }
89        self.dependencies.extend(new.dependencies.clone());
90        self.framework = self.framework.take().or(new.framework);
91        self.version = self.version.take().or(new.version);
92        self.build_system = self.build_system.take().or(new.build_system);
93        self.test_framework = self.test_framework.take().or(new.test_framework);
94        self.plugins.extend(new.plugins);
95        self.dependencies.sort();
96        self.dependencies.dedup();
97    }
98}
99
100impl CommitContext {
101    pub fn new(
102        branch: String,
103        recent_commits: Vec<RecentCommit>,
104        staged_files: Vec<StagedFile>,
105        project_metadata: ProjectMetadata,
106        user_name: String,
107        user_email: String,
108    ) -> Self {
109        Self {
110            branch,
111            recent_commits,
112            staged_files,
113            project_metadata,
114            user_name,
115            user_email,
116        }
117    }
118    pub fn optimize(&mut self, max_tokens: usize) {
119        let optimizer = TokenOptimizer::new(max_tokens);
120        optimizer.optimize_context(self);
121    }
122}
123
124pub fn format_commit_message(response: &GeneratedMessage) -> String {
125    let mut message = String::new();
126
127    if let Some(emoji) = &response.emoji {
128        message.push_str(&format!("{emoji} "));
129    }
130
131    message.push_str(&response.title);
132    message.push_str("\n\n");
133
134    let wrapped_message = wrap(&response.message, 78);
135    for line in wrapped_message {
136        message.push_str(&line);
137        message.push('\n');
138    }
139
140    message
141}