From 12501c8c734608c7488159b825715ebb7fa3ea31 Mon Sep 17 00:00:00 2001 From: nobody Date: Sun, 18 Aug 2024 02:30:39 +0200 Subject: [PATCH] improve debuggability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit more Debug-derives, trace/verbose, … --- src/date.rs | 1 + src/main.rs | 115 ++++++++++++++++++++++++++++++++++++++++------- src/mediawiki.rs | 13 +++++- 3 files changed, 113 insertions(+), 16 deletions(-) diff --git a/src/date.rs b/src/date.rs index 5f76952..53a54c1 100644 --- a/src/date.rs +++ b/src/date.rs @@ -10,6 +10,7 @@ use std::collections::BTreeSet; use std::error::Error; /// Defines a day within the month. Negative numbers count from the end. +#[derive(Debug, Clone, Copy)] pub enum DaySpec { /// nth day, no matter the weekday DayOfMonth(i32), diff --git a/src/main.rs b/src/main.rs index 6940d2d..6860272 100644 --- a/src/main.rs +++ b/src/main.rs @@ -141,20 +141,67 @@ macro_rules! verboseln { fn is_trace() -> bool { env::var("TRACE").map(|v| !v.is_empty()).unwrap_or(false) } +/// Like `println!`, but only if `is_trace` is true (due to the environment +/// variable `TRACE` being set.) macro_rules! traceln { ($($arg:tt)*) => { if is_trace() { - println!($($arg)*); + println!( "{}", format!($($arg)*).yellow() ); } }; } +/// `trace_var!( [msg,] var )` prints either `varname = value` or `msg: value` +/// *if TRACE is set* (else is silent.) +/// +/// There's an alternative form of `trace_var!( [msg,] var[, true] )` or the +/// preferred form of `trace_var_!( [msg,] var )` (i.e. just add an underscore +/// to the name), which will use the "pretty" form. +macro_rules! trace_var { + ($var:expr, $pretty:expr) => { + if is_trace() { + if $pretty { + println!("{} = {}", stringify!($var).green(), format!("{:#?}", $var).cyan()); + } else { + println!("{} = {}", stringify!($var).green(), format!("{:?}", $var).cyan()); + } + } + }; + ($msg:expr, $var:expr, $pretty:expr) => { + if is_trace() { + if $pretty { + println!("{}: {}", $msg.green(), format!("{:#?}", $var).cyan()); + } else { + println!("{}: {}", $msg.green(), format!("{:?}", $var).cyan()); + } + } + }; + ($var:expr) => { + trace_var!($var, false); + }; + ($msg:expr, $var:expr) => { + trace_var!($msg, $var, false); + }; +} +/// Pretty form of `trace_var!` +macro_rules! trace_var_ { + ($var:expr) => { + trace_var!($var, true); + }; + ($msg:expr, $var:expr) => { + trace_var!($msg, $var, true); + }; +} + +/// Gets either today or the date from the environment variable `TODAY` (for +/// testing purposes.) fn today() -> NaiveDate { env::var("TODAY") .map(|v| NaiveDate::parse_from_str(&v, "%F").expect("'TODAY' hat nicht format YYYY-MM-DD")) .unwrap_or(Local::now().date_naive()) } +#[derive(Debug)] struct Args { check_mode: bool, config_file: String, @@ -190,8 +237,9 @@ fn parse_args() -> Args { fn main() -> Result<(), Box> { // set up config file access let args = parse_args(); + trace_var!(args); let config_file = args.config_file.as_str(); - verboseln!("Using config file {config_file}."); + verboseln!("Using config file {}.", config_file.cyan()); let config = KV::new(config_file).unwrap(); config_spec::populate_defaults(&CONFIG_SPEC, &config); if args.check_mode { @@ -200,7 +248,7 @@ fn main() -> Result<(), Box> { } // get config let hedgedoc = HedgeDoc::new(&config["hedgedoc-server-url"], is_dry_run()); - traceln!("Hedgedoc: {:?}", hedgedoc); + trace_var!(hedgedoc); let email_ = Email::new( &config["email-server"], &config["email-user"], @@ -213,18 +261,21 @@ fn main() -> Result<(), Box> { &config["email-to"], config.get("email-in-reply-to").ok(), ); - traceln!("Email: {:?}", email); + trace_var_!(email); let wiki = Mediawiki::new( &config["wiki-server-url"], &config["wiki-http-user"], &config["wiki-http-password"], is_dry_run(), ); - traceln!("Wiki: {:?}", wiki); + trace_var_!(wiki); // get next plenum days let today = today(); + verboseln!("Heute ist {}", today.to_string().cyan()); let plenum_spec = date::parse_spec(&config["date-spec"])?; + trace_var!(plenum_spec); let nearest_plenum_days = date::get_matching_dates_around(today, plenum_spec); + trace_var!(nearest_plenum_days); // figure out where we are let mut last_state = ProgramState::parse(&config["state-name"]); let last_run = config.get("state-last-run").unwrap_or_default(); @@ -256,20 +307,28 @@ fn main() -> Result<(), Box> { } .unwrap(); // always has at least 2 elems let plenum_day = today.checked_add_signed(chrono::TimeDelta::days(delta)).unwrap(); + verboseln!( + "Relevantes Plenum ist am {} ({})", + plenum_day.to_string().cyan(), + relative_date(delta).cyan() + ); let intended_state = if delta > 3 { ProgramState::Normal // nothing to do 3+ days in advance } else if delta > 1 { ProgramState::Announced // 2+ days in advance we want to have it announced } else if delta >= 0 { ProgramState::Reminded // up to the day of, we want to send a reminder (or cancel) - } else if delta > -2 { + } else if delta >= -1 { ProgramState::Waiting // we will wait a day for the protocol to be cleaned up } else { ProgramState::Logged // after that, we want to log it to the list & the wiki }; + verboseln!("Aktueller Zustand: {}", last_state.to_string().cyan()); + verboseln!("Soll-Zustand: {}", intended_state.to_string().cyan()); - let action: TransitionFunction = TRANSITION_LUT[last_state as usize][intended_state as usize]; - action(delta, &plenum_day, &config, &hedgedoc, &email, &wiki)?; + let action: &ST = &TRANSITION_LUT[last_state as usize][intended_state as usize]; + trace_var!(action); + action.get()(delta, &plenum_day, &config, &hedgedoc, &email, &wiki)?; // TODO: cleanup / write new state @@ -638,15 +697,41 @@ type TransitionFunction = fn( ) -> Result<(), Box>; #[rustfmt::skip] -const TRANSITION_LUT: [[TransitionFunction; 5]; 5] = [ - /* NORMAL ANNOUNCED REMINDED WAITING LOGGED */ - /* NORMAL */ [nop, do_announcement, do_reminder, nop, nop], - /* ANNOUNCED */ [do_cleanup, nop, do_reminder, nop, do_protocol], - /* REMINDED */ [do_cleanup, do_clean_announcement, nop, nop, do_protocol], - /* WAITING */ [do_cleanup, do_clean_announcement, do_clean_reminder, nop, do_protocol], - /* LOGGED */ [do_cleanup, do_clean_announcement, do_clean_reminder, nop, do_cleanup], +const TRANSITION_LUT: [[ST; 5]; 5] = [ + /* NORMAL ANNOUNCED REMINDED WAITING LOGGED */ + /* NORMAL */ [ST::Nop, ST::DoAnnouncement, ST::DoReminder, ST::Nop, ST::Nop], + /* ANNOUNCED */ [ST::DoCleanup, ST::Nop, ST::DoReminder, ST::Nop, ST::DoProtocol], + /* REMINDED */ [ST::DoCleanup, ST::DoCleanupThenAnnouncement, ST::Nop, ST::Nop, ST::DoProtocol], + /* WAITING */ [ST::DoCleanup, ST::DoCleanupThenAnnouncement, ST::DoCleanupThenReminder, ST::Nop, ST::DoProtocol], + /* LOGGED */ [ST::DoCleanup, ST::DoCleanupThenAnnouncement, ST::DoCleanupThenReminder, ST::Nop, ST::DoCleanup], ]; +#[derive(Debug, Default, Clone, Copy)] +enum ST { + #[default] + Nop, + DoAnnouncement, + DoReminder, + DoProtocol, + DoCleanup, + DoCleanupThenAnnouncement, + DoCleanupThenReminder, +} + +impl ST { + fn get(&self) -> TransitionFunction { + match self { + ST::Nop => nop, + ST::DoAnnouncement => do_announcement, + ST::DoReminder => do_reminder, + ST::DoProtocol => do_protocol, + ST::DoCleanup => do_cleanup, + ST::DoCleanupThenAnnouncement => do_clean_announcement, + ST::DoCleanupThenReminder => do_clean_reminder, + } + } +} + fn nop( _: i64, _: &NaiveDate, _: &KV, _: &HedgeDoc, _: &SimpleEmail, _: &Mediawiki, ) -> Result<(), Box> { diff --git a/src/mediawiki.rs b/src/mediawiki.rs index a63e470..628a9fa 100644 --- a/src/mediawiki.rs +++ b/src/mediawiki.rs @@ -35,7 +35,6 @@ pub const CONFIG: CfgGroup<'static> = CfgGroup { ], }; -#[derive(Debug)] pub struct Mediawiki { server_url: String, http_user: String, @@ -44,6 +43,18 @@ pub struct Mediawiki { client: Client, } +impl std::fmt::Debug for Mediawiki { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Mediawiki") + .field("server_url", &self.server_url) + .field("http_user", &self.http_user) + .field("http_password", &"*****") + .field("is_dry_run", &self.is_dry_run) + .field("client", &self.client) + .finish() + } +} + impl Mediawiki { pub fn new( server_url: &str, http_auth_user: &str, http_auth_password: &str, is_dry_run: bool,