diff --git a/src/arg.rs b/src/arg.rs index 0fb3bab..e4fdb0e 100644 --- a/src/arg.rs +++ b/src/arg.rs @@ -66,6 +66,7 @@ pub fn get_args() -> ArgMatches<'static> { .help("Create an issue for a repository") ) ) + .arg(Arg::with_name("")) .subcommand(SubCommand::with_name("pr") .about("Create, search, comment, merge, and close pull requests") .arg(Arg::with_name("create") diff --git a/src/config.rs b/src/config.rs index 1230d8b..66a1f41 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,10 +1,13 @@ use std::env; use std::ops::Deref; +use std::process; use config::File; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Deserialize)] +use crate::generate; + +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Configuration { pub api_token: Option, pub base_api: String, @@ -15,6 +18,16 @@ pub struct Configuration { impl Configuration { pub fn new() -> Configuration { + Configuration { + api_token: None, + base_api: String::new(), + base_url: String::new(), + username: None, + password: None, + } + } + + pub fn load_config_file(&mut self) { let home_dir_env = env::var("HOME").unwrap(); let mut settings = config::Config::default(); let mut location: Vec = Vec::new(); @@ -24,18 +37,21 @@ impl Configuration { "windows" => { location.push(String::from("config.json")); location.push(format!("{:?}/AppData/Roaming/gt/config.json", home_dir_env)) - }, + } // this case is currently untested "macos" => { location.push(String::from("config.json")); - }, + } "linux" => { location.push(String::from("config.json")); location.push(String::from("/etc/gt/config.json")); - location.push(format!("{:?}/.config/gt/config.json", home_dir_env)); - }, + location.push(format!("{:?}/.config/gt/config.json", home_dir_env)); + } _ => { - println!("Unsupported operating system! {:?} might cause some instabilities!", env::consts::OS); + println!( + "Unsupported operating system! {:?} might cause some instabilities!", + env::consts::OS + ); location.push(String::from("config.json")); } } @@ -44,46 +60,75 @@ impl Configuration { settings.merge(File::with_name(&i).required(false)).unwrap(); } - let config = settings - .try_into::() - .expect("Couldn't load config into gt!"); + let config: Result = + settings.try_into::(); - config + match config { + Ok(_) => { + let c = config.unwrap(); + + if (self.api_token.is_none()) { + self.api_token = c.api_token; + } + + if (self.base_url.is_empty()) { + self.base_url = c.base_url; + } + + if (self.base_api.is_empty()) { + self.base_api = c.base_api; + } + + if (self.username.is_none()) { + self.username = c.username; + } + + if (self.password.is_none()) { + self.password = c.password; + } + println!("{:?}", self); + } + Err(e) => { + println!("No config found! Trying to generate config: {}", e); + generate::generate_config(); + print!("Go fill out your config.json file in your present working directory. If you need assistance, please reference the readme."); + process::exit(4); + } + } } - + pub fn load_envs(&mut self) { // get environment variables - let username_env = env::var("GT_USERNAME").unwrap_or_else(|_| "".to_string()); - let password_env = env::var("GT_PASSWORD").unwrap_or_else(|_| "".to_string()); - let api_token_env = env::var("GT_API_TOKEN").unwrap_or_else(|_| "".to_string()); - - // get struct fields - let mut user_config = self.username.as_ref().unwrap(); - let mut password_config = self.password.as_ref().unwrap(); - let mut api_token_config = self.api_token.as_ref().unwrap(); + let username_env = env::var("GT_USERNAME").unwrap_or_else(|_| String::new()); + let password_env = env::var("GT_PASSWORD").unwrap_or_else(|_| String::new()); + let api_token_env = env::var("GT_API_TOKEN").unwrap_or_else(|_| String::new()); + let base_url_env = env::var("GT_URL").unwrap_or_else(|_| String::new()); // check and see if the env vars are empty // if they are not, put the env vars in place of the config property - if username_env != "".to_string() { + if !username_env.is_empty() { self.username = Some(username_env); } else { println!("cannot find username env var"); } - - if password_env != "".to_string() { - password_config = &password_env.deref().to_string(); + + if !password_env.is_empty() { + self.password = Some(password_env); } else { println!("cannot find password env var"); } - if api_token_env != "".to_string() { - api_token_config = &api_token_env.deref().to_string(); + if !api_token_env.is_empty() { + self.api_token = Some(api_token_env); } else { println!("cannot find api token env var"); } - println!("{:?}", &self); + if !base_url_env.is_empty() { + self.base_url = base_url_env; + } + println!("{:?}", &self); } } diff --git a/src/generate.rs b/src/generate.rs index 9ceed81..1ee63f3 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -1,5 +1,20 @@ use crate::config::Configuration; +use std::fs; +use std::path::Path; -fn generate_config() { - -} \ No newline at end of file +pub fn generate_config() { + let json_file = String::from("config.json"); + if !Path::new(&json_file).exists() { + let generate = Configuration { + api_token: Some("".to_string()), + base_api: "/api/v1".to_string(), + base_url: "https://gitea.com".to_string(), + username: Some("gitea".to_string()), + password: Some("".to_string()), + }; + + let json = serde_json::to_string(&generate).unwrap(); + + fs::write(json_file, json).expect("Failed to write file."); + } +} diff --git a/src/issue.rs b/src/issue.rs index c2591ba..afdfe38 100644 --- a/src/issue.rs +++ b/src/issue.rs @@ -1,11 +1,12 @@ use std::collections::HashMap; use colored::*; -use reqwest::StatusCode; +use reqwest::blocking::Client; +use reqwest::{StatusCode, Response}; use serde_derive::{Serialize, Deserialize}; use serde_json::Value; -use crate::request::Request; +use crate::request::{Request, Authentication, AuthenticationType}; use crate::util; pub struct Issue; @@ -66,6 +67,17 @@ pub struct Repository { pub full_name: String, } +// trait IssueRequest { +// fn client() -> &'static Client; +// fn url_request_decision() -> String; +// fn arg_value_decision() -> Vec<&'static str>; +// } + +// impl IssueRequest for Issue { +// fn client() -> &'static Client { +// return +// } +// } impl Issue { pub fn new() -> Issue { @@ -118,4 +130,79 @@ impl Issue { Err(e) => panic!("{}", e), } } + + pub fn list_issue(&self, request: &Request) { + let client = &request.client; + let url = self.url_request_decision(&request); + let response = client.get(&url).send(); + + // TODO: fix this to match context + match response { + Ok(repo) => { + match repo.status() { + StatusCode::CREATED => println!("{}", "Issue successfully created!".green()), + StatusCode::FORBIDDEN => println!("{}", "Issue creation forbidden!".red()), + StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Issue input validation failed!".red()), + _ => println!(), + } + }, + Err(e) => panic!("{}", e), + } + } + + pub fn search_issue(&self, request: &Request) { + let client = &request.client; + let url = self.url_request_decision(&request); + let response = client.get(&url).send(); + + // TODO: fix this to match context + match response { + Ok(repo) => { + match repo.status() { + StatusCode::CREATED => println!("{}", "Issue successfully created!".green()), + StatusCode::FORBIDDEN => println!("{}", "Issue creation forbidden!".red()), + StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Issue input validation failed!".red()), + _ => println!(), + } + }, + Err(e) => panic!("{}", e), + } + } + + fn url_request_decision(&self, request: &Request) -> String { + let arg_value: Vec<&str> = request + .arg_value + .subcommand() + .1 + .unwrap() + // TODO: fix the command line variable listed below + .values_of("search") + .unwrap() + .collect(); + + // TODO: fix the url formatting for basic auth + match &request.authentication.auth_type { + AuthenticationType::ApiToken => { + format!( + "{request}/repos/{owner}/{repo}/issues?token={api_token}", + request = request.url.as_ref().unwrap(), + owner = arg_value[0], + repo = arg_value[1], + api_token = request.authentication.credentials.1.as_ref().unwrap() + ) + }, + AuthenticationType::BasicAuth => { + format!( + "{request}/repos/{owner}/{repo}/issues?token={api_token}", + request = request.url.as_ref().unwrap(), + owner = arg_value[0], + repo = arg_value[1], + api_token = request.authentication.credentials.1.as_ref().unwrap() + ) + }, + // this case _shouldn't_ happen but ya know + AuthenticationType::None => panic!("idk what happened man you wrote the code 🤷") + } + } + } diff --git a/src/main.rs b/src/main.rs index 28536d4..bfa96b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,10 +5,11 @@ mod arg; mod config; +mod generate; mod issue; +mod pr; mod repo; mod request; -mod pr; mod user; mod util; @@ -18,6 +19,14 @@ fn main() { let matches: ArgMatches = arg::get_args(); let mut config = crate::config::Configuration::new(); config.load_envs(); + + if config.base_url.is_empty() + || config.api_token.is_none() + || (config.username.is_none() && config.password.is_none()) + { + config.load_config_file(); + } + let auth = request::Authentication::new(&config); let request = auth.request_chooser(config.clone(), matches); diff --git a/src/repo.rs b/src/repo.rs index 2a593f8..105eea5 100644 --- a/src/repo.rs +++ b/src/repo.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; +use std::{collections::HashMap, path::{Path, self}, file}; use colored::*; -use git2::Repository as Repo; +use git2::{Repository as Repo, build::RepoBuilder, Credentials, Cred, CredentialType, RemoteCallbacks, FetchOptions}; use reqwest::StatusCode; use serde_derive::{Deserialize, Serialize}; @@ -287,7 +287,7 @@ impl Repository { println!("{}.\tRepository name: {:1}\n\tRepository owner: {:2}\n\tRepository description: {:3}\n", i + 1, data.name, data.owner.as_ref().unwrap().full_name, data.description) } - println!("Total number of repositories indexed: {}", deserialized.data.iter().count()) + println!("Total number of repositories indexed: {}", deserialized.data.len()) }, false => println!("{}", "The authenticated user doesn't have any repositories. Why not create one?".yellow()) } @@ -299,12 +299,15 @@ impl Repository { } } - pub fn push_to_remote(&self, request: &Request) { - + pub fn push_to_remote(&self, request: &Request, config: &Configuration) { + // code to push to the remote server goes here + // this is based on the user, the remote, and the branch + } - pub fn pull_from_remote(&self, request: &Request) { - + pub fn pull_from_remote(&self, request: &Request, config: &Configuration) { + // code to pull from the remote server goes here + // this is based on the user, the remote, and the branch } pub fn clone_from_remote(&self, request: &Request, config: &Configuration) { @@ -322,7 +325,48 @@ impl Repository { owner = arg_value[0], repo = arg_value[1] ); - Repo::clone(url.as_str(), ".").unwrap(); - println!("Repository successfully cloned!"); + + let mut builder = RepoBuilder::new(); + let mut callbacks = RemoteCallbacks::new(); + callbacks.credentials(|_url, _user_from_url, _allowed_types| { + let user = _user_from_url.unwrap_or("git"); + + if _allowed_types.contains(CredentialType::USERNAME) { + return Cred::username(user) + } + + // if request.url_request() { + // Cred::ssh_key( + // user, + // Some(Path::new(&format!("{}/.ssh/id_ed25519.pub", std::env::var("HOME").unwrap()).as_str())), + // Path::new(&format!("{}/.ssh/id_ed25519", std::env::var("HOME").unwrap()).as_str()), + // None + // ) + // } else { + // Cred::userpass_plaintext( + // request.authentication.credentials.0.as_ref().unwrap().as_str(), + // request.authentication.credentials.1.as_ref().unwrap().as_str() + // ) + // } + Cred::userpass_plaintext( + request.authentication.credentials.0.as_ref().unwrap().as_str(), + config.password.as_ref().unwrap().as_str() + ) + }); + builder.branch("master"); + match builder.clone(url.as_str(), Path::new(arg_value[1])) { + Ok(_r) => Ok(println!("Repository cloned successfully!")), + Err(_e) => Err(println!("Repository clone failed!")), + _ => panic!("agh") + }; + + } + + pub fn add_to_staging(&self, request: &Request, config: Configuration) { + + } + + pub fn create_commit(&self, request: &Request, config: Configuration) { + } } diff --git a/src/request.rs b/src/request.rs index 0e81099..d2e57c1 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, process}; use clap::ArgMatches; use reqwest::blocking::Client; @@ -56,17 +56,6 @@ impl<'a> Request<'a> { authentication: auth, } } - - /// public method that decides whether to format the request url - /// based on the authentication written in the config file. - /// Returns an integer. - pub fn url_request(&self) -> bool { - if self.authentication.api_token { - true - } else { - false - } - } } impl Authentication { @@ -90,7 +79,8 @@ impl Authentication { basic_auth, ) } else { - panic!("Must have some form of authentication! Exiting..."); + println!("Must have some form of authentication! Exiting..."); + process::exit(3) } } else { Authentication::with_api_token(config.username.as_ref().unwrap().to_string(), api_auth) diff --git a/src/user.rs b/src/user.rs index 37d4b20..76f64c4 100644 --- a/src/user.rs +++ b/src/user.rs @@ -1,3 +1,6 @@ +use std::collections::HashMap; + +use crate::{request::Request, util}; use serde_derive::{Deserialize, Serialize}; pub struct User; @@ -30,4 +33,29 @@ impl User { pub fn new() -> User { User {} } -} \ No newline at end of file + + pub fn create_user(request: &Request) { + let client = &request.client; + let arg_value = &request + .arg_value + .subcommand() + .1 + .unwrap() + .value_of("create") + .unwrap(); + + let mut user_create_input: HashMap = HashMap::new(); + let username = util::get_input(String::from("Please enter a username:")); + let email = util::get_input(String::from("Please enter a email address:")); + let password = util::get_input(String::from("Please enter a password:")); + user_create_input.insert("login".to_string(), username); + user_create_input.insert("email".to_string(), email); + user_create_input.insert("password".to_string(), password); + } + + pub fn list_user() {} + + pub fn search_user() {} + + pub fn delete_user() {} +}