diff --git a/Cargo.lock b/Cargo.lock index 0db1e72..44b2c8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,18 +145,18 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.54" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -166,9 +166,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.65" +version = "4.5.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" +checksum = "c757a3b7e39161a4e56f9365141ada2a6c915a8622c408ab6bb4b5d047371031" dependencies = [ "clap", "clap_lex", @@ -178,9 +178,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cmake" @@ -267,6 +267,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs" version = "6.0.0" @@ -473,9 +482,11 @@ dependencies = [ name = "girep" version = "0.11.0-beta" dependencies = [ + "cfg-if", "clap", "clap_complete", "color-print", + "directories", "dirs", "futures", "git2", @@ -507,7 +518,7 @@ dependencies = [ [[package]] name = "grp-core" -version = "0.1.1" +version = "0.1.2" dependencies = [ "color-print", "futures", diff --git a/Cargo.toml b/Cargo.toml index 01eb994..abf8d7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,12 +11,12 @@ description = "A simple CLI tool to mannage platforms for git repositories" homepage = "https://github.com/feraxhp/grp" [dependencies] -grp-core = { path = "./grp-core", version="0.1.0" } +grp-core = { path = "./grp-core", version="0.1.2" } tokio = { version = "1.49.0", features = ["rt", "rt-multi-thread", "macros"] } reqwest = { version = "0.13.1", features = ["json"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" -clap = { version = "4.5.54" , features = ["cargo"] } +clap = { version = "4.5.60" , features = ["cargo"] } dirs = "6.0.0" color-print = "0.3.7" indicatif = "0.18.3" @@ -24,7 +24,9 @@ futures = "0.3.31" git2 = "0.20.3" urlencoding = "2.1.3" regex = "1.12.2" -clap_complete = { version = "4.5.65", features = ["unstable-dynamic"] } +clap_complete = { version = "4.5.66", features = ["unstable-dynamic"] } +cfg-if = "1.0.4" +directories = "6.0.0" [[bin]] name = "grp" diff --git a/README.md b/README.md index 371b896..4ff8066 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,20 @@ cargo install girep --version 0.11.0-beta ~~~bash source <(COMPLETE=bash grp) ~~~ +Currently bash has a little problem with the autocompletion... +so, in order to solve the problem for auto completion of the repostructure +you may whant to add the completion to a file, and replace the last 5 lines +for the next: +~~~bash +if [[ "${BASH_VERSINFO[0]}" -eq 4 && "${BASH_VERSINFO[1]}" -ge 4 || "${BASH_VERSINFO[0]}" -gt 4 ]]; then + COMP_WORDBREAKS=${COMP_WORDBREAKS//:} complete -o nospace -o bashdefault -o nosort -F _clap_complete_grp dgrp +else + COMP_WORDBREAKS=${COMP_WORDBREAKS//:} complete -o nospace -o bashdefault -F _clap_complete_grp dgrp +fi +~~~ +> [!note] +> I already open a feature request in [clap-rs](https://github.com/clap-rs/clap/) +> if you find this a _Headache_ please help me by comenting in this [issue](https://github.com/clap-rs/clap/issues/6280) ### zsh ~~~zsh diff --git a/grp-core/Cargo.toml b/grp-core/Cargo.toml index 08991a6..27789e4 100644 --- a/grp-core/Cargo.toml +++ b/grp-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "grp-core" -version = "0.1.1" +version = "0.1.2" edition = "2024" license = "GPL-3.0-only" authors = ["feraxhp "] diff --git a/grp-core/src/error/structs.rs b/grp-core/src/error/structs.rs index 880ae1d..b95f0d8 100644 --- a/grp-core/src/error/structs.rs +++ b/grp-core/src/error/structs.rs @@ -89,7 +89,11 @@ impl Error { } } - pub fn new_custom>(message: T, content: Vec) -> Error { + pub fn new_custom(message: M, content: Vec) -> Error + where + M: Into, + C: Into + { Error { message: message.into(), content: content.into_iter().map(|s| s.into()).collect() diff --git a/src/cache/mod.rs b/src/cache/mod.rs new file mode 100644 index 0000000..4e645e5 --- /dev/null +++ b/src/cache/mod.rs @@ -0,0 +1,3 @@ +pub mod structure; +pub mod users; +pub mod repos; diff --git a/src/cache/repos.rs b/src/cache/repos.rs new file mode 100644 index 0000000..e2d209a --- /dev/null +++ b/src/cache/repos.rs @@ -0,0 +1,68 @@ +use grp_core::Error; +use grp_core::structs::Repo; +use std::collections::{HashMap, HashSet}; + +use crate::cache::structure::{Cacher, Hasher, Uncacher, Values}; + +impl Hasher for Vec { + fn to_set(&self) -> HashSet { + let mut hash = HashSet::new(); + self.iter() + .map(|s| { s.path.clone() }) + .for_each(|s| { hash.insert(s); }); + + hash + } + + fn to_values(&self) -> Values { + Values { + repos: self.to_set(), + users: HashSet::new() + } + } +} + +impl Cacher for Vec { + fn get(pconf: &str) -> Result, Error> { + let values = Self::load()?; + match values.get(pconf) { + Some(v) => Ok(v.repos.clone()), + None => Ok(HashSet::new()), + } + } + + fn put(&self, pconf: &str, union: bool) -> Result, Error> { + let mut values = Self::load()?; + + match values.get_mut(pconf) { + Some(p) if union => { + p.repos = p.repos + .union(&self.to_set()) + .cloned() + .collect(); + } + Some(p) => { + p.repos = self.to_set(); + } + None => { + values.insert(pconf.to_string(), self.to_values()); + } + }; + + Ok(values) + } +} + +impl Uncacher> for Repo { + fn __rm(pconf: &str, name: &str) -> Result>, Error> { + let mut values = Self::load()?; + + let removed = match values.get_mut(pconf) { + Some(p) => p.repos.remove(name), + None => false + }; + + if removed { Ok(Some(values)) } + else { Ok(None) } + } +} diff --git a/src/cache/structure.rs b/src/cache/structure.rs new file mode 100644 index 0000000..d870930 --- /dev/null +++ b/src/cache/structure.rs @@ -0,0 +1,74 @@ +use std::{collections::{HashMap, HashSet}, path::PathBuf}; + +use color_print::cformat; +use grp_core::{Error, JSON}; +use serde::{Deserialize, Serialize}; + +use crate::system::{directories::{Cache, Directories}, file::File}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Values { + pub users: HashSet, + pub repos: HashSet +} + +pub trait Cacher { + fn path() -> Result { Cache::file() } + fn load() -> Result, Error> { + let path = Self::path()?; + let text = File::read(&path)?; + + if text.is_empty() { return Ok(HashMap::new()) } + + JSON::from_str(&text) + } + + fn get(pconf: &str) -> Result, Error>; + fn put(&self, pconf: &str, union: bool) -> Result, Error>; + + fn save(&self, pconf: &str, union: bool) -> Result<(), Error> { + let path = Self::path()?; + let data = self.put(pconf, union)?; + + let contents = serde_json::to_string_pretty(&data) + .map_err(|e| Error::new_custom( + "Error creating the configuration file".to_string(), + vec![ + cformat!("* Error: {:?}", e) + ] + ))?; + + File::write(&path, &contents) + } +} + +pub trait Uncacher +where + T: Cacher +{ + fn load() -> Result, Error> { T::load() } + fn remove(pconf: &str, name: &str) -> Result { + let path = T::path()?; + let data = match Self::__rm(pconf, name)? { + Some(d) => d, + None => return Ok(false), + }; + + let contents = serde_json::to_string_pretty(&data) + .map_err(|e| Error::new_custom( + "Error creating the configuration file".to_string(), + vec![ + cformat!("* Error: {:?}", e) + ] + ))?; + + File::write(&path, &contents)?; + Ok(true) + } + fn __rm(pconf: &str, name: &str) -> Result>, Error>; +} + +pub trait Hasher { + fn to_set(&self) -> HashSet; + fn to_values(&self) -> Values; +} diff --git a/src/cache/users.rs b/src/cache/users.rs new file mode 100644 index 0000000..809f334 --- /dev/null +++ b/src/cache/users.rs @@ -0,0 +1,73 @@ +use grp_core::Error; +use grp_core::structs::User; +use std::collections::{HashMap, HashSet}; + +use crate::cache::structure::{Cacher, Hasher, Uncacher, Values}; + +impl Hasher for Vec { + fn to_set(&self) -> HashSet { + let mut hash = HashSet::new(); + self.iter() + .map(|s| { + match &s.path { + Some(p) => p.clone(), + None => s.name.clone(), + } + }) + .for_each(|s| { hash.insert(s); }); + + hash + } + + fn to_values(&self) -> Values { + Values { + users: self.to_set(), + repos: HashSet::new() + } + } +} + +impl Cacher for Vec { + fn get(pconf: &str) -> Result, Error> { + let values = Self::load()?; + match values.get(pconf) { + Some(v) => Ok(v.users.clone()), + None => Ok(HashSet::new()), + } + } + + fn put(&self, pconf: &str, union: bool) -> Result, Error> { + let mut values = Self::load()?; + + match values.get_mut(pconf) { + Some(p) if union => { + p.users = p.users + .union(&self.to_set()) + .cloned() + .collect(); + } + Some(p) => { + p.users = self.to_set(); + } + None => { + values.insert(pconf.to_string(), self.to_values()); + } + }; + + Ok(values) + } +} + +impl Uncacher> for User { + fn __rm(pconf: &str, name: &str) -> Result>, Error> { + let mut values = Self::load()?; + + let removed = match values.get_mut(pconf) { + Some(p) => p.users.remove(name), + _ => false + }; + + if removed { Ok(Some(values)) } + else { Ok(None) } + } +} diff --git a/src/commands/completions/git/branch.rs b/src/commands/completions/git/branch.rs index ef1000a..8781e68 100644 --- a/src/commands/completions/git/branch.rs +++ b/src/commands/completions/git/branch.rs @@ -1,6 +1,5 @@ use std::{env, ffi::OsStr}; -use clap_complete::CompletionCandidate; use git2::BranchType; use super::super::structure::Completer; @@ -10,7 +9,7 @@ pub struct Branch; impl<'a> Completer for Branch { - fn canditates(current: &OsStr) -> Vec { + fn canditates(current: &OsStr) -> Vec { let prefix = current.to_string_lossy(); let path = match env::current_dir() { Ok(p) => p, @@ -22,12 +21,12 @@ impl<'a> Completer for Branch { Err(_) => return vec![], }; - let remotes = match repo.branches(Some(BranchType::Local)) { + let branches = match repo.branches(Some(BranchType::Local)) { Ok(sa) => sa, Err(_) => return vec![], }; - remotes + branches .filter_map(|b| { let branch = match b { Ok(b) => b, @@ -40,7 +39,7 @@ impl<'a> Completer for Branch { }; if prefix.is_empty() || name.starts_with(&*prefix) { - Some(CompletionCandidate::new(name)) + Some(name.to_string()) } else { None } diff --git a/src/commands/completions/git/mod.rs b/src/commands/completions/git/mod.rs index 02e5105..3d8ad4c 100644 --- a/src/commands/completions/git/mod.rs +++ b/src/commands/completions/git/mod.rs @@ -1,2 +1,3 @@ +pub mod upstream; pub mod remote; pub mod branch; diff --git a/src/commands/completions/git/remote.rs b/src/commands/completions/git/remote.rs index b4309a8..2c41344 100644 --- a/src/commands/completions/git/remote.rs +++ b/src/commands/completions/git/remote.rs @@ -1,7 +1,5 @@ use std::{env, ffi::OsStr}; -use clap_complete::CompletionCandidate; - use super::super::structure::Completer; @@ -9,7 +7,7 @@ pub struct Remote; impl<'a> Completer for Remote { - fn canditates(current: &OsStr) -> Vec { + fn canditates(current: &OsStr) -> Vec { let prefix = current.to_string_lossy(); let path = match env::current_dir() { Ok(p) => p, @@ -26,7 +24,7 @@ impl<'a> Completer for Remote { Err(_) => return vec![], }; - if remotes.len() == 0 { return vec![CompletionCandidate::new("[no-remote]")] } + if remotes.len() == 0 { return vec![] } remotes .iter() @@ -37,7 +35,7 @@ impl<'a> Completer for Remote { }; if prefix.is_empty() || remote.starts_with(&*prefix) { - Some(CompletionCandidate::new(remote)) + Some(remote.to_string()) } else { None } diff --git a/src/commands/completions/git/upstream.rs b/src/commands/completions/git/upstream.rs new file mode 100644 index 0000000..659b693 --- /dev/null +++ b/src/commands/completions/git/upstream.rs @@ -0,0 +1,23 @@ +use std::{env, ffi::OsStr}; + +use super::super::structure::Completer; +use crate::commands::completions::git::{branch::Branch, remote::Remote}; + + +pub struct Upstream; + +impl<'a> Completer for Upstream { + fn canditates(current: &OsStr) -> Vec { + let len: usize = env::args().len(); + let index: Option = env::args() + .rposition(|arg| matches!(arg.as_str(), "-u" | "--set-upstream")) + .map(|index| index + 1); + + match index { + Some(index) if (len - index) == 0 => vec![String::new()], + Some(index) if (len - index) == 1 => Remote::canditates(current), + Some(index) if (len - index) == 2 => Branch::canditates(current), + _ => Vec::with_capacity(0), + } + } +} diff --git a/src/commands/completions/mod.rs b/src/commands/completions/mod.rs index 9cc7a95..b77ba7a 100644 --- a/src/commands/completions/mod.rs +++ b/src/commands/completions/mod.rs @@ -1,4 +1,5 @@ pub mod repostructure; pub mod structure; pub mod usettings; +pub mod repo; pub mod git; diff --git a/src/commands/completions/repo.rs b/src/commands/completions/repo.rs new file mode 100644 index 0000000..63e62e2 --- /dev/null +++ b/src/commands/completions/repo.rs @@ -0,0 +1,30 @@ +use std::ffi::OsStr; +use grp_core::structs::Repo; + +use crate::commands::completions::structure::Completer; +use crate::cache::structure::Cacher; + +type VecRepo = Vec; + +impl Completer for Repo { + fn canditates(current: &OsStr) -> Vec { + let current = match current.to_str() { + Some(s) => s, + None => return vec![], + }; + + match VecRepo::get("gh") { + Ok(s) => { + s.into_iter() + .flat_map(|s| { + if s.starts_with(current) { + Some(s) + } + else { None } + }) + .collect() + }, + Err(_) => vec![], + } + } +} \ No newline at end of file diff --git a/src/commands/completions/repostructure.rs b/src/commands/completions/repostructure.rs index d00776b..d5261ed 100644 --- a/src/commands/completions/repostructure.rs +++ b/src/commands/completions/repostructure.rs @@ -1,51 +1,77 @@ use std::ffi::OsStr; use clap_complete::CompletionCandidate; +use grp_core::structs::Repo; +use crate::cache::structure::Cacher; +use crate::candiates; use crate::usettings::structs::Usettings; use super::structure::Completer; use super::super::validations::repo::RepoStructure; +type VRepo = Vec; impl Completer for RepoStructure { - fn canditates(current: &OsStr) -> Vec { - let prefix = current.to_string_lossy(); + candiates!(); + + fn ccanditates(current: &OsStr) -> Vec { + let prefix = current.to_string_lossy(); let is_simple = prefix.starts_with("."); + let is_complete_pconf = prefix.contains(":"); - let candidates: Vec = match Usettings::read() { - Ok(u) if u.pconfs.len() > 0 => u.pconfs - .iter() - .filter_map(|p| { - let compl = - if p.owner.is_empty() || is_simple - { format!("{}:", p.name) } - else - { format!("{}:{}/", p.name, p.owner) }; - - let dot_compl = format!(".{compl}"); - - if - prefix.is_empty() || - compl.starts_with(&*prefix) || - ( is_simple && dot_compl.starts_with(&*prefix) ) - { Some(compl) } - else - { None } - }) - .collect() - , - _ => vec![], - }; - - candidates - .iter() - .map(|c| { - if - is_simple && - candidates.len() > 1 - { CompletionCandidate::new(c).add_prefix(".") } - else - { CompletionCandidate::new(c) } - }) - .collect() + match (is_simple, is_complete_pconf) { + (true, true) => vec![], + (is_simple, false) => { + let real_current: String = if is_simple { + prefix.chars().skip(1).collect() + } else { + prefix.to_string() + }; + + let candidates = Usettings::canditates(&OsStr::new(real_current.as_str())); + let candidates_len = candidates.len(); + candidates + .into_iter() + .flat_map(|c| { + if !c.starts_with(&*prefix) { return None } + let mut candidate = CompletionCandidate::new(format!("{c}:")); + + if candidates_len != 1 && is_simple { + candidate = candidate.add_prefix("."); + } + + Some(candidate) + }) + .collect() + }, + (false, true) => { + let parts: Vec<&str> = prefix.split(':').collect(); + + if parts.len() > 2 { return vec![] } + + let pconf = *parts.get(0).unwrap(); + let body = *parts.get(1).unwrap_or(&""); + + let pconf_owner = match Usettings::read() { + Ok(us) => us + .get_pconf_by_name(pconf) + .map(|p| format!("{}/", p.owner)) + .unwrap_or_default(), + Err(_) => todo!(), + }; + + let repos = match VRepo::get(pconf) { + Ok(mut r) => { let _ = r.insert(pconf_owner); r }, + Err(_) => return vec![], + }; + + repos.iter() + .flat_map(|s| { + if !s.starts_with(body) { return None } + let candidate = format!("{pconf}:{s}"); + Some(CompletionCandidate::new(candidate)) + }) + .collect() + }, + } } } diff --git a/src/commands/completions/structure.rs b/src/commands/completions/structure.rs index 0e0bc0e..315b8e8 100644 --- a/src/commands/completions/structure.rs +++ b/src/commands/completions/structure.rs @@ -2,8 +2,17 @@ use std::ffi::OsStr; use clap_complete::engine::{ArgValueCompleter, CompletionCandidate}; +#[macro_export] +macro_rules! candiates { + () => { fn canditates(_: &std::ffi::OsStr) -> Vec { unreachable!() } }; +} pub(crate) trait Completer: 'static { - fn complete() -> ArgValueCompleter { ArgValueCompleter::new(Self::canditates) } - fn canditates(current: &OsStr) -> Vec; + fn complete() -> ArgValueCompleter { ArgValueCompleter::new(Self::ccanditates) } + fn ccanditates(current: &OsStr) -> Vec { + Self::canditates(current).iter() + .map(|s| CompletionCandidate::new(s)) + .collect() + } + fn canditates(current: &OsStr) -> Vec; } diff --git a/src/commands/completions/usettings.rs b/src/commands/completions/usettings.rs index d6bdb63..c206ec5 100644 --- a/src/commands/completions/usettings.rs +++ b/src/commands/completions/usettings.rs @@ -1,24 +1,22 @@ use std::ffi::OsStr; -use clap_complete::engine::CompletionCandidate; use crate::usettings::structs::Usettings; use super::structure::Completer; impl Completer for Usettings { - fn canditates(current: &OsStr) -> Vec { + fn canditates(current: &OsStr) -> Vec { let prefix = current.to_string_lossy(); // convertir a &str (fallar con cadena vacĂ­a si no es UTF-8) match Self::read() { Ok(u) if u.pconfs.len() > 0 => u.pconfs .iter() .filter_map(|p| if prefix.is_empty() || p.name.starts_with(&*prefix) { - Some(CompletionCandidate::new(p.name.clone())) + Some(p.name.clone()) } else { None }) .collect() , - Ok(_) => vec![CompletionCandidate::new("[no_repo]")], - Err(_) => vec![], + _ => vec![], } } } diff --git a/src/commands/config/commands/path.rs b/src/commands/config/commands/path.rs index 6307b26..5540f17 100644 --- a/src/commands/config/commands/path.rs +++ b/src/commands/config/commands/path.rs @@ -1,6 +1,7 @@ use clap::{command, Command}; -use crate::system::{directories::Directories, stdout}; +use crate::system::stdout; +use crate::system::directories::{Config, Directories}; pub fn command() -> Command { command!("path") @@ -9,7 +10,7 @@ pub fn command() -> Command { } pub fn manager() { - match Directories::config_dir() { + match Config::directory() { Ok(path) => { let string = path.as_os_str().to_str(); match string { diff --git a/src/commands/local/pull.rs b/src/commands/local/pull.rs index f226881..98d7d3d 100644 --- a/src/commands/local/pull.rs +++ b/src/commands/local/pull.rs @@ -8,6 +8,7 @@ use grp_core::Error; use crate::commands::completions::git::branch::Branch; use crate::commands::completions::git::remote::Remote; +use crate::commands::completions::git::upstream::Upstream; use crate::commands::completions::structure::Completer; use crate::commands::core::args::Arguments; use crate::local::git::options::{Methods, Options}; @@ -22,14 +23,15 @@ pub(crate) fn command() -> Command { command!("pull").aliases(["j"]) .about(cformat!("Interface to git pull using the given pconf")) .args([ - Arguments::pconf(false, true), arg!( -f --force "Do a force pull"), arg!( -r --rebase).help(cformat!("Do a pull --rebase")), arg!( -n --"dry-run" "Do everything except actually fetch the updates."), Arg::new("set-upstream").short('u').long("set-upstream") .num_args(2) .value_names(["remote", "branch"]) + .add(Upstream::complete()) .help("Sets the name of the remote as default upstream for a branch"), + Arguments::pconf(false, true), arg!([remote] "The name of the remote to pull from") .conflicts_with("set-upstream") .add(Remote::complete()), diff --git a/src/commands/local/push.rs b/src/commands/local/push.rs index 9de90b5..bb97d38 100644 --- a/src/commands/local/push.rs +++ b/src/commands/local/push.rs @@ -7,6 +7,7 @@ use grp_core::Error; use crate::commands::completions::git::branch::Branch; use crate::commands::completions::git::remote::Remote; +use crate::commands::completions::git::upstream::Upstream; use crate::commands::completions::structure::Completer; use crate::commands::core::args::Arguments; use crate::local::structs::{Git2Error, Local}; @@ -19,7 +20,6 @@ pub(crate) fn command() -> Command { command!("push").aliases(["p"]) .about(cformat!("Interface to git push using the given pconf")) .args([ - Arguments::pconf(false, true), arg!( -A --all "Push all branches") .conflicts_with_all(["branches", "tags", "set-upstream", "branch", "tag"]) , @@ -38,8 +38,10 @@ pub(crate) fn command() -> Command { Arg::new("set-upstream").short('u').long("set-upstream") .num_args(2) .value_names(["remote", "branch"]) + .add(Upstream::complete()) .conflicts_with_all(["all", "branches", "tags", "tag"]) .help("Sets the name of the remote as default upstream for a branch"), + Arguments::pconf(false, true), arg!([remote] "The name of the remote to push to") .conflicts_with("set-upstream") .add(Remote::complete()) diff --git a/src/commands/orgs/commands/delete.rs b/src/commands/orgs/commands/delete.rs index 3c0b456..72eca6d 100644 --- a/src/commands/orgs/commands/delete.rs +++ b/src/commands/orgs/commands/delete.rs @@ -7,7 +7,9 @@ use color_print::cformat; use grp_core::Platform; use grp_core::animation::Animation; +use grp_core::structs::User; use crate::animations::animation::Delete; +use crate::cache::structure::Uncacher; use crate::usettings::structs::{Pconf, Usettings}; use crate::commands::core::args::Arguments; use crate::commands::core::commands::Commands; @@ -71,7 +73,7 @@ pub async fn manager(args: &ArgMatches, _usettings: Usettings) { match platform.delete_org(name, &config, !soft, &animation).await { Ok(_) => { - let message = match (soft, platform) { + let message = match (soft, &platform) { (true, Platform::Gitlab) => cformat!("group marked for delition!"), (true, _) => vec![ cformat!("org delition succeeded!"), @@ -81,6 +83,7 @@ pub async fn manager(args: &ArgMatches, _usettings: Usettings) { (false, _) => cformat!("org delition succeeded!"), }; + let _ = User::remove(&pconf.name, &name); animation.finish_with_success(message); }, Err(e) => { diff --git a/src/commands/orgs/commands/list.rs b/src/commands/orgs/commands/list.rs index 3bf62c1..edec859 100644 --- a/src/commands/orgs/commands/list.rs +++ b/src/commands/orgs/commands/list.rs @@ -5,6 +5,7 @@ use color_print::{cformat, cprintln}; use grp_core::animation::Animation; use grp_core::Platform; +use crate::cache::structure::Cacher; use crate::system::show::Show; use crate::animations::animation::Fetch; use crate::commands::core::args::Arguments; @@ -42,7 +43,12 @@ pub async fn manager(args: &ArgMatches, usettings: Usettings) { }; let config = pconf.to_config(); - let (orgs, _pag_error, _errors) = platform.list_orgs(&config, &animation).await; + let (orgs, _pag_error, mut _errors) = platform.list_orgs(&config, &animation).await; + + match orgs.save(&pconf.name, false) { + Ok(_) => (), + Err(e) => _errors.push(e), + }; match (orgs, _pag_error, _errors) { (o, None, e) if e.is_empty() && !o.is_empty() => { diff --git a/src/commands/repos/create.rs b/src/commands/repos/create.rs index 6a5ac3c..342f1dd 100644 --- a/src/commands/repos/create.rs +++ b/src/commands/repos/create.rs @@ -15,7 +15,7 @@ use crate::commands::validations::repo::RepoStructure; use crate::local::structs::{Git2Error, Local}; use crate::system::show::Show; use crate::local::git::structs::Action; -use crate::system::directories::Directories; +use crate::system::directories::BasicDir; use crate::usettings::structs::Usettings; pub fn command() -> Command { @@ -55,7 +55,7 @@ pub async fn manager(args: &ArgMatches, usettings: Usettings) { let add_to_local = args.get_flag("add-to-local"); let path: Option = match (add_to_local, remote) { - (true, _) => match Directories::current_dir() { + (true, _) => match BasicDir::current() { Ok(path) => Some(path), Err(e) => { animation.finish_with_error(&e.message); diff --git a/src/commands/repos/delete.rs b/src/commands/repos/delete.rs index 46466be..35e48f4 100644 --- a/src/commands/repos/delete.rs +++ b/src/commands/repos/delete.rs @@ -6,8 +6,10 @@ use color_print::cformat; use grp_core::animation::Animation; use grp_core::Platform; +use grp_core::structs::Repo; use crate::animations::animation::Delete; +use crate::cache::structure::Uncacher; use crate::commands::core::args::Arguments; use crate::commands::core::commands::Commands; use crate::commands::validations::repo::RepoStructure; @@ -92,6 +94,7 @@ pub async fn manager(args: &ArgMatches, usettings: Usettings) { (false, _) => cformat!("repo delition succeeded!"), }; + let _ = Repo::remove(&pconf.name, &repo.path); animation.finish_with_success(message); }, Err(e) => { diff --git a/src/commands/repos/list.rs b/src/commands/repos/list.rs index e5f31ae..37b7440 100644 --- a/src/commands/repos/list.rs +++ b/src/commands/repos/list.rs @@ -6,6 +6,7 @@ use grp_core::animation::Animation; use grp_core::Platform; use crate::animations::animation::Fetch; +use crate::cache::structure::Cacher; use crate::commands::core::args::Arguments; use crate::commands::core::commands::Commands; use crate::commands::validations::or_exit::structure::OrExit; @@ -30,6 +31,7 @@ pub async fn manager(args: &ArgMatches, usettings: Usettings) { None => usettings.get_default_pconf().or_exit(&animation), }; + let owner = args.get_one::("owner"); let show_errors = args.get_flag("show-errors"); let platform = match Platform::matches(&pconf.r#type) { @@ -42,7 +44,12 @@ pub async fn manager(args: &ArgMatches, usettings: Usettings) { }; let config = pconf.to_config(); - let (repos, _pag_error, _errors) = platform.list_repos(args.get_one::("owner"), &config, &animation).await; + let (repos, _pag_error, mut _errors) = platform.list_repos(owner, &config, &animation).await; + + match repos.save(&pconf.name, !owner.is_none()) { + Ok(_) => (), + Err(e) => _errors.push(e), + }; match (repos, _pag_error, _errors) { (r, None, e) if e.is_empty() && !r.is_empty() => { diff --git a/src/local/git/error.rs b/src/local/git/error.rs index 06c4a6a..66a6e95 100644 --- a/src/local/git/error.rs +++ b/src/local/git/error.rs @@ -123,7 +123,7 @@ impl Git2Error for Error { } (ErrorCode::NotFound, ErrorClass::Config, m, _) | (ErrorCode::NotFound, ErrorClass::Reference, m, _) => { - Error::new_custom(m, vec![]) + Error::new_custom(m, Vec::<&str>::new()) } (ErrorCode::NotFound, ErrorClass::Merge, m, Action::Pull) if m.contains("r:") => { diff --git a/src/main.rs b/src/main.rs index f96627e..d37290d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ // mod girep; mod local; +mod cache; mod system; mod update; mod commands; diff --git a/src/system/directories.rs b/src/system/directories.rs index 087fbeb..7c7089d 100644 --- a/src/system/directories.rs +++ b/src/system/directories.rs @@ -1,75 +1,92 @@ -use std::{fs::{create_dir_all, File}, path::PathBuf}; +use std::fs::{File, create_dir_all}; +use std::path::{Path, PathBuf}; +use directories::ProjectDirs; use grp_core::{Error, ErrorType}; -pub struct Directories; +macro_rules! create { + ($method:ident) => {{ + let pd = Self::new()?; + let directory = pd.$method(); + Self::create_dirs(directory)?; + + Ok(directory.into()) + }}; + + ($file:literal) => {{ + let directory = Self::directory()?; + let path = directory.join($file); + Self::create_files(&path)?; + Ok(path) + }} +} -impl Directories { - #[allow(dead_code)] - pub fn config_file() -> Result { - let path = Self::config_dir()?.join("config.json"); - if !path.exists() { - File::create(&path) - .expect("Failed to create config directory"); +/// ## Example +/// This `directory!(Config config_dir "config.json");` +/// +/// **Expands to:** +/// +/// ~~~rust +/// pub struct Config; +/// impl Directories for Config { +/// fn file() -> Result { create!("config.json") } +/// fn directory() -> Result { create!(config_dir) } +/// } +/// ~~~ +macro_rules! directory { + ($struct:ident $method:ident $name:literal) => { + pub struct $struct; + impl Directories for $struct { + fn file() -> Result { create!($name) } + fn directory() -> Result { create!($method) } } - Ok(path) + }; +} + +directory!(Config config_dir "config.json"); +directory!(Cache cache_dir "cache.json"); + +pub struct BasicDir; +impl BasicDir { + pub fn current() -> Result { + std::env::current_dir() + .map_err(|e| Error::new( + ErrorType::Path404, vec!["CURRENT_DIR", "Directory", &e.to_string()] + )) + } +} + +pub trait Directories { + fn file() -> Result; + fn directory() -> Result; + + fn new() -> Result { + let project = ProjectDirs::from("", "", "girep"); + project.ok_or(Error::new_custom("Internal error", vec!["* The system directories can not be determined"])) } - #[allow(dead_code)] - pub fn config_dir() -> Result { - let dir_name = "girep"; - let home_dir = match dirs::home_dir() { - Some(e) => e, - None => return Err( - Error::new( - ErrorType::Path404, - vec![ - "HOME_DIR", - "Imposible to optain the home directory" - ] - ) - ), - }; - let location = match std::env::consts::OS { - "linux" => home_dir.join(format!(".config/{}", dir_name)), - "windows" => { - let appdata = match std::env::var("APPDATA") { - Ok(s) => s, - Err(_) => return Err( - Error::new( - ErrorType::Path404, - vec![ - "APPDATA", - "The env variable is not configured!" - ] - ) - ), - }; - PathBuf::from(appdata).join(dir_name) + fn create_dirs(location: &Path) -> Result<(), Error> { + match location.exists() { + true => Ok(()), + false => { + create_dir_all(&location).map_err(|e| { + Error::new_custom("Error during directory creation", vec![e.to_string()]) + })?; + + Ok(()) }, - "macos" => home_dir.join(format!("Library/Application Support/{}", dir_name)), - _ => home_dir.join(format!(".config/{}", dir_name)), - }; - - // Create file if it does not exist - if !location.exists() { create_dir_all(&location) - .expect("Failed to create config directory"); } - - Ok(location) } - pub fn current_dir() -> Result { - let current_dir = std::env::current_dir() - .map_err(|e| Error::new( - ErrorType::Path404, - vec![ - "CURRENT_DIR", - "Directory", - &e.to_string() - ] - ))?; - - Ok(current_dir) + fn create_files(path: &Path) -> Result<(), Error> { + match path.exists() { + true => Ok(()), + false => { + File::create(&path).map_err(|e| { + Error::new_custom("Error during file creation", vec![e.to_string()]) + })?; + Ok(()) + }, + } } -} +} \ No newline at end of file diff --git a/src/system/file.rs b/src/system/file.rs new file mode 100644 index 0000000..bbd3206 --- /dev/null +++ b/src/system/file.rs @@ -0,0 +1,33 @@ +use std::{fs::{read_to_string, write}, path::Path}; + +use color_print::cformat; +use grp_core::Error; + + +pub struct File; + +impl File { + pub fn read(path: &Path) -> Result { + read_to_string(&path) + .map_err(|e| { + Error::new_custom("The config file could not be read", + vec![ + cformat!("* Error : {}", e), + cformat!(" Please check the config file at {:?}", path), + ] + ) + }) + } + + pub fn write(path: &Path, contents: &str) -> Result<(), Error> { + write(path, contents) + .map_err(|e| { + Error::new_custom("The config file could not be read", + vec![ + cformat!("* Error : {}", e), + cformat!(" Please check the config file at {:?}", path), + ] + ) + }) + } +} \ No newline at end of file diff --git a/src/system/mod.rs b/src/system/mod.rs index 4ea46e8..b03c707 100644 --- a/src/system/mod.rs +++ b/src/system/mod.rs @@ -1,3 +1,4 @@ pub mod directories; pub mod stdout; -pub mod show; \ No newline at end of file +pub mod show; +pub mod file; diff --git a/src/usettings/read.rs b/src/usettings/read.rs index 19366db..98b2918 100644 --- a/src/usettings/read.rs +++ b/src/usettings/read.rs @@ -1,36 +1,32 @@ -use color_print::{cformat, cprintln}; +use color_print::cprintln; use grp_core::{Error, ErrorType}; use super::structs::{Pconf, Usettings}; -use crate::system::directories::Directories; +use crate::system::{directories::{Config, Directories}, file::File}; impl Usettings { pub fn get_pconf_by_name(&self, name: &str) -> Option { if name == "*" { return self.get_default_pconf(); } - self.pconfs.iter().find(|pconf| pconf.name == name).cloned() + self.pconfs.iter() + .find(|pconf| pconf.name == name) + .cloned() } + pub fn get_default_pconf(&self) -> Option { - self.pconfs.iter().find(|pconf| pconf.name == self.default).cloned() + self.pconfs.iter() + .find(|pconf| pconf.name == self.default) + .cloned() } + pub fn get_pconf_or_default(&self, name: &str) -> Option { - self.get_pconf_by_name(name).or_else(|| self.get_default_pconf()) + self.get_pconf_by_name(name) + .or_else(|| self.get_default_pconf()) } + pub fn read() -> Result { - let mut path = Directories::config_file()?; - - let file = match std::fs::read_to_string(&path) { - Ok(file) => file, - Err(e) => return Err( - Error::new_custom( - "The config file could not be read".to_string(), - vec![ - cformat!("* Error : {}", e), - cformat!(" Please check the config file at {:?}", path), - ] - ) - ) - }; + let mut path = Config::file()?; + let file = File::read(&path)?; if file.is_empty() { let void_config = Usettings { diff --git a/src/usettings/write.rs b/src/usettings/write.rs index e69f446..193baee 100644 --- a/src/usettings/write.rs +++ b/src/usettings/write.rs @@ -3,20 +3,13 @@ use color_print::cformat; use grp_core::Error; use super::structs::Usettings; -use crate::system::directories::Directories; +use crate::system::{directories::{Config, Directories}, file::File}; impl Usettings { pub(crate) fn save(&self) -> Result<(), Error> { - let file_location = Directories::config_file()?; - let file = std::fs::File::create(file_location) - .map_err(|e| Error::new_custom( - "Error creating the configuration file".to_string(), - vec![ - cformat!("* Error: {:?}", e) - ] - ))?; + let path = Config::file()?; - serde_json::to_writer_pretty(file, self) + let contents = serde_json::to_string_pretty(self) .map_err(|e| Error::new_custom( "Error creating the configuration file".to_string(), vec![ @@ -24,6 +17,6 @@ impl Usettings { ] ))?; - Ok(()) + File::write(&path, &contents) } }