Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reuse table_formatting logic for almost everything #39

Merged
merged 11 commits into from
Mar 10, 2024
216 changes: 95 additions & 121 deletions youmubot-cf/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::{collections::HashMap, sync::Arc, time::Duration};

use codeforces::Contest;
use serenity::{
builder::{CreateMessage, EditMessage},
Expand All @@ -8,8 +10,15 @@ use serenity::{
model::{channel::Message, guild::Member},
utils::MessageBuilder,
};
use std::{collections::HashMap, sync::Arc, time::Duration};
use youmubot_prelude::*;

use db::{CfSavedUsers, CfUser};
pub use hook::InfoHook;
use youmubot_prelude::table_format::table_formatting_unsafe;
use youmubot_prelude::table_format::Align::{Left, Right};
use youmubot_prelude::{
table_format::{table_formatting, Align},
*,
};

mod announcer;
mod db;
Expand All @@ -26,10 +35,6 @@ impl TypeMapKey for CFClient {
type Value = Arc<codeforces::Client>;
}

use db::{CfSavedUsers, CfUser};

pub use hook::InfoHook;

/// Sets up the CF databases.
pub async fn setup(path: &std::path::Path, data: &mut TypeMap, announcers: &mut AnnouncerHandler) {
CfSavedUsers::insert_into(data, path.join("cf_saved_users.yaml"))
Expand Down Expand Up @@ -174,6 +179,7 @@ pub async fn ranks(ctx: &Context, m: &Message) -> CommandResult {

paginate_reply_fn(
move |page, ctx, msg| {
use Align::*;
let ranks = ranks.clone();
Box::pin(async move {
let page = page as usize;
Expand All @@ -184,56 +190,37 @@ pub async fn ranks(ctx: &Context, m: &Message) -> CommandResult {
}
let ranks = &ranks[start..end];

let handle_width = ranks.iter().map(|(_, cfu)| cfu.handle.len()).max().unwrap();
let username_width = ranks
const HEADERS: [&'static str; 4] = ["Rank", "Rating", "Handle", "Username"];
const ALIGNS: [Align; 4] = [Right, Right, Left, Left];

let ranks_arr = ranks
.iter()
.map(|(mem, _)| mem.distinct().len())
.max()
.unwrap();

let mut m = MessageBuilder::new();
m.push_line("```");

// Table header
m.push_line(format!(
"Rank | Rating | {:hw$} | {:uw$}",
"Handle",
"Username",
hw = handle_width,
uw = username_width
));
m.push_line(format!(
"----------------{:->hw$}---{:->uw$}",
"",
"",
hw = handle_width,
uw = username_width
));

for (id, (mem, cfu)) in ranks.iter().enumerate() {
let id = id + start + 1;
m.push_line(format!(
"{:>4} | {:>6} | {:hw$} | {:uw$}",
format!("#{}", id),
cfu.rating
.map(|v| v.to_string())
.unwrap_or_else(|| "----".to_owned()),
cfu.handle,
mem.distinct(),
hw = handle_width,
uw = username_width
));
}
.enumerate()
.map(|(i, (mem, cfu))| {
[
format!("#{}", 1 + i + start),
cfu.rating
.map(|v| v.to_string())
.unwrap_or_else(|| "----".to_owned()),
cfu.handle.clone(),
mem.distinct(),
]
})
.collect::<Vec<_>>();

let table = table_formatting(&HEADERS, &ALIGNS, ranks_arr);

m.push_line("```");
m.push(format!(
"Page **{}/{}**. Last updated **{}**",
page + 1,
total_pages,
last_updated.to_rfc2822()
));
let content = MessageBuilder::new()
.push_line(table)
.push_line(format!(
"Page **{}/{}**. Last updated **{}**",
page + 1,
total_pages,
last_updated.to_rfc2822()
))
.build();

msg.edit(ctx, EditMessage::new().content(m.build())).await?;
msg.edit(ctx, EditMessage::new().content(content)).await?;
Ok(true)
})
},
Expand Down Expand Up @@ -340,79 +327,66 @@ pub(crate) async fn contest_rank_table(
return Ok(false);
}
let ranks = &ranks[start..end];
let hw = ranks
.iter()
.map(|(mem, handle, _)| format!("{} ({})", handle, mem.distinct()).len())
.max()
.unwrap_or(0)
.max(6);
let hackw = ranks

let score_headers: Vec<&str> = [
vec!["Rank", "Handle", "User", "Total", "Hacks"],
problems
.iter()
.map(|p| p.index.as_str())
.collect::<Vec<&str>>(),
]
.concat();

let score_aligns: Vec<Align> = [
vec![Right, Left, Left, Right, Right],
problems.iter().map(|_| Right).collect::<Vec<Align>>(),
]
.concat();

let score_arr = ranks
.iter()
.map(|(_, _, row)| {
format!(
"{}/{}",
row.successful_hack_count, row.unsuccessful_hack_count
)
.len()
})
.max()
.unwrap_or(0)
.max(5);

let mut table = MessageBuilder::new();
let mut header = MessageBuilder::new();
// Header
header.push(format!(
" Rank | {:hw$} | Total | {:hackw$}",
"Handle",
"Hacks",
hw = hw,
hackw = hackw
));
for p in &problems {
header.push(format!(" | {:4}", p.index));
}
let header = header.build();
table
.push_line(&header)
.push_line(format!("{:-<w$}", "", w = header.len()));

// Body
for (mem, handle, row) in ranks {
table.push(format!(
"{:>5} | {:<hw$} | {:>5.0} | {:<hackw$}",
row.rank,
format!("{} ({})", handle, mem.distinct()),
row.points,
format!(
"{}/{}",
row.successful_hack_count, row.unsuccessful_hack_count
),
hw = hw,
hackw = hackw
));
for p in &row.problem_results {
table.push(" | ");
if p.points > 0.0 {
table.push(format!("{:^4.0}", p.points));
} else if p.best_submission_time_seconds.is_some() {
table.push(format!("{:^4}", "?"));
} else if p.rejected_attempt_count > 0 {
table.push(format!("{:^4}", format!("-{}", p.rejected_attempt_count)));
} else {
table.push(format!("{:^4}", ""));
.map(|(mem, handle, row)| {
let mut p_results: Vec<String> = Vec::new();
for result in &row.problem_results {
if result.points > 0.0 {
p_results.push(format!("{}", result.points));
} else if result.best_submission_time_seconds.is_some() {
p_results.push(format!("{}", "?"));
} else if result.rejected_attempt_count > 0 {
p_results.push(format!("-{}", result.rejected_attempt_count));
} else {
p_results.push(format!("{}", "----"));
}
}
}
table.push_line("");
}

let mut m = MessageBuilder::new();
m.push_bold_safe(&contest.name)
[
vec![
format!("{}", row.rank),
handle.clone(),
mem.distinct(),
format!("{}", row.points),
format!(
"{}/{}",
row.successful_hack_count, row.unsuccessful_hack_count
),
],
p_results,
]
.concat()
})
.collect::<Vec<_>>();

let score_table = table_formatting_unsafe(&score_headers, &score_aligns, score_arr);

let content = MessageBuilder::new()
.push_bold_safe(&contest.name)
.push(" ")
.push_line(contest.url())
.push_codeblock(table.build(), None)
.push_line(format!("Page **{}/{}**", page + 1, total_pages));
msg.edit(ctx, EditMessage::new().content(m.build())).await?;
.push_line(score_table)
.push_line(format!("Page **{}/{}**", page + 1, total_pages))
.build();

msg.edit(ctx, EditMessage::new().content(content)).await?;
Ok(true)
})
},
Expand Down
85 changes: 31 additions & 54 deletions youmubot-core/src/community/roles.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::db::Roles as DB;
use serenity::{
builder::EditMessage,
framework::standard::{macros::command, Args, CommandResult},
Expand All @@ -9,9 +8,13 @@ use serenity::{
},
utils::MessageBuilder,
};
use youmubot_prelude::*;

pub use reaction_watcher::Watchers as ReactionWatchers;
use youmubot_prelude::table_format::Align::Right;
use youmubot_prelude::table_format::{table_formatting, Align};
use youmubot_prelude::*;

use crate::db::Roles as DB;

#[command("listroles")]
#[description = "List all available roles in the server."]
Expand Down Expand Up @@ -50,59 +53,31 @@ async fn list(ctx: &Context, m: &Message, _: Args) -> CommandResult {
if end <= start {
return Ok(false);
}

let roles = &roles[start..end];
let nw = roles // name width
.iter()
.map(|(r, _)| r.name.len())
.max()
.unwrap()
.max(6);
let idw = roles[0].0.id.to_string().len();
let dw = roles
.iter()
.map(|v| v.1.len())
.max()
.unwrap()
.max(" Description ".len());
let mut m = MessageBuilder::new();
m.push_line("```");

// Table header
m.push_line(format!(
"{:nw$} | {:idw$} | {:dw$}",
"Name",
"ID",
"Description",
nw = nw,
idw = idw,
dw = dw,
));
m.push_line(format!(
"{:->nw$}---{:->idw$}---{:->dw$}",
"",
"",
"",
nw = nw,
idw = idw,
dw = dw,
));

for (role, description) in roles.iter() {
m.push_line(format!(
"{:nw$} | {:idw$} | {:dw$}",
role.name,
role.id,
description,
nw = nw,
idw = idw,
dw = dw,
));
}
m.push_line("```");
m.push(format!("Page **{}/{}**", page + 1, pages));

msg.edit(ctx, EditMessage::new().content(m.to_string()))
.await?;
const ROLE_HEADERS: [&'static str; 3] = ["Name", "ID", "Description"];
const ROLE_ALIGNS: [Align; 3] = [Right, Right, Right];

let roles_arr = roles
.iter()
.map(|(role, description)| {
[
role.name.clone(),
format!("{}", role.id),
description.clone(),
]
})
.collect::<Vec<_>>();

let roles_table = table_formatting(&ROLE_HEADERS, &ROLE_ALIGNS, roles_arr);

let content = MessageBuilder::new()
.push_line(roles_table)
.push_line(format!("Page **{}/{}**", page + 1, pages))
.build();

msg.edit(ctx, EditMessage::new().content(content)).await?;
Ok(true)
})
},
Expand Down Expand Up @@ -415,7 +390,6 @@ async fn rmrolemessage(ctx: &Context, m: &Message, _args: Args) -> CommandResult
}

mod reaction_watcher {
use crate::db::{Role, RoleMessage, Roles};
use dashmap::DashMap;
use flume::{Receiver, Sender};
use serenity::{
Expand All @@ -427,8 +401,11 @@ mod reaction_watcher {
id::{ChannelId, GuildId, MessageId},
},
};

use youmubot_prelude::*;

use crate::db::{Role, RoleMessage, Roles};

/// A set of watchers.
#[derive(Debug)]
pub struct Watchers {
Expand Down
Loading
Loading