From e6ecdbb3d882a940e10e35906cb687750a548c32 Mon Sep 17 00:00:00 2001 From: morde Date: Mon, 10 Jun 2024 11:08:56 +0300 Subject: [PATCH 01/11] added: list-remote versions command --- src/cli.rs | 7 ++- src/github_requests.rs | 28 +++++++----- src/handlers/list_remote_handler.rs | 70 +++++++++++++++++++++++++++++ src/handlers/mod.rs | 1 + 4 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 src/handlers/list_remote_handler.rs diff --git a/src/cli.rs b/src/cli.rs index 88c597f..4e3546e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,8 +1,7 @@ use crate::{ config::Config, handlers::{ - self, erase_handler, list_handler, rollback_handler, sync_handler, uninstall_handler, - update_handler, InstallResult, + self, erase_handler, list_handler, list_remote_handler, rollback_handler, sync_handler, uninstall_handler, update_handler, InstallResult }, }; use anyhow::Result; @@ -76,6 +75,9 @@ enum Cli { #[clap(visible_alias = "ls")] List, + #[clap(visible_alias = "ls-remote")] + ListRemote, + /// Generate shell completion Complete { /// Shell to generate completion for @@ -150,6 +152,7 @@ pub async fn start(config: Config) -> Result<()> { error!("Please provide a version or use the --all flag"); } } + Cli::ListRemote => list_remote_handler::start(config, client).await?, } Ok(()) diff --git a/src/github_requests.rs b/src/github_requests.rs index 7a1c47d..6ef4915 100644 --- a/src/github_requests.rs +++ b/src/github_requests.rs @@ -33,9 +33,12 @@ pub struct ErrorResponse { pub documentation_url: String, } -pub async fn get_upstream_nightly(client: &Client) -> Result { +pub async fn make_github_request + reqwest::IntoUrl>( + client: &Client, + url: T, +) -> Result { let response = client - .get("https://api.github.com/repos/neovim/neovim/releases/tags/nightly") + .get(url) .header("user-agent", "bob") .header("Accept", "application/vnd.github.v3+json") .send() @@ -43,6 +46,16 @@ pub async fn get_upstream_nightly(client: &Client) -> Result { .text() .await?; + Ok(response) +} + +pub async fn get_upstream_nightly(client: &Client) -> Result { + let response = make_github_request( + client, + "https://api.github.com/repos/neovim/neovim/releases/tags/nightly", + ) + .await?; + deserialize_response(response) } @@ -51,15 +64,8 @@ pub async fn get_commits_for_nightly( since: &DateTime, until: &DateTime, ) -> Result> { - let response = client - .get(format!( - "https://api.github.com/repos/neovim/neovim/commits?since={since}&until={until}&per_page=100")) - .header("user-agent", "bob") - .header("Accept", "application/vnd.github.v3+json") - .send() - .await? - .text() - .await?; + let response = make_github_request(client, format!( + "https://api.github.com/repos/neovim/neovim/commits?since={since}&until={until}&per_page=100")).await?; deserialize_response(response) } diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs new file mode 100644 index 0000000..b4880a1 --- /dev/null +++ b/src/handlers/list_remote_handler.rs @@ -0,0 +1,70 @@ +use std::{fs, path::PathBuf}; + +use anyhow::Result; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use yansi::Paint; + +use crate::{ + config::Config, + github_requests::{deserialize_response, make_github_request}, + helpers::{self, directories}, +}; + +pub async fn start(config: Config, client: Client) -> Result<()> { + let downloads_dir = directories::get_downloads_directory(&config).await?; + let response = make_github_request( + &client, + "https://api.github.com/repos/neovim/neovim/tags?per_page=50", + ) + .await?; + + let mut local_versions: Vec = fs::read_dir(downloads_dir)? + .filter_map(Result::ok) + .filter(|entry| { + entry + .path() + .file_name() + .unwrap() + .to_str() + .unwrap() + .contains("v") + }) + .map(|entry| entry.path()) + .collect(); + + let versions: Vec = deserialize_response(response)?; + let filtered_versions: Vec = versions + .into_iter() + .filter(|v| v.name.starts_with("v")) + .collect(); + + for version in filtered_versions { + let version_installed = local_versions.iter().any(|v| { + v.file_name() + .and_then(|str| str.to_str()) + .map_or(false, |str| str.contains(&version.name)) + }); + + if helpers::version::is_version_used(&version.name, &config).await { + println!("{}", Paint::green(version.name)); + } else if version_installed { + println!("{}", Paint::yellow(&version.name)); + + local_versions.retain(|v| { + v.file_name() + .and_then(|str| str.to_str()) + .map_or(true, |str| !str.contains(&version.name)) + }); + } else { + println!("{}", version.name); + } + } + + Ok(()) +} + +#[derive(Serialize, Deserialize, Debug)] +struct RemoteVersion { + pub name: String, +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 4e49f56..d09d0c8 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -6,6 +6,7 @@ pub mod sync_handler; pub mod uninstall_handler; pub mod update_handler; pub mod use_handler; +pub mod list_remote_handler; use super::version::types::LocalVersion; From 0a815d8df21856fde81fd8c473a1e96b3aa0265a Mon Sep 17 00:00:00 2001 From: morde Date: Mon, 10 Jun 2024 11:13:57 +0300 Subject: [PATCH 02/11] chore: formatting --- src/cli.rs | 3 ++- src/handlers/mod.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 4e3546e..ed2e886 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,8 @@ use crate::{ config::Config, handlers::{ - self, erase_handler, list_handler, list_remote_handler, rollback_handler, sync_handler, uninstall_handler, update_handler, InstallResult + self, erase_handler, list_handler, list_remote_handler, rollback_handler, sync_handler, + uninstall_handler, update_handler, InstallResult, }, }; use anyhow::Result; diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index d09d0c8..191a62c 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,12 +1,12 @@ pub mod erase_handler; pub mod install_handler; pub mod list_handler; +pub mod list_remote_handler; pub mod rollback_handler; pub mod sync_handler; pub mod uninstall_handler; pub mod update_handler; pub mod use_handler; -pub mod list_remote_handler; use super::version::types::LocalVersion; From 11479acc8890f0addc986e9112ea317fe51635cf Mon Sep 17 00:00:00 2001 From: morde Date: Mon, 10 Jun 2024 11:15:50 +0300 Subject: [PATCH 03/11] chore: clippy --- src/handlers/list_remote_handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs index b4880a1..02b506f 100644 --- a/src/handlers/list_remote_handler.rs +++ b/src/handlers/list_remote_handler.rs @@ -28,7 +28,7 @@ pub async fn start(config: Config, client: Client) -> Result<()> { .unwrap() .to_str() .unwrap() - .contains("v") + .contains('v') }) .map(|entry| entry.path()) .collect(); @@ -36,7 +36,7 @@ pub async fn start(config: Config, client: Client) -> Result<()> { let versions: Vec = deserialize_response(response)?; let filtered_versions: Vec = versions .into_iter() - .filter(|v| v.name.starts_with("v")) + .filter(|v| v.name.starts_with('v')) .collect(); for version in filtered_versions { From 325b97095ca62568af45cc833957748b32ea181f Mon Sep 17 00:00:00 2001 From: morde Date: Thu, 13 Jun 2024 14:22:42 +0300 Subject: [PATCH 04/11] added: spaces and stable version indicator --- src/handlers/list_remote_handler.rs | 24 +++++++++++++++++++----- src/helpers/version/mod.rs | 2 +- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs index 02b506f..b38135d 100644 --- a/src/handlers/list_remote_handler.rs +++ b/src/handlers/list_remote_handler.rs @@ -8,7 +8,7 @@ use yansi::Paint; use crate::{ config::Config, github_requests::{deserialize_response, make_github_request}, - helpers::{self, directories}, + helpers::{self, directories, version::search_stable_version}, }; pub async fn start(config: Config, client: Client) -> Result<()> { @@ -39,17 +39,27 @@ pub async fn start(config: Config, client: Client) -> Result<()> { .filter(|v| v.name.starts_with('v')) .collect(); + let length = filtered_versions.len(); + let mut counter = 0; + for version in filtered_versions { + counter += 1; let version_installed = local_versions.iter().any(|v| { v.file_name() .and_then(|str| str.to_str()) .map_or(false, |str| str.contains(&version.name)) }); + let stable_version_string = if search_stable_version(&client).await? == version.name { + " (stable)" + } else { + "" + }; + if helpers::version::is_version_used(&version.name, &config).await { - println!("{}", Paint::green(version.name)); + println!("{}{}", Paint::green(version.name), stable_version_string); } else if version_installed { - println!("{}", Paint::yellow(&version.name)); + println!("{}{}", Paint::yellow(&version.name), stable_version_string); local_versions.retain(|v| { v.file_name() @@ -57,14 +67,18 @@ pub async fn start(config: Config, client: Client) -> Result<()> { .map_or(true, |str| !str.contains(&version.name)) }); } else { - println!("{}", version.name); + println!("{}{}", version.name, stable_version_string); + } + + if length != counter { + println!(""); } } Ok(()) } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] struct RemoteVersion { pub name: String, } diff --git a/src/helpers/version/mod.rs b/src/helpers/version/mod.rs index d4a5a7c..7e8c212 100644 --- a/src/helpers/version/mod.rs +++ b/src/helpers/version/mod.rs @@ -132,7 +132,7 @@ pub async fn is_version_used(version: &str, config: &Config) -> bool { } } -async fn search_stable_version(client: &Client) -> Result { +pub async fn search_stable_version(client: &Client) -> Result { let response = client .get("https://api.github.com/repos/neovim/neovim/releases?per_page=10") .header("user-agent", "bob") From 4d40fc86fed221a6e6456c038564b9915fc9c7f4 Mon Sep 17 00:00:00 2001 From: morde Date: Thu, 13 Jun 2024 14:23:32 +0300 Subject: [PATCH 05/11] fix: dont look for stable version in loop --- src/handlers/list_remote_handler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs index b38135d..607933a 100644 --- a/src/handlers/list_remote_handler.rs +++ b/src/handlers/list_remote_handler.rs @@ -41,6 +41,7 @@ pub async fn start(config: Config, client: Client) -> Result<()> { let length = filtered_versions.len(); let mut counter = 0; + let stable_version = search_stable_version(&client).await?; for version in filtered_versions { counter += 1; @@ -50,7 +51,7 @@ pub async fn start(config: Config, client: Client) -> Result<()> { .map_or(false, |str| str.contains(&version.name)) }); - let stable_version_string = if search_stable_version(&client).await? == version.name { + let stable_version_string = if stable_version == version.name { " (stable)" } else { "" From 445b1a1a07aad05add408e395aca530547544e54 Mon Sep 17 00:00:00 2001 From: morde Date: Thu, 13 Jun 2024 14:39:27 +0300 Subject: [PATCH 06/11] chore: clippy --- src/handlers/list_remote_handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs index 607933a..40a41db 100644 --- a/src/handlers/list_remote_handler.rs +++ b/src/handlers/list_remote_handler.rs @@ -72,7 +72,7 @@ pub async fn start(config: Config, client: Client) -> Result<()> { } if length != counter { - println!(""); + println!(); } } From 2a5746b98cc6cf0b9473613e77aed48b986b646b Mon Sep 17 00:00:00 2001 From: morde Date: Thu, 13 Jun 2024 14:47:30 +0300 Subject: [PATCH 07/11] added: code docs --- src/handlers/list_remote_handler.rs | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs index 40a41db..c03e080 100644 --- a/src/handlers/list_remote_handler.rs +++ b/src/handlers/list_remote_handler.rs @@ -11,6 +11,34 @@ use crate::{ helpers::{self, directories, version::search_stable_version}, }; +/// Asynchronously starts the process of listing remote versions of Neovim. +/// +/// This function takes a `Config` and a `Client` as arguments. It first gets the downloads directory path by calling the `get_downloads_directory` function. +/// It then makes a GitHub API request to get the tags of the Neovim repository, which represent the versions of Neovim. +/// The function then reads the downloads directory and filters the entries that contain 'v' in their names, which represent the local versions of Neovim. +/// It deserializes the response from the GitHub API request into a vector of `RemoteVersion`. +/// It filters the versions that start with 'v' and then iterates over the filtered versions. +/// For each version, it checks if it is installed locally and if it is the stable version. +/// It then prints the version name in green if it is being used, in yellow if it is installed but not being used, and in default color if it is not installed. +/// It also appends ' (stable)' to the version name if it is the stable version. +/// +/// # Arguments +/// +/// * `config` - A `Config` containing the application configuration. +/// * `client` - A `Client` used to make the GitHub API request. +/// +/// # Returns +/// +/// This function returns a `Result` that contains `()` if the operation was successful. +/// If the operation failed, the function returns `Err` with a description of the error. +/// +/// # Example +/// +/// ```rust +/// let config = Config::default(); +/// let client = Client::new(); +/// start(config, client).await?; +/// ``` pub async fn start(config: Config, client: Client) -> Result<()> { let downloads_dir = directories::get_downloads_directory(&config).await?; let response = make_github_request( @@ -79,6 +107,22 @@ pub async fn start(config: Config, client: Client) -> Result<()> { Ok(()) } +/// Represents a remote version of Neovim. +/// +/// This struct is used to deserialize the response from the GitHub API request that gets the tags of the Neovim repository. +/// Each tag represents a version of Neovim, and the `name` field of the `RemoteVersion` struct represents the name of the version. +/// +/// # Fields +/// +/// * `name` - A `String` that represents the name of the version. +/// +/// # Example +/// +/// ```rust +/// let remote_version = RemoteVersion { +/// name: "v0.5.0".to_string(), +/// }; +/// ``` #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] struct RemoteVersion { pub name: String, From a6c8d24c381f426a2122ae14388a7166b2ed7899 Mon Sep 17 00:00:00 2001 From: morde Date: Thu, 13 Jun 2024 15:01:19 +0300 Subject: [PATCH 08/11] code docs --- src/github_requests.rs | 22 ++++++++++++++++++++++ src/handlers/list_remote_handler.rs | 2 +- src/helpers/version/mod.rs | 22 ++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/github_requests.rs b/src/github_requests.rs index 4504171..0ea3689 100644 --- a/src/github_requests.rs +++ b/src/github_requests.rs @@ -139,6 +139,28 @@ pub struct ErrorResponse { pub documentation_url: String, } +/// Asynchronously makes a GitHub API request. +/// +/// This function takes a reference to a `Client` and a URL as arguments. It sets the "user-agent" header to "bob" and the "Accept" header to "application/vnd.github.v3+json". +/// It then sends the request and awaits the response. It reads the response body as text and returns it as a `String`. +/// +/// # Arguments +/// +/// * `client` - A reference to a `Client` used to make the request. +/// * `url` - A URL that implements `AsRef` and `reqwest::IntoUrl`. +/// +/// # Returns +/// +/// This function returns a `Result` that contains a `String` representing the response body if the operation was successful. +/// If the operation failed, the function returns `Err` with a description of the error. +/// +/// # Example +/// +/// ```rust +/// let client = Client::new(); +/// let url = "https://api.github.com/repos/neovim/neovim/tags"; +/// let response = make_github_request(&client, url).await?; +/// ``` pub async fn make_github_request + reqwest::IntoUrl>( client: &Client, url: T, diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs index c03e080..006ff11 100644 --- a/src/handlers/list_remote_handler.rs +++ b/src/handlers/list_remote_handler.rs @@ -79,7 +79,7 @@ pub async fn start(config: Config, client: Client) -> Result<()> { .map_or(false, |str| str.contains(&version.name)) }); - let stable_version_string = if stable_version == version.name { + let stable_version_string = if stable_version == version.name { " (stable)" } else { "" diff --git a/src/helpers/version/mod.rs b/src/helpers/version/mod.rs index 303e695..e2c8f59 100644 --- a/src/helpers/version/mod.rs +++ b/src/helpers/version/mod.rs @@ -259,6 +259,28 @@ pub async fn is_version_used(version: &str, config: &Config) -> bool { } } +/// Asynchronously searches for the stable version of Neovim. +/// +/// This function takes a reference to a `Client` as an argument and makes a GitHub API request to get the releases of the Neovim repository. +/// It then deserializes the response into a vector of `UpstreamVersion`. +/// It finds the release that has the tag name "stable" and the release that has the same `target_commitish` as the stable release but does not have the tag name "stable". +/// The function returns the tag name of the found release. +/// +/// # Arguments +/// +/// * `client` - A reference to a `Client` used to make the GitHub API request. +/// +/// # Returns +/// +/// This function returns a `Result` that contains a `String` representing the tag name of the stable version if the operation was successful. +/// If the operation failed, the function returns `Err` with a description of the error. +/// +/// # Example +/// +/// ```rust +/// let client = Client::new(); +/// let stable_version = search_stable_version(&client).await?; +/// ``` pub async fn search_stable_version(client: &Client) -> Result { let response = client .get("https://api.github.com/repos/neovim/neovim/releases?per_page=10") From 49feef6657724d5b02c16b4bafd59e6c0a47df64 Mon Sep 17 00:00:00 2001 From: morde Date: Thu, 13 Jun 2024 15:32:29 +0300 Subject: [PATCH 09/11] add: padding --- src/handlers/list_remote_handler.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs index 006ff11..64601f6 100644 --- a/src/handlers/list_remote_handler.rs +++ b/src/handlers/list_remote_handler.rs @@ -67,12 +67,10 @@ pub async fn start(config: Config, client: Client) -> Result<()> { .filter(|v| v.name.starts_with('v')) .collect(); - let length = filtered_versions.len(); - let mut counter = 0; let stable_version = search_stable_version(&client).await?; + let padding = " ".repeat(12); for version in filtered_versions { - counter += 1; let version_installed = local_versions.iter().any(|v| { v.file_name() .and_then(|str| str.to_str()) @@ -86,9 +84,9 @@ pub async fn start(config: Config, client: Client) -> Result<()> { }; if helpers::version::is_version_used(&version.name, &config).await { - println!("{}{}", Paint::green(version.name), stable_version_string); + println!("{padding}{}{}", Paint::green(version.name), stable_version_string); } else if version_installed { - println!("{}{}", Paint::yellow(&version.name), stable_version_string); + println!("{padding}{}{}", Paint::yellow(&version.name), stable_version_string); local_versions.retain(|v| { v.file_name() @@ -96,11 +94,7 @@ pub async fn start(config: Config, client: Client) -> Result<()> { .map_or(true, |str| !str.contains(&version.name)) }); } else { - println!("{}{}", version.name, stable_version_string); - } - - if length != counter { - println!(); + println!("{padding}{}{}", version.name, stable_version_string); } } From 45614a5b97d6713281020f30c42c407ae8746cd6 Mon Sep 17 00:00:00 2001 From: morde Date: Thu, 13 Jun 2024 15:36:24 +0300 Subject: [PATCH 10/11] chore: formatting --- src/handlers/list_remote_handler.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/handlers/list_remote_handler.rs b/src/handlers/list_remote_handler.rs index 64601f6..7e24e87 100644 --- a/src/handlers/list_remote_handler.rs +++ b/src/handlers/list_remote_handler.rs @@ -84,9 +84,17 @@ pub async fn start(config: Config, client: Client) -> Result<()> { }; if helpers::version::is_version_used(&version.name, &config).await { - println!("{padding}{}{}", Paint::green(version.name), stable_version_string); + println!( + "{padding}{}{}", + Paint::green(version.name), + stable_version_string + ); } else if version_installed { - println!("{padding}{}{}", Paint::yellow(&version.name), stable_version_string); + println!( + "{padding}{}{}", + Paint::yellow(&version.name), + stable_version_string + ); local_versions.retain(|v| { v.file_name() From 933ef18b335aa5969702a0b52423830a078b3d52 Mon Sep 17 00:00:00 2001 From: morde Date: Thu, 13 Jun 2024 16:20:19 +0300 Subject: [PATCH 11/11] docs: update readme --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 2f587ff..f24ec13 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,12 @@ Update existing version, can specify either a version or the flag `--all` --- +- `bob list-remote` + +List all remote neovim versions available for download. + +--- + ## ⚙ Configuration This section is a bit more advanced and thus the user will have to do the work himself since bob doesn't do that.