turbocommit 0.3.1

A CLI tool to create commit messages with gpt-3.5-turbo
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..97a4aa5
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,84 @@
+mod openai;
+
+use openai::{Message, Role};
+
+use std::collections::HashMap;
+use std::fmt::{Error, format};
+use std::process::Command;
+use colored::Colorize;
+use reqwest::{Request, RequestBuilder};
+use serde::{Serialize, Deserialize};
+
+const SYSTEM_MSG: &str = "NICE MAN";
+const MODEL: &str = "gpt-3.5-turbo";
+
+fn main() {
+    println!();
+    let diff = match git_diff() {
+        Ok(diff) => diff,
+        Err(e) => {
+            println!("{}", e);
+            return;
+        }
+    };
+    // println!("{}", diff);
+
+    let mut messages = Vec::new();
+    messages.push(Message::system(SYSTEM_MSG));
+    messages.push(Message::user(diff));
+
+    let json = serde_json::to_string(&messages).unwrap();
+
+    let client = reqwest::blocking::Client::new();
+    let res = client
+        .get("https://api.openai.com/v1/chat/completions")
+        .header("Content-Type", "application/json")
+        .header("Authorization", format!("Bearer {}", 123))
+        .body(json)
+        .send();
+
+    match res {
+        Ok(res) => {
+            match res.status() {
+                reqwest::StatusCode::OK => {
+                    let body = res.text().unwrap();
+                    let resp = serde_json::from_str::<openai::Response>(&body).unwrap();
+                    println!("This used {} token, costing you ~{}$", format!("{}", resp.usage.total_tokens).green(), format!("{}", cost(resp.usage.total_tokens)).green());
+                    for choice in resp.choices {
+                        println!("{}", choice.message.content);
+                    }
+                }
+                _ => {
+                    let e = res.text().unwrap();
+                    let error = serde_json::from_str::<openai::ErrorRoot>(&e).unwrap().error;
+                    println!("{}", error);
+                    return;
+                }
+            }
+        }
+        Err(e) => {
+            println!("{}", e);
+            return;
+        }
+    }
+}
+
+fn git_diff() -> Result<String, String> {
+    let diff = Command::new("git")
+        .arg("diff")
+        .arg("--staged")
+        .arg("--minimal")
+        .arg("-U2")
+        .output()
+        .expect("failed to execute git diff");
+    match diff.status.success() {
+        true => Ok(String::from_utf8_lossy(&diff.stdout).to_string()),
+        false => Err(String::from_utf8_lossy(&diff.stderr).to_string()),
+    }
+}
+
+const PRICE: f64 = 0.002;
+
+fn cost(token: i64) -> f64 {
+    token as f64 * (PRICE / 1000.0)
+}
\ No newline at end of file
diff --git a/src/openai.rs b/src/openai.rs
new file mode 100644
index 0000000..24caddb
--- /dev/null
+++ b/src/openai.rs
@@ -0,0 +1,85 @@
+use std::fmt;
+use colored::Colorize;
+use serde::{Serialize, Deserialize};
+use serde_json::Value;
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "lowercase")]
+pub enum Role {
+    System,
+    User,
+    Assistant,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Message {
+    pub role: Role,
+    pub content: String,
+}
+
+impl Message {
+    pub fn system<S: Into<String>>(content: S) -> Message {
+        Message {
+            role: Role::System,
+            content: content.into(),
+        }
+    }
+    pub fn user<S: Into<String>>(content: S) -> Message {
+        Message {
+            role: Role::User,
+            content: content.into(),
+        }
+    }
+    pub fn assistant<S: Into<String>>(content: S) -> Message {
+        Message {
+            role: Role::Assistant,
+            content: content.into(),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct ErrorRoot {
+    pub error: Error,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+#[serde(rename_all = "camelCase")]
+pub struct Error {
+    pub message: String,
+    #[serde(rename = "type")]
+    pub type_field: String,
+    pub param: Value,
+    pub code: String,
+}
+
+impl fmt::Display for Error {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{} ({}): {:?}", self.type_field.red(), self.code, self.message)
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Response {
+    pub id: String,
+    pub object: String,
+    pub created: i64,
+    pub model: String,
+    pub choices: Vec<Choice>,
+    pub usage: Usage,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Choice {
+    pub index: i64,
+    pub finish_reason: String,
+    pub message: Message,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct Usage {
+    pub prompt_tokens: i64,
+    pub completion_tokens: i64,
+    pub total_tokens: i64,
+}