Merge pull request 'add user commands' (#86) from user-commands into main

Reviewed-on: https://codeberg.org/Cyborus/forgejo-cli/pulls/86
This commit is contained in:
Cyborus 2024-07-08 23:53:16 +00:00
commit 61f513146f
2 changed files with 997 additions and 3 deletions

View file

@ -12,6 +12,7 @@ mod issues;
mod prs;
mod release;
mod repo;
mod user;
#[derive(Parser, Debug)]
pub struct App {
@ -37,6 +38,7 @@ pub enum Command {
#[clap(subcommand)]
Auth(auth::AuthCommand),
Release(release::ReleaseCommand),
User(user::UserCommand),
Version {
/// Checks for updates
#[clap(long)]
@ -76,6 +78,7 @@ async fn main() -> eyre::Result<()> {
}
Command::Auth(subcommand) => subcommand.run(&mut keys, host_name).await?,
Command::Release(subcommand) => subcommand.run(&mut keys, host_name).await?,
Command::User(subcommand) => subcommand.run(&mut keys, host_name).await?,
Command::Version {
#[cfg(feature = "update-check")]
check,
@ -238,7 +241,7 @@ struct SpecialRender {
// blue: &'static str,
bright_blue: &'static str,
// cyan: &'static str,
// bright_cyan: &'static str,
bright_cyan: &'static str,
yellow: &'static str,
// bright_yellow: &'static str,
// magenta: &'static str,
@ -289,7 +292,7 @@ impl SpecialRender {
// blue: "\x1b[34m",
bright_blue: "\x1b[94m",
// cyan: "\x1b[36m",
// bright_cyan: "\x1b[96m",
bright_cyan: "\x1b[96m",
yellow: "\x1b[33m",
// bright_yellow: "\x1b[93m",
// magenta: "\x1b[35m",
@ -331,7 +334,7 @@ impl SpecialRender {
// blue: "",
bright_blue: "",
// cyan: "",
// bright_cyan: "",
bright_cyan: "",
yellow: "",
// bright_yellow: "",
// magenta: "",

991
src/user.rs Normal file
View file

@ -0,0 +1,991 @@
use clap::{Args, Subcommand};
use eyre::OptionExt;
use forgejo_api::Forgejo;
use crate::{repo::RepoInfo, SpecialRender};
#[derive(Args, Clone, Debug)]
pub struct UserCommand {
#[clap(long, short = 'R')]
remote: Option<String>,
#[clap(subcommand)]
command: UserSubcommand,
}
#[derive(Subcommand, Clone, Debug)]
pub enum UserSubcommand {
Search {
/// The name to search for
query: String,
#[clap(long, short)]
page: Option<usize>,
},
View {
/// The name of the user to view
user: Option<String>,
},
Browse {
/// The name of the user to open in your browser
user: Option<String>,
},
Follow {
/// The name of the user to follow
user: String,
},
Unfollow {
/// The name of the user to follow
user: String,
},
Following {
/// The name of the user whose follows to list
user: Option<String>,
},
Followers {
/// The name of the user whose followers to list
user: Option<String>,
},
Block {
/// The name of the user to block
user: String,
},
Unblock {
/// The name of the user to unblock
user: String,
},
Repos {
/// The name of the user whose repos to list
user: Option<String>,
/// List starred repos instead of owned repos
#[clap(long)]
starred: bool,
/// Method by which to sort the list
#[clap(long)]
sort: Option<RepoSortOrder>,
},
Orgs {
/// The name of the user to view org membership of
user: Option<String>,
},
Activity {
/// The name of the user to view the activity of
user: Option<String>,
},
#[clap(subcommand)]
Edit(EditCommand),
}
#[derive(Subcommand, Clone, Debug)]
pub enum EditCommand {
/// Set your bio
Bio {
/// The new description. Leave this out to open your editor.
content: Option<String>,
},
/// Set your full name
Name {
/// The new name.
#[clap(group = "arg")]
name: Option<String>,
/// Remove your name from your profile
#[clap(long, short, group = "arg")]
unset: bool,
},
/// Set your pronouns
Pronouns {
/// The new pronouns.
#[clap(group = "arg")]
pronouns: Option<String>,
/// Remove your pronouns from your profile
#[clap(long, short, group = "arg")]
unset: bool,
},
/// Set your activity visibility
Location {
/// The new location.
#[clap(group = "arg")]
location: Option<String>,
/// Remove your location from your profile
#[clap(long, short, group = "arg")]
unset: bool,
},
/// Set your activity visibility
Activity {
/// The visibility of your activity.
#[clap(long, short)]
visibility: VisbilitySetting,
},
/// Manage the email addresses associated with your account
Email {
/// Set the visibility of your email address.
#[clap(long, short)]
visibility: Option<VisbilitySetting>,
/// Add a new email address
#[clap(long, short)]
add: Vec<String>,
/// Remove an email address
#[clap(long, short)]
rm: Vec<String>,
},
/// Set your linked website
Website {
/// Your website URL.
#[clap(group = "arg")]
url: Option<String>,
/// Remove your website from your profile
#[clap(long, short, group = "arg")]
unset: bool,
},
}
#[derive(clap::ValueEnum, Clone, Debug, PartialEq, Eq)]
pub enum VisbilitySetting {
Hidden,
Public,
}
impl UserCommand {
pub async fn run(self, keys: &mut crate::KeyInfo, host_name: Option<&str>) -> eyre::Result<()> {
let repo = RepoInfo::get_current(host_name, None, self.remote.as_deref())?;
let api = keys.get_api(repo.host_url()).await?;
match self.command {
UserSubcommand::Search { query, page } => user_search(&api, &query, page).await?,
UserSubcommand::View { user } => view_user(&api, user.as_deref()).await?,
UserSubcommand::Browse { user } => {
browse_user(&api, repo.host_url(), user.as_deref()).await?
}
UserSubcommand::Follow { user } => follow_user(&api, &user).await?,
UserSubcommand::Unfollow { user } => unfollow_user(&api, &user).await?,
UserSubcommand::Following { user } => list_following(&api, user.as_deref()).await?,
UserSubcommand::Followers { user } => list_followers(&api, user.as_deref()).await?,
UserSubcommand::Block { user } => block_user(&api, &user).await?,
UserSubcommand::Unblock { user } => unblock_user(&api, &user).await?,
UserSubcommand::Repos {
user,
starred,
sort,
} => list_repos(&api, user.as_deref(), starred, sort).await?,
UserSubcommand::Orgs { user } => list_orgs(&api, user.as_deref()).await?,
UserSubcommand::Activity { user } => list_activity(&api, user.as_deref()).await?,
UserSubcommand::Edit(cmd) => match cmd {
EditCommand::Bio { content } => edit_bio(&api, content).await?,
EditCommand::Name { name, unset } => edit_name(&api, name, unset).await?,
EditCommand::Pronouns { pronouns, unset } => {
edit_pronouns(&api, pronouns, unset).await?
}
EditCommand::Location { location, unset } => {
edit_location(&api, location, unset).await?
}
EditCommand::Activity { visibility } => edit_activity(&api, visibility).await?,
EditCommand::Email {
visibility,
add,
rm,
} => edit_email(&api, visibility, add, rm).await?,
EditCommand::Website { url, unset } => edit_website(&api, url, unset).await?,
},
}
Ok(())
}
}
async fn user_search(api: &Forgejo, query: &str, page: Option<usize>) -> eyre::Result<()> {
let page = page.unwrap_or(1);
if page == 0 {
println!("There is no page 0");
}
let query = forgejo_api::structs::UserSearchQuery {
q: Some(query.to_owned()),
..Default::default()
};
let result = api.user_search(query).await?;
let users = result.data.ok_or_eyre("search did not return data")?;
let ok = result.ok.ok_or_eyre("search did not return ok")?;
if !ok {
println!("Search failed");
return Ok(());
}
if users.is_empty() {
println!("No users matched that query");
} else {
let SpecialRender {
bullet,
dash,
bold,
reset,
..
} = *crate::special_render();
let page_start = (page - 1) * 20;
let pages_total = users.len().div_ceil(20);
if page_start >= users.len() {
if pages_total == 1 {
println!("There is only 1 page");
} else {
println!("There are only {pages_total} pages");
}
} else {
for user in users.iter().skip(page_start).take(20) {
let username = user
.login
.as_deref()
.ok_or_eyre("user does not have name")?;
println!("{bullet} {bold}{username}{reset}");
}
println!(
"Showing {bold}{}{dash}{}{reset} of {bold}{}{reset} results ({page}/{pages_total})",
page_start + 1,
(page_start + 20).min(users.len()),
users.len()
);
if users.len() > 20 {
println!("View more with the --page flag");
}
}
}
Ok(())
}
async fn view_user(api: &Forgejo, user: Option<&str>) -> eyre::Result<()> {
let SpecialRender {
bold,
dash,
bright_cyan,
light_grey,
reset,
..
} = *crate::special_render();
let user_data = match user {
Some(user) => api.user_get(user).await?,
None => api.user_get_current().await?,
};
let username = user_data
.login
.as_deref()
.ok_or_eyre("user has no username")?;
print!("{bright_cyan}{bold}{username}{reset}");
if let Some(pronouns) = user_data.pronouns.as_deref() {
if !pronouns.is_empty() {
print!("{light_grey} {dash} {bold}{pronouns}{reset}");
}
}
println!();
let followers = user_data.followers_count.unwrap_or_default();
let following = user_data.following_count.unwrap_or_default();
println!("{bold}{followers}{reset} followers {dash} {bold}{following}{reset} following");
let mut first = true;
if let Some(website) = user_data.website.as_deref() {
if !website.is_empty() {
print!("{bold}{website}{reset}");
first = false;
}
}
if let Some(email) = user_data.email.as_deref() {
if !email.is_empty() && !email.contains("noreply") {
if !first {
print!(" {dash} ");
}
print!("{bold}{email}{reset}");
}
}
if !first {
println!();
}
if let Some(desc) = user_data.description.as_deref() {
if !desc.is_empty() {
println!();
println!("{}", crate::markdown(desc));
println!();
}
}
let joined = user_data
.created
.ok_or_eyre("user does not have join date")?;
let date_format = time::macros::format_description!("[month repr:short] [day], [year]");
println!("Joined on {bold}{}{reset}", joined.format(&date_format)?);
Ok(())
}
async fn browse_user(api: &Forgejo, host_url: &url::Url, user: Option<&str>) -> eyre::Result<()> {
let username = match user {
Some(user) => user.to_owned(),
None => {
let myself = api.user_get_current().await?;
myself
.login
.ok_or_eyre("authenticated user does not have login")?
}
};
// `User` doesn't have an `html_url` field, so we gotta construct the user
// page url ourselves
let mut url = host_url.clone();
url.path_segments_mut()
.map_err(|_| eyre::eyre!("invalid host url"))?
.push(&username);
open::that(url.as_str())?;
Ok(())
}
async fn follow_user(api: &Forgejo, user: &str) -> eyre::Result<()> {
api.user_current_put_follow(user).await?;
println!("Followed {user}");
Ok(())
}
async fn unfollow_user(api: &Forgejo, user: &str) -> eyre::Result<()> {
api.user_current_delete_follow(user).await?;
println!("Unfollowed {user}");
Ok(())
}
async fn list_following(api: &Forgejo, user: Option<&str>) -> eyre::Result<()> {
let following = match user {
Some(user) => {
let query = forgejo_api::structs::UserListFollowingQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.user_list_following(user, query).await?
}
None => {
let query = forgejo_api::structs::UserCurrentListFollowingQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.user_current_list_following(query).await?
}
};
if following.is_empty() {
match user {
Some(name) => println!("{name} isn't following anyone"),
None => println!("You aren't following anyone"),
}
} else {
match user {
Some(name) => println!("{name} is following:"),
None => println!("You are following:"),
}
let SpecialRender { bullet, .. } = *crate::special_render();
for followed in following {
let username = followed
.login
.as_deref()
.ok_or_eyre("user does not have username")?;
println!("{bullet} {username}");
}
}
Ok(())
}
async fn list_followers(api: &Forgejo, user: Option<&str>) -> eyre::Result<()> {
let followers = match user {
Some(user) => {
let query = forgejo_api::structs::UserListFollowersQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.user_list_followers(user, query).await?
}
None => {
let query = forgejo_api::structs::UserCurrentListFollowersQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.user_current_list_followers(query).await?
}
};
if followers.is_empty() {
match user {
Some(name) => println!("{name} has no followers"),
None => println!("You have no followers :("),
}
} else {
match user {
Some(name) => println!("{name} is followed by:"),
None => println!("You are followed by:"),
}
let SpecialRender { bullet, .. } = *crate::special_render();
for follower in followers {
let username = follower
.login
.as_deref()
.ok_or_eyre("user does not have username")?;
println!("{bullet} {username}");
}
}
Ok(())
}
async fn block_user(api: &Forgejo, user: &str) -> eyre::Result<()> {
api.user_block_user(user).await?;
println!("Blocked {user}");
Ok(())
}
async fn unblock_user(api: &Forgejo, user: &str) -> eyre::Result<()> {
api.user_unblock_user(user).await?;
println!("Unblocked {user}");
Ok(())
}
#[derive(clap::ValueEnum, Clone, Debug, Default)]
pub enum RepoSortOrder {
#[default]
Name,
Modified,
Created,
Stars,
Forks,
}
async fn list_repos(
api: &Forgejo,
user: Option<&str>,
starred: bool,
sort: Option<RepoSortOrder>,
) -> eyre::Result<()> {
let mut repos = if starred {
match user {
Some(user) => {
let query = forgejo_api::structs::UserListStarredQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.user_list_starred(user, query).await?
}
None => {
let query = forgejo_api::structs::UserCurrentListStarredQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.user_current_list_starred(query).await?
}
}
} else {
match user {
Some(user) => {
let query = forgejo_api::structs::UserListReposQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.user_list_repos(user, query).await?
}
None => {
let query = forgejo_api::structs::UserCurrentListReposQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.user_current_list_repos(query).await?
}
}
};
if repos.is_empty() {
if starred {
match user {
Some(user) => println!("{user} has not starred any repos"),
None => println!("You have not starred any repos"),
}
} else {
match user {
Some(user) => println!("{user} does not own any repos"),
None => println!("You do not own any repos"),
}
};
} else {
let sort_fn: fn(
&forgejo_api::structs::Repository,
&forgejo_api::structs::Repository,
) -> std::cmp::Ordering = match sort.unwrap_or_default() {
RepoSortOrder::Name => |a, b| a.full_name.cmp(&b.full_name),
RepoSortOrder::Modified => |a, b| b.updated_at.cmp(&a.updated_at),
RepoSortOrder::Created => |a, b| b.created_at.cmp(&a.created_at),
RepoSortOrder::Stars => |a, b| b.stars_count.cmp(&a.stars_count),
RepoSortOrder::Forks => |a, b| b.forks_count.cmp(&a.forks_count),
};
repos.sort_unstable_by(sort_fn);
let SpecialRender { bullet, .. } = *crate::special_render();
for repo in &repos {
let name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have name")?;
println!("{bullet} {name}");
}
if repos.len() == 1 {
println!("1 repo");
} else {
println!("{} repos", repos.len());
}
}
Ok(())
}
async fn list_orgs(api: &Forgejo, user: Option<&str>) -> eyre::Result<()> {
let mut orgs = match user {
Some(user) => {
let query = forgejo_api::structs::OrgListUserOrgsQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.org_list_user_orgs(user, query).await?
}
None => {
let query = forgejo_api::structs::OrgListCurrentUserOrgsQuery {
limit: Some(u32::MAX),
..Default::default()
};
api.org_list_current_user_orgs(query).await?
}
};
if orgs.is_empty() {
match user {
Some(user) => println!("{user} is not a member of any organizations"),
None => println!("You are not a member of any organizations"),
}
} else {
orgs.sort_unstable_by(|a, b| a.name.cmp(&b.name));
let SpecialRender { bullet, dash, .. } = *crate::special_render();
for org in &orgs {
let name = org.name.as_deref().ok_or_eyre("org does not have name")?;
let full_name = org
.full_name
.as_deref()
.ok_or_eyre("org does not have name")?;
if !full_name.is_empty() {
println!("{bullet} {name} {dash} \"{full_name}\"");
} else {
println!("{bullet} {name}");
}
}
if orgs.len() == 1 {
println!("1 organization");
} else {
println!("{} organizations", orgs.len());
}
}
Ok(())
}
async fn list_activity(api: &Forgejo, user: Option<&str>) -> eyre::Result<()> {
let user = match user {
Some(s) => s.to_owned(),
None => {
let myself = api.user_get_current().await?;
myself.login.ok_or_eyre("current user does not have name")?
}
};
let query = forgejo_api::structs::UserListActivityFeedsQuery {
only_performed_by: Some(true),
..Default::default()
};
let feed = api.user_list_activity_feeds(&user, query).await?;
let SpecialRender {
bold,
yellow,
bright_cyan,
reset,
..
} = *crate::special_render();
for activity in feed {
let actor = activity
.act_user
.as_ref()
.ok_or_eyre("activity does not have actor")?;
let actor_name = actor
.login
.as_deref()
.ok_or_eyre("actor does not have name")?;
let op_type = activity
.op_type
.as_deref()
.ok_or_eyre("activity does not have op type")?;
// do not add ? to these. they are here to make each branch smaller
let repo = activity
.repo
.as_ref()
.ok_or_eyre("activity does not have repo");
let content = activity
.content
.as_deref()
.ok_or_eyre("activity does not have content");
let ref_name = activity
.ref_name
.as_deref()
.ok_or_eyre("repo does not have full name");
fn issue_name<'a, 'b>(
repo: &'a forgejo_api::structs::Repository,
content: &'b str,
) -> eyre::Result<(&'a str, &'b str)> {
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
let (issue_id, _issue_name) = content.split_once("|").unwrap_or((content, ""));
Ok((full_name, issue_id))
}
print!("");
match op_type {
"create_repo" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
if let Some(parent) = &repo.parent {
let parent_full_name = parent
.full_name
.as_deref()
.ok_or_eyre("parent repo does not have full name")?;
println!("{bold}{actor_name}{reset} forked repository {bold}{yellow}{parent_full_name}{reset} to {bold}{yellow}{full_name}{reset}");
} else {
if repo.mirror.is_some_and(|b| b) {
println!("{bold}{actor_name}{reset} created mirror {bold}{yellow}{full_name}{reset}");
} else {
println!("{bold}{actor_name}{reset} created repository {bold}{yellow}{full_name}{reset}");
}
}
}
"rename_repo" => {
let repo = repo?;
let content = content?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
println!("{bold}{actor_name}{reset} renamed repository from {bold}{yellow}\"{content}\"{reset} to {bold}{yellow}{full_name}{reset}");
}
"star_repo" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
println!(
"{bold}{actor_name}{reset} starred repository {bold}{yellow}{full_name}{reset}"
);
}
"watch_repo" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
println!(
"{bold}{actor_name}{reset} watched repository {bold}{yellow}{full_name}{reset}"
);
}
"commit_repo" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
let ref_name = ref_name?;
let branch = ref_name
.rsplit_once("/")
.map(|(_, b)| b)
.unwrap_or(ref_name);
if !content?.is_empty() {
println!("{bold}{actor_name}{reset} pushed to {bold}{bright_cyan}{branch}{reset} on {bold}{yellow}{full_name}{reset}");
}
}
"create_issue" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} opened issue {bold}{yellow}{name}#{id}{reset}");
}
"create_pull_request" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} created pull request {bold}{yellow}{name}#{id}{reset}");
}
"transfer_repo" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
let content = content?;
println!("{bold}{actor_name}{reset} transfered repository {bold}{yellow}{content}{reset} to {bold}{yellow}{full_name}{reset}");
}
"push_tag" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
let ref_name = ref_name?;
let tag = ref_name
.rsplit_once("/")
.map(|(_, b)| b)
.unwrap_or(ref_name);
println!("{bold}{actor_name}{reset} pushed tag {bold}{bright_cyan}{tag}{reset} to {bold}{yellow}{full_name}{reset}");
}
"comment_issue" => {
let (name, id) = issue_name(repo?, content?)?;
println!(
"{bold}{actor_name}{reset} commented on issue {bold}{yellow}{name}#{id}{reset}"
);
}
"merge_pull_request" | "auto_merge_pull_request" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} merged pull request {bold}{yellow}{name}#{id}{reset}");
}
"close_issue" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} closed issue {bold}{yellow}{name}#{id}{reset}");
}
"reopen_issue" => {
let (name, id) = issue_name(repo?, content?)?;
println!(
"{bold}{actor_name}{reset} reopened issue {bold}{yellow}{name}#{id}{reset}"
);
}
"close_pull_request" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} closed pull request {bold}{yellow}{name}#{id}{reset}");
}
"reopen_pull_request" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} reopened pull request {bold}{yellow}{name}#{id}{reset}");
}
"delete_tag" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
let ref_name = ref_name?;
let tag = ref_name
.rsplit_once("/")
.map(|(_, b)| b)
.unwrap_or(ref_name);
println!("{bold}{actor_name}{reset} deleted tag {bold}{bright_cyan}{tag}{reset} from {bold}{yellow}{full_name}{reset}");
}
"delete_branch" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
let ref_name = ref_name?;
let branch = ref_name
.rsplit_once("/")
.map(|(_, b)| b)
.unwrap_or(ref_name);
println!("{bold}{actor_name}{reset} deleted branch {bold}{bright_cyan}{branch}{reset} from {bold}{yellow}{full_name}{reset}");
}
"mirror_sync_push" => {}
"mirror_sync_create" => {}
"mirror_sync_delete" => {}
"approve_pull_request" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} approved {bold}{yellow}{name}#{id}{reset}");
}
"reject_pull_request" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} suggested changes for {bold}{yellow}{name}#{id}{reset}");
}
"comment_pull" => {
let (name, id) = issue_name(repo?, content?)?;
println!("{bold}{actor_name}{reset} commented on pull request {bold}{yellow}{name}#{id}{reset}");
}
"publish_release" => {
let repo = repo?;
let full_name = repo
.full_name
.as_deref()
.ok_or_eyre("repo does not have full name")?;
let content = content?;
println!("{bold}{actor_name}{reset} created release {bold}{bright_cyan}\"{content}\"{reset} to {bold}{yellow}{full_name}{reset}");
}
"pull_review_dismissed" => {}
"pull_request_ready_for_review" => {}
_ => eyre::bail!("invalid op type"),
}
}
Ok(())
}
fn default_settings_opt() -> forgejo_api::structs::UserSettingsOptions {
forgejo_api::structs::UserSettingsOptions {
description: None,
diff_view_style: None,
enable_repo_unit_hints: None,
full_name: None,
hide_activity: None,
hide_email: None,
language: None,
location: None,
pronouns: None,
theme: None,
website: None,
}
}
async fn edit_bio(api: &Forgejo, new_bio: Option<String>) -> eyre::Result<()> {
let new_bio = match new_bio {
Some(s) => s,
None => {
let mut bio = api
.user_get_current()
.await?
.description
.unwrap_or_default();
crate::editor(&mut bio, Some("md")).await?;
bio
}
};
let opt = forgejo_api::structs::UserSettingsOptions {
description: Some(new_bio),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
Ok(())
}
async fn edit_name(api: &Forgejo, new_name: Option<String>, unset: bool) -> eyre::Result<()> {
match (new_name, unset) {
(Some(_), true) => unreachable!(),
(Some(name), false) if !name.is_empty() => {
let opt = forgejo_api::structs::UserSettingsOptions {
full_name: Some(name),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
(None, true) => {
let opt = forgejo_api::structs::UserSettingsOptions {
full_name: Some(String::new()),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
_ => println!("Use --unset to remove your name from your profile"),
}
Ok(())
}
async fn edit_pronouns(
api: &Forgejo,
new_pronouns: Option<String>,
unset: bool,
) -> eyre::Result<()> {
match (new_pronouns, unset) {
(Some(_), true) => unreachable!(),
(Some(pronouns), false) if !pronouns.is_empty() => {
let opt = forgejo_api::structs::UserSettingsOptions {
pronouns: Some(pronouns),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
(None, true) => {
let opt = forgejo_api::structs::UserSettingsOptions {
pronouns: Some(String::new()),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
_ => println!("Use --unset to remove your pronouns from your profile"),
}
Ok(())
}
async fn edit_location(
api: &Forgejo,
new_location: Option<String>,
unset: bool,
) -> eyre::Result<()> {
match (new_location, unset) {
(Some(_), true) => unreachable!(),
(Some(location), false) if !location.is_empty() => {
let opt = forgejo_api::structs::UserSettingsOptions {
location: Some(location),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
(None, true) => {
let opt = forgejo_api::structs::UserSettingsOptions {
location: Some(String::new()),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
_ => println!("Use --unset to remove your location from your profile"),
}
Ok(())
}
async fn edit_activity(api: &Forgejo, visibility: VisbilitySetting) -> eyre::Result<()> {
let opt = forgejo_api::structs::UserSettingsOptions {
hide_activity: Some(visibility == VisbilitySetting::Hidden),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
Ok(())
}
async fn edit_email(
api: &Forgejo,
visibility: Option<VisbilitySetting>,
add: Vec<String>,
rm: Vec<String>,
) -> eyre::Result<()> {
if let Some(vis) = visibility {
let opt = forgejo_api::structs::UserSettingsOptions {
hide_activity: Some(vis == VisbilitySetting::Hidden),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
if !add.is_empty() {
let opt = forgejo_api::structs::CreateEmailOption { emails: Some(add) };
api.user_add_email(opt).await?;
}
if !rm.is_empty() {
let opt = forgejo_api::structs::DeleteEmailOption { emails: Some(rm) };
api.user_delete_email(opt).await?;
}
Ok(())
}
async fn edit_website(api: &Forgejo, new_url: Option<String>, unset: bool) -> eyre::Result<()> {
match (new_url, unset) {
(Some(_), true) => unreachable!(),
(Some(url), false) if !url.is_empty() => {
let opt = forgejo_api::structs::UserSettingsOptions {
website: Some(url),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
(None, true) => {
let opt = forgejo_api::structs::UserSettingsOptions {
website: Some(String::new()),
..default_settings_opt()
};
api.update_user_settings(opt).await?;
}
_ => println!("Use --unset to remove your name from your profile"),
}
Ok(())
}