diff --git a/src/main.rs b/src/main.rs index 0af25ee..c72cea3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,48 +1,53 @@ // gt - a gitea cli client // Written by Wyatt J. Miller // All right reserved, 2020 +// Licensed by the MPL v2 -use clap::{Arg, App, SubCommand}; +mod arg; +mod config; +mod issue; +mod repo; + +use clap::{ArgMatches}; fn main() { - let matches = App::new("gt - A Gitea CLI client") - .version("0.0.1") - .author("Wyatt J. Miller ") - .about("It's a CLI client, what do you expect?") - .subcommand(SubCommand::with_name("repo") - .about("Create, delete, or fork a repo") - .arg(Arg::with_name("create") - .short("c") - .long("create") - .value_names(&["OWNER", "REPO"]) - .help("Create a repo")) - .arg(Arg::with_name("delete") - .short("d") - .long("delete") - .value_names(&["OWNER", "REPO"]) - .help("Delete a repo")) - .arg(Arg::with_name("fork") - .short("f") - .long("fork") - .value_names(&["OWNER", "REPO", "FORKED_OWNER", "FORKED_REPO"]) - .help("Fork a repo"))) - .get_matches(); + let matches: ArgMatches = arg::get_args(); + let config = crate::config::Configuration::new(); match matches.subcommand() { + ("", None) => println!("No subcommand was given!"), ("repo", Some(repo_matches)) => { + let repo = repo::Repository::new(); + + // TODO: match expression should be here if repo_matches.is_present("create") { - println!("\"repo create\" passed") + repo.create_repo(&config, repo_matches); } if repo_matches.is_present("delete") { - println!("\"repo delete\" passed") + repo.delete_repo(&config, repo_matches); } if repo_matches.is_present("fork") { - println!("\"repo fork\" passed") + repo.fork_repo(&config, repo_matches) } - } - ("", None) => println!("No subcommand was given!"), - _ => unreachable!() + + if repo_matches.is_present("search") { + repo.search_repo(&config, repo_matches) + } + + if repo_matches.is_present("list") { + repo.list_repo(&config) + } + }, + ("issue", Some(issue_matches)) => { + let issue = issue::Issue::new(); + + // TODO: match expression should be here + if issue_matches.is_present("create") { + issue.create_issue(&config, issue_matches); + } + }, + _ => println!("Huh?") } -} +} \ No newline at end of file diff --git a/src/repo.rs b/src/repo.rs new file mode 100644 index 0000000..4e03b73 --- /dev/null +++ b/src/repo.rs @@ -0,0 +1,290 @@ +use std::collections::HashMap; + +use clap::ArgMatches; +use colored::*; +use reqwest::{StatusCode, Client}; +use serde_derive::{Serialize, Deserialize}; + +use crate::config::Configuration; + +pub struct Repository; + +#[derive(Debug, Serialize, Deserialize)] +pub struct MultipleRepositories { + pub data: Vec, + pub ok: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct RepositoryResponse { + pub allow_merge_commits: bool, + pub allow_rebase: bool, + pub allow_rebase_explicit: bool, + pub allow_squash_merge: bool, + pub archived: bool, + pub avatar_url: String, + pub clone_url: String, + pub created_at: String, + pub default_branch: String, + pub description: String, + pub empty: bool, + pub external_tracker: Option, + pub external_wiki: Option, + pub fork: bool, + pub forks_count: u32, + pub full_name: String, + pub has_issues: bool, + pub has_pull_requests: bool, + pub has_wiki: bool, + pub html_url: String, + pub id: u32, + pub ignore_whitespace_conflicts: bool, + pub internal_tracker: Option, + pub mirror: bool, + pub name: String, + pub open_issues_count: u32, + pub open_pr_counter: u32, + pub original_url: String, + pub owner: Option, + pub permissions: Option, + pub private: bool, + pub release_counter: u32, + pub size: u32, + pub ssh_url: String, + pub stars_count: u32, + pub template: bool, + pub updated_at: String, + pub watchers_count: u32, + pub website: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ExternalTracker { + pub external_tracker_format: String, + pub external_tracker_style: String, + pub external_tracker_url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ExternalWiki { + pub external_wiki_url: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct InternalTracker { + pub allow_only_contributors_to_track_time: bool, + pub enable_issue_dependencies: bool, + pub enable_time_tracker: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Owner { + pub avatar_url: String, + pub created: String, + pub email: String, + pub full_name: String, + pub id: u32, + pub is_admin: bool, + pub language: String, + pub last_login: String, + pub login: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct Permissions { + pub admin: bool, + pub pull: bool, + pub push: bool, +} + +impl Repository { + pub fn new() -> Repository { + Repository {} + } + + pub fn create_repo(&self, config: &Configuration, arg: &ArgMatches) { + let client = Client::new(); + let arg_value = arg.value_of("create").unwrap(); + let mut map: HashMap<&str, &str> = HashMap::new(); + let url = format!("{base_url}{base_api}/user/repos?token={api_token}", + base_url = config.base_url, + base_api = config.base_api, + api_token = config.api_token); + + map.insert("name", arg_value); + map.insert("readme", arg_value); + map.insert("description", arg_value); + + let response = client.post(url.as_str()) + .json(&map) + .send(); + + match response { + Ok(mut repo) => { + match repo.status() { + StatusCode::CREATED => { + let deserialized: RepositoryResponse = repo.json().unwrap(); + println!("{}", "Repository successfully created!".green()); + println!("\tRepository name: {:0?}\n\tRepository owner: {:1?}\n\tRepository description: {:2?}", deserialized.name, deserialized.owner.unwrap().full_name, deserialized.description); + }, + StatusCode::CONFLICT => println!("{}", "Repository already exists!".red()), + StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Repository input validation failed!".red()), + _ => println!("Repository creation failed! HTTP status code: {}", repo.status().as_str()) + } + }, + Err(e) => panic!(e) + } + } + + pub fn delete_repo(&self, config: &Configuration, arg: &ArgMatches) { + let client = Client::new(); + let arg_iter: Vec<&str> = arg.values_of("delete") + .unwrap() + .collect(); + + let url = format!("{base_url}{base_api}/repos/{username}/{repo_name}?token={api_token}", + base_url = config.base_url, + base_api = config.base_api, + username = arg_iter[0], + repo_name = arg_iter[1], + api_token = config.api_token); + + let response = client.delete(&url) + .send(); + + match response { + Ok(repo) => { + match repo.status() { + StatusCode::NO_CONTENT => println!("{}", "Repository successfully deleted!".green()), + StatusCode::FORBIDDEN => println!("{}", "Forbidden to delete this repository!".red()), + StatusCode::NOT_FOUND => println!("{}", "Repository doesn't exist!".red()), + _ => println!("Repository deletion failed! HTTP status code: {}", repo.status().to_string()) + } + }, + Err(e) => panic!(e) + } + } + + pub fn fork_repo(&self, config: &Configuration, arg: &ArgMatches) { + let client = Client::new(); + let arg_item: Vec<&str> = arg.values_of("fork") + .unwrap() + .collect(); + + let mut map: HashMap<&str, &str> = HashMap::new(); + let user = config.username + .clone() + .unwrap(); + + + let url = format!("{base_url}{base_api}/repos/{owner}/{repo}/forks?token={api_token}", + base_url = config.base_url, + base_api = config.base_api, + owner = arg_item[0], + repo = arg_item[1], + api_token = config.api_token); + + map.insert("name", user.as_str()); + + let response = client.post(url.as_str()) + .json(&map) + .send(); + + match response { + Ok(mut repo) => { + match repo.status() { + StatusCode::ACCEPTED => { + let deserialized: RepositoryResponse = repo.json().unwrap(); + println!("{}", "Repository forked successfully".green()); + println!("\tOriginal repository name: {:0?}\n\tOriginal repository owner: {:1?}\n\tForked repository name: {:2?}\n\tForked repository owner: {:3?}", deserialized.name, arg_item[0], deserialized.name, deserialized.owner.unwrap().full_name); + }, + StatusCode::INTERNAL_SERVER_ERROR => println!("{}", "Repository already forked!".red()), + StatusCode::FORBIDDEN => println!("{}", "Repository fork forbidden!".red()), + StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Repository fork input validation failed!".red()), + StatusCode::NOT_FOUND => println!("{}", "Repository not found!"), + _ => println!("Repository creation failed! HTTP status code: {}", repo.status().as_str()) + } + }, + Err(e) => panic!(e) + } + } + + pub fn search_repo(&self, config: &Configuration, arg: &ArgMatches) { + let client = Client::new(); + let arg_value = arg.value_of("search").unwrap(); + let url = format!("{base_url}{base_api}/repos/search?q={query}&token={api_token}", + base_url = config.base_url, + base_api = config.base_api, + query = arg_value, + api_token = config.api_token); + + let response = client.get(url.as_str()) + .send(); + + match response { + Ok(mut repo) => { + match repo.status() { + StatusCode::OK => { + let deserialized: MultipleRepositories = repo.json() + .unwrap(); + + match deserialized.data.len() != 0 { + true => { + println!("{}", "List of repositories found:".green()); + + for (i, data) in deserialized.data.iter().enumerate() { + 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()) + }, + false => println!("{}", "Repository searched doesn't exist!".red()) + } + }, + StatusCode::NOT_FOUND => println!("{}", "Repository searched doesn't exist!".red()), + StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Repository input validation failed!".red()), + _ => println!("Repository search failed! HTTP status code: {}", repo.status().as_str()) + } + }, + Err(e) => panic!(e) + } + } + + pub fn list_repo(&self, config: &Configuration) { + let client = Client::new(); + let url = format!("{base_url}{base_api}/repos/search?token={api_token}", + base_url = config.base_url, + base_api = config.base_api, + api_token = config.api_token); + + let response = client.get(url.as_str()) + .send(); + + match response { + Ok(mut repo) => { + match repo.status() { + StatusCode::OK => { + let deserialized: MultipleRepositories = repo.json() + .unwrap(); + + match deserialized.data.len() != 0 { + true => { + println!("{}", "List of repositories found:".green()); + + for (i, data) in deserialized.data.iter().enumerate() { + 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()) + }, + false => println!("{}", "The authenticated user doesn't have any repositories. Why not create one?".yellow()) + } + }, + StatusCode::UNPROCESSABLE_ENTITY => println!("{}", "Repository input validation failed!".red()), + _ => println!("Repository search failed! HTTP status code: {}", repo.status().as_str()) + } + }, + Err(e) => panic!(e) + } + } +}