From 135acf09b757d9ff51d3873ff811362513b27526 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 2 Jul 2025 12:56:17 -0500 Subject: [PATCH 01/12] Basic impl Display for the Release struct I'm not certain what info I want to present when listing the Releases. The idea is that the release version is the most important, and that it matches the git-tag associated with the release. I'll print that first. Next, the name of the release followed by the body text. The list of releases will become quite large for some projects, and the body text may include a changelog. Both of these will cause the output to become quite large. I will need to create a size limiter, but I'm ignoring that for now. Who created the release and when may be useful when searching for a release, so I've included that as the final section. --- src/main.rs | 2 +- src/structs/release.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 555b843..67c6241 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ async fn main() -> Result<(), gt_tool::Error> { let releases = gt_tool::api::release::list_releases(&client, &args.gitea_url, &args.repo).await?; for release in releases { - println!("{:?}", release); + println!("{}", release); } } gt_tool::cli::Commands::CreateRelease { diff --git a/src/structs/release.rs b/src/structs/release.rs index 1f8a33b..e065d5a 100644 --- a/src/structs/release.rs +++ b/src/structs/release.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] @@ -19,6 +21,31 @@ pub struct Release { author: Author, } +impl Display for Release { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let body = if self.body.len() > 0 { + &self.body + } else { + &String::from("(empty body)") + }; + write!(f, +"Tag: {} +Name: {} + {} +Published: {} (created: {}) +Author: {} ({}) +", + self.tag_name, + self.name, + body, + self.published_at, + self.created_at, + self.author.login, + self.author.email, + ) + } +} + #[derive(Debug, Deserialize, Serialize)] pub struct Author { id: usize, From ea046c929f6757412b2e03a176e9d73d1d0f27fc Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 2 Jul 2025 21:42:41 -0500 Subject: [PATCH 02/12] Print releases in reverse order for easier reading The result list has the newest item first, but I want to print them the other way around. This way the newest (and presumably most interesting) release is always the visible item, regardless of how many others have printed and scrolled off screen. --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 67c6241..5685d5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,10 @@ async fn main() -> Result<(), gt_tool::Error> { gt_tool::cli::Commands::ListReleases => { let releases = gt_tool::api::release::list_releases(&client, &args.gitea_url, &args.repo).await?; - for release in releases { + // Print in reverse order so the newest items are closest to the + // user's command prompt. Otherwise the newest item scrolls off the + // screen and can't be seen. + for release in releases.iter().rev() { println!("{}", release); } } From b82cfdb822f7a65bf9a20b5c25a1854dd4fba032 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 2 Jul 2025 22:06:36 -0500 Subject: [PATCH 03/12] Colorize the output! --- Cargo.toml | 1 + src/structs/release.rs | 23 +++++++++++++++-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index febccc4..58d4a05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] clap = { version = "4.0.7", features = ["derive", "env"] } +colored = "2.0.0" reqwest = { version = "0.11.13", features = ["json", "stream", "multipart"] } serde = { version = "1.0.152", features = ["derive"] } tokio = { version = "1.24.2", features = ["macros", "rt-multi-thread"] } diff --git a/src/structs/release.rs b/src/structs/release.rs index e065d5a..4f9fc07 100644 --- a/src/structs/release.rs +++ b/src/structs/release.rs @@ -1,5 +1,6 @@ use std::fmt::Display; +use colored::Colorize; use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] @@ -23,23 +24,29 @@ pub struct Release { impl Display for Release { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let tag = "Tag:".green().bold(); + let name = "Name:".green(); + let published = "Published:".bright_green(); + let created = "Created:".green().dimmed(); + let author = "Author:".blue(); let body = if self.body.len() > 0 { - &self.body + &self.body.white() } else { - &String::from("(empty body)") + &String::from("(empty body)").dimmed() }; + write!(f, -"Tag: {} -Name: {} +"{tag} {} +{name} {} {} -Published: {} (created: {}) -Author: {} ({}) +{published} {} ({created} {}) +{author} {} ({}) ", - self.tag_name, + self.tag_name.bold(), self.name, body, self.published_at, - self.created_at, + self.created_at.dimmed(), self.author.login, self.author.email, ) From 8120cb04898b4d7c4b1c24ea4f2a2578dc137068 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 2 Jul 2025 22:08:45 -0500 Subject: [PATCH 04/12] Remove trailing newline in Release item printout --- src/structs/release.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/structs/release.rs b/src/structs/release.rs index 4f9fc07..fd6a876 100644 --- a/src/structs/release.rs +++ b/src/structs/release.rs @@ -40,8 +40,7 @@ impl Display for Release { {name} {} {} {published} {} ({created} {}) -{author} {} ({}) -", +{author} {} ({})", self.tag_name.bold(), self.name, body, From d94c350cdeb87ac1cb7307d476a235e6a54860e7 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 2 Jul 2025 22:29:07 -0500 Subject: [PATCH 05/12] Galaxy-brained newline intersperse function Itertools already has an intersperse method for me. Why would I build my own when I can do this? There's even a `fold()` over the units that come out of the print routine. --- Cargo.toml | 1 + src/main.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 58d4a05..b2d0ad1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] clap = { version = "4.0.7", features = ["derive", "env"] } colored = "2.0.0" +itertools = "0.10.0" reqwest = { version = "0.11.13", features = ["json", "stream", "multipart"] } serde = { version = "1.0.152", features = ["derive"] } tokio = { version = "1.24.2", features = ["macros", "rt-multi-thread"] } diff --git a/src/main.rs b/src/main.rs index 5685d5f..c0f5ca9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use gt_tool::structs::release::{CreateReleaseOption, Release}; use clap::Parser; +use itertools::Itertools; use reqwest::header; use reqwest::header::ACCEPT; @@ -36,9 +37,13 @@ async fn main() -> Result<(), gt_tool::Error> { // Print in reverse order so the newest items are closest to the // user's command prompt. Otherwise the newest item scrolls off the // screen and can't be seen. - for release in releases.iter().rev() { - println!("{}", release); - } + let _ = releases + .iter() + .rev() + .map(|release| release.to_string()) + .intersperse(String::from("")) + .map(|release| println!("{}", release)) + .fold((), |_, _| () ); } gt_tool::cli::Commands::CreateRelease { name, From d4ef21e2435377b6e60067802ce2ce7a71b877ec Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 2 Jul 2025 22:36:59 -0500 Subject: [PATCH 06/12] Change to free-fn intersperse for stdlib compat Itertools warns that the standard library may be stabilizing the intersperse method soon and recommends using this function instead. --- src/main.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index c0f5ca9..9d0ed86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ use gt_tool::structs::release::{CreateReleaseOption, Release}; use clap::Parser; -use itertools::Itertools; use reqwest::header; use reqwest::header::ACCEPT; @@ -37,11 +36,13 @@ async fn main() -> Result<(), gt_tool::Error> { // Print in reverse order so the newest items are closest to the // user's command prompt. Otherwise the newest item scrolls off the // screen and can't be seen. - let _ = releases - .iter() - .rev() - .map(|release| release.to_string()) - .intersperse(String::from("")) + let _ = itertools::Itertools::intersperse( + releases + .iter() + .rev() + .map(|release| release.to_string()), + String::from("") + ) .map(|release| println!("{}", release)) .fold((), |_, _| () ); } From f068e8233e3f6a07ebf76b0b0257f35448110e4d Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Thu, 3 Jul 2025 17:46:12 -0500 Subject: [PATCH 07/12] `Release.colorized()`, not std::fmt::Display I don't know for sure if the string-ified version of a Release struct is being printed to the terminal. As such, I don't know if the user wants, does not want, or has mixed intentions for the stringification of this thing. No Display impl, instead just a `colorized()` method. --- src/main.rs | 2 +- src/structs/release.rs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9d0ed86..e5c1dd2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,7 +40,7 @@ async fn main() -> Result<(), gt_tool::Error> { releases .iter() .rev() - .map(|release| release.to_string()), + .map(|release| release.colorized()), String::from("") ) .map(|release| println!("{}", release)) diff --git a/src/structs/release.rs b/src/structs/release.rs index fd6a876..365d171 100644 --- a/src/structs/release.rs +++ b/src/structs/release.rs @@ -1,4 +1,3 @@ -use std::fmt::Display; use colored::Colorize; use serde::{Deserialize, Serialize}; @@ -22,8 +21,8 @@ pub struct Release { author: Author, } -impl Display for Release { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl Release { + pub fn colorized(&self) -> String { let tag = "Tag:".green().bold(); let name = "Name:".green(); let published = "Published:".bright_green(); @@ -35,7 +34,7 @@ impl Display for Release { &String::from("(empty body)").dimmed() }; - write!(f, + format!( "{tag} {} {name} {} {} From 336f1453b95129fdda111f8ff84b240722df0134 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 2 Jul 2025 22:51:58 -0500 Subject: [PATCH 08/12] Address most of the cargo-clippy lints --- src/api/release.rs | 6 +++--- src/api/release_attachment.rs | 2 +- src/main.rs | 4 ++-- src/structs/release.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/api/release.rs b/src/api/release.rs index 4250d3f..a3f4917 100644 --- a/src/api/release.rs +++ b/src/api/release.rs @@ -20,7 +20,7 @@ pub async fn list_releases( ) -> Result> { let request_url = format!("{gitea_url}/api/v1/repos/{repo}/releases/"); let req = client.get(request_url).send().await; - let response = req.map_err(|reqwest_err| crate::Error::WrappedReqwestErr(reqwest_err))?; + let response = req.map_err(crate::Error::WrappedReqwestErr)?; if response.status().is_success() { let release_list = response .json::>() @@ -50,12 +50,12 @@ pub async fn create_release( .json(&submission) .send() .await - .map_err(|e| crate::Error::from(e))?; + .map_err(crate::Error::from)?; if response.status().is_success() { let new_release = response .json::() .await - .map_err(|e| crate::Error::from(e))?; + .map_err(crate::Error::from)?; return Ok(new_release); } else if response.status().is_client_error() { let mesg = crate::decode_client_error(response).await?; diff --git a/src/api/release_attachment.rs b/src/api/release_attachment.rs index ae626ef..706192f 100644 --- a/src/api/release_attachment.rs +++ b/src/api/release_attachment.rs @@ -44,7 +44,7 @@ pub async fn create_release_attachment( let attachment_desc = response .json::() .await - .map_err(|e| crate::Error::from(e))?; + .map_err( crate::Error::from)?; return Ok(attachment_desc); } else if response.status().is_client_error() { let mesg = crate::decode_client_error(response).await?; diff --git a/src/main.rs b/src/main.rs index e5c1dd2..04fbbe4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ async fn main() -> Result<(), gt_tool::Error> { // Print in reverse order so the newest items are closest to the // user's command prompt. Otherwise the newest item scrolls off the // screen and can't be seen. - let _ = itertools::Itertools::intersperse( + itertools::Itertools::intersperse( releases .iter() .rev() @@ -152,5 +152,5 @@ fn match_release_by_tag(tag: &String, releases: Vec) -> Option } } } - return release; + release } diff --git a/src/structs/release.rs b/src/structs/release.rs index 365d171..4888350 100644 --- a/src/structs/release.rs +++ b/src/structs/release.rs @@ -28,7 +28,7 @@ impl Release { let published = "Published:".bright_green(); let created = "Created:".green().dimmed(); let author = "Author:".blue(); - let body = if self.body.len() > 0 { + let body = if !self.body.is_empty() { &self.body.white() } else { &String::from("(empty body)").dimmed() From c9dda5760c1082a9a687322a44c6af3d2325c9e0 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Wed, 2 Jul 2025 22:53:42 -0500 Subject: [PATCH 09/12] Prefix unused variables to quiet the linter --- src/api/release.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/api/release.rs b/src/api/release.rs index a3f4917..311417d 100644 --- a/src/api/release.rs +++ b/src/api/release.rs @@ -6,7 +6,7 @@ use crate::{ }, }; -pub fn get_release(id: u64) -> Result { +pub fn get_release(_id: u64) -> Result { todo!(); } pub fn get_latest_release() -> Result { @@ -63,9 +63,9 @@ pub async fn create_release( } panic!("Reached end of create_release without matching a return path"); } -pub fn edit_release(id: u64) -> Result { +pub fn edit_release(_id: u64) -> Result { todo!(); } -pub fn delete_release(id: u64) -> Result<()> { +pub fn delete_release(_id: u64) -> Result<()> { todo!(); } From 84eaaa1dbde0393dbc95ea557e26867fa9b284c6 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Thu, 3 Jul 2025 17:30:56 -0500 Subject: [PATCH 10/12] Autoformat --- src/api/release.rs | 7 ++----- src/api/release_attachment.rs | 8 +++++--- src/lib.rs | 6 ++---- src/main.rs | 34 ++++++++++++---------------------- src/structs/release.rs | 3 +-- 5 files changed, 22 insertions(+), 36 deletions(-) diff --git a/src/api/release.rs b/src/api/release.rs index 311417d..fa2f8c4 100644 --- a/src/api/release.rs +++ b/src/api/release.rs @@ -1,9 +1,6 @@ - use crate::{ Result, - structs::{ - release::{CreateReleaseOption, Release}, - }, + structs::release::{CreateReleaseOption, Release}, }; pub fn get_release(_id: u64) -> Result { @@ -59,7 +56,7 @@ pub async fn create_release( return Ok(new_release); } else if response.status().is_client_error() { let mesg = crate::decode_client_error(response).await?; - return Err(crate::Error::ApiErrorMessage(mesg)) + return Err(crate::Error::ApiErrorMessage(mesg)); } panic!("Reached end of create_release without matching a return path"); } diff --git a/src/api/release_attachment.rs b/src/api/release_attachment.rs index 706192f..e5b7b61 100644 --- a/src/api/release_attachment.rs +++ b/src/api/release_attachment.rs @@ -22,8 +22,10 @@ pub async fn create_release_attachment( Ok(false) => return Err(crate::Error::NoSuchFile), Err(e) => { eprintln!("Uh oh! The file-exists check couldn't be done: {e}"); - panic!("TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)"); - }, + panic!( + "TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)" + ); + } } println!("Uploading file {}", &file); @@ -44,7 +46,7 @@ pub async fn create_release_attachment( let attachment_desc = response .json::() .await - .map_err( crate::Error::from)?; + .map_err(crate::Error::from)?; return Ok(attachment_desc); } else if response.status().is_client_error() { let mesg = crate::decode_client_error(response).await?; diff --git a/src/lib.rs b/src/lib.rs index 42cac0a..be67790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,13 +10,11 @@ pub struct ApiError { url: String, } -pub (crate) async fn decode_client_error(response: reqwest::Response) -> Result { +pub(crate) async fn decode_client_error(response: reqwest::Response) -> Result { response .json::() .await - .map_err(|reqwest_err| { - crate::Error::WrappedReqwestErr(reqwest_err) - }) + .map_err(|reqwest_err| crate::Error::WrappedReqwestErr(reqwest_err)) } #[derive(Debug)] diff --git a/src/main.rs b/src/main.rs index 04fbbe4..1c98e8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ - use std::path; use gt_tool::cli::Args; @@ -22,10 +21,7 @@ async fn main() -> Result<(), gt_tool::Error> { headers.append("Authorization", token.parse().unwrap()); } let client = reqwest::Client::builder() - .user_agent(format!( - "gt-tools-agent-{}", - env!("CARGO_PKG_VERSION") - )) + .user_agent(format!("gt-tools-agent-{}", env!("CARGO_PKG_VERSION"))) .default_headers(headers) .build()?; @@ -37,14 +33,11 @@ async fn main() -> Result<(), gt_tool::Error> { // user's command prompt. Otherwise the newest item scrolls off the // screen and can't be seen. itertools::Itertools::intersperse( - releases - .iter() - .rev() - .map(|release| release.colorized()), - String::from("") - ) - .map(|release| println!("{}", release)) - .fold((), |_, _| () ); + releases.iter().rev().map(|release| release.colorized()), + String::from(""), + ) + .map(|release| println!("{}", release)) + .fold((), |_, _| ()); } gt_tool::cli::Commands::CreateRelease { name, @@ -61,13 +54,8 @@ async fn main() -> Result<(), gt_tool::Error> { tag_name, target_commitish, }; - gt_tool::api::release::create_release( - &client, - &args.gitea_url, - &args.repo, - submission, - ) - .await?; + gt_tool::api::release::create_release(&client, &args.gitea_url, &args.repo, submission) + .await?; } gt_tool::cli::Commands::UploadRelease { tag_name, @@ -97,8 +85,10 @@ async fn main() -> Result<(), gt_tool::Error> { Ok(false) => return Err(gt_tool::Error::NoSuchFile), Err(e) => { eprintln!("Uh oh! The file-exists check couldn't be done: {e}"); - panic!("TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)"); - }, + panic!( + "TODO: Deal with scenario where the file's existence cannot be checked (e.g.: no permission)" + ); + } } } for file in files { diff --git a/src/structs/release.rs b/src/structs/release.rs index 4888350..9ed537e 100644 --- a/src/structs/release.rs +++ b/src/structs/release.rs @@ -1,4 +1,3 @@ - use colored::Colorize; use serde::{Deserialize, Serialize}; @@ -35,7 +34,7 @@ impl Release { }; format!( -"{tag} {} + "{tag} {} {name} {} {} {published} {} ({created} {}) From 7246c7afb61ad980277f29e63020072190861eb2 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Thu, 3 Jul 2025 18:05:18 -0500 Subject: [PATCH 11/12] Oops, missed one --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index be67790..70a11a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ pub(crate) async fn decode_client_error(response: reqwest::Response) -> Result() .await - .map_err(|reqwest_err| crate::Error::WrappedReqwestErr(reqwest_err)) + .map_err(crate::Error::WrappedReqwestErr) } #[derive(Debug)] From 119831481e6ff143eae79d911b7858a7bf0dfe6f Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Thu, 3 Jul 2025 18:13:09 -0500 Subject: [PATCH 12/12] Bump to v2.2.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index b2d0ad1..4fd569c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gt-tool" -version = "1.0.0" +version = "2.2.0" edition = "2024" [dependencies]