Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 113 additions & 2 deletions calendar_check/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ fn utc_time_to_api_time(time: &UtcTime) -> String {
time.to_rfc3339_opts(chrono::SecondsFormat::Secs, use_z)
}

#[derive(Copy, Clone, Debug, Default)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum CommunityEventType {
#[default]
OfficeHours,
SyncUp,
}

#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CommunityEventDescriptionData {
/// Type of the event, as specified by the user.
pub event_type: CommunityEventType,
Expand Down Expand Up @@ -136,14 +136,28 @@ fn parse_event_description_data(
event_title: &str,
event_description_html: &str,
) -> Option<CommunityEventDescriptionData> {
// The description field may be plain text or HTML; the API offers no indication which is the
// case.
//
// Try as text first, then fall back to HTML parsing if that fails.
if let Some(result) = parse_event_description_data_impl(event_title, event_description_html) {
return Some(result);
}
log::debug!("Parsing {event_title:?} description as text failed; trying as HTML...");
let event_description = match description_html_to_text(event_description_html) {
Err(x) => {
warn!("Failed converting event description for {event_title:?} to text: {x}");
return None;
}
Ok(x) => x,
};
parse_event_description_data_impl(event_title, &event_description)
}

fn parse_event_description_data_impl(
event_title: &str,
event_description: &str,
) -> Option<CommunityEventDescriptionData> {
log::info!("Description for {event_title:?} was {event_description:?}");

let mut event_type = None;
Expand Down Expand Up @@ -409,9 +423,106 @@ pub async fn fetch_near_llvm_calendar_office_hour_events(

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_reqwest_url_parses() {
// This has an internal `unwrap`. If it doesn't crash, we're all good.
let _ = super::get_baseline_llvm_calendar_url();
}

#[test]
fn test_parse_plain_text_basic() {
// Minimal valid plain-text description.
let basic_plain_text = "\
discord-bot-event-type: office-hours
discord-bot-channels-to-mention: #llvm-officehours
discord-bot-mention: @alice, @bob
";
assert_eq!(
parse_event_description_data("My Event", basic_plain_text),
Some(CommunityEventDescriptionData {
event_type: CommunityEventType::OfficeHours,
mention_channels: vec!["llvm-officehours".into()],
mention_users: vec!["alice".into(), "bob".into()],
ping_duration_before_start_mins: 30,
extra_message: None,
}),
);
}

#[test]
fn test_parse_plain_text_sync_up_and_all_fields() {
let desc = "\
discord-bot-event-type: sync-up
discord-bot-channels-to-mention: #general, #announcements
discord-bot-mention: @carol
discord-bot-reminder-time-before-start: 15
discord-bot-message: Don't forget!
";
assert_eq!(
parse_event_description_data("Sync", desc),
Some(CommunityEventDescriptionData {
event_type: CommunityEventType::SyncUp,
mention_channels: vec!["general".into(), "announcements".into()],
mention_users: vec!["carol".into()],
ping_duration_before_start_mins: 15,
extra_message: Some("Don't forget!".into()),
}),
);
}

#[test]
fn test_parse_no_event_type_returns_none() {
let desc = "discord-bot-channels-to-mention: #llvm-officehours\n";
assert!(parse_event_description_data("My Event", desc).is_none());
}

#[test]
fn test_parse_empty_description_returns_none() {
assert!(parse_event_description_data("My Event", "").is_none());
}

#[test]
fn test_parse_invalid_event_type_returns_none() {
let desc = "discord-bot-event-type: totally-made-up\n";
assert!(parse_event_description_data("My Event", desc).is_none());
}

#[test]
fn test_parse_html_description() {
// Same content as basic_plain_text but wrapped in HTML with <br> line breaks.
let html = "\
discord-bot-event-type: office-hours <br>\
discord-bot-channels-to-mention: #llvm-officehours<br>\
discord-bot-mention: @alice<br>";
assert_eq!(
parse_event_description_data("My Event", html),
Some(CommunityEventDescriptionData {
event_type: CommunityEventType::OfficeHours,
mention_channels: vec!["llvm-officehours".into()],
mention_users: vec!["alice".into()],
ping_duration_before_start_mins: 30,
extra_message: None,
}),
);
}

#[test]
fn test_parse_comments_stripped() {
let desc = "\
discord-bot-event-type: office-hours // this is a comment
discord-bot-channels-to-mention: #llvm-officehours // another comment
";
assert_eq!(
parse_event_description_data("My Event", desc),
Some(CommunityEventDescriptionData {
event_type: CommunityEventType::OfficeHours,
mention_channels: vec!["llvm-officehours".into()],
mention_users: vec![],
ping_duration_before_start_mins: 30,
extra_message: None,
}),
);
}
}
24 changes: 14 additions & 10 deletions calendar_check/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ use clap::Parser;
struct Args {
#[clap(long)]
debug: bool,
#[clap(long, default_value_t = 14)]
days_to_fetch: u64,
}

async fn run() -> Result<()> {
async fn run(days_to_fetch: u64) -> Result<()> {
let events = calendar_check::fetch_near_llvm_calendar_office_hour_events(
&reqwest::Client::new(),
&chrono::Utc::now(),
// Fetch a few weeks out. Doubt anyone will care about more than that.
Duration::from_secs(14 * 24 * 60 * 60),
Duration::from_secs(days_to_fetch * 24 * 60 * 60),
)
.await
.context("fetching LLVM office hour events")?;
Expand All @@ -26,16 +27,19 @@ async fn run() -> Result<()> {
fn main() -> Result<()> {
let args = Args::parse();

simple_logger::init_with_level(if args.debug {
log::Level::Debug
} else {
log::Level::Info
})
.unwrap();
simple_logger::SimpleLogger::new()
.with_level(if args.debug {
log::LevelFilter::Debug
} else {
log::LevelFilter::Info
})
.with_module_level("html5ever", log::LevelFilter::Warn)
.init()
.unwrap();

tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("building tokio runtime")?
.block_on(run())
.block_on(run(args.days_to_fetch))
}