Migrate leptos_webpage to session_iter logic (build now broken)

This commit is contained in:
Leonard Steppy 2025-02-18 17:11:07 +01:00
parent 6208e5d1dc
commit ca5a8312e4
8 changed files with 31 additions and 451 deletions

View File

@ -13,5 +13,4 @@ leptos = { version = "0.7", features = ["csr"] }
console_error_panic_hook = "0.1.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
clap = { version = "4.5", features = ["derive"] }
session_iter = { path = "../session_iter"}

View File

@ -1,29 +0,0 @@
use clap::Parser;
use jana_sessions_webpage::localize_day;
use jana_sessions_webpage::session_date_calculator::{DayIter, NthWeekday};
use std::io::stdin;
#[derive(Debug, Parser)]
struct Args {
sessions: Vec<NthWeekday>,
}
fn main() {
let args = Args::parse();
let mut iter = DayIter::default().filter(|day| {
args
.sessions
.iter()
.any(|nth_weekday| nth_weekday.matches(day))
});
loop {
iter
.by_ref()
.take(3)
.for_each(|day| println!("{}", localize_day(&day)));
println!("Press enter for more...");
stdin().read_line(&mut String::new()).unwrap();
}
}

View File

@ -4,11 +4,11 @@ use std::fs::File;
use std::path::Path;
use std::{env, fs, io};
fn main() -> io::Result<()> {
fn main() -> Result<(), String> {
let out_dir = env::var_os("TRUNK_STAGING_DIR").unwrap_or("target/default_configs".into());
fs::create_dir_all(&out_dir)?;
fs::create_dir_all(&out_dir).map_err(|e| format!("failed to create target directory: {e}"))?;
create_default_config::<SessionConfig, _>("session_config", &out_dir)?;
create_default_config::<SessionConfig, _>("session_config", &out_dir).map_err(|e| format!("Failed to create session_config: {e}"))?;
Ok(())
}

View File

@ -1,14 +1,12 @@
use crate::session_date_calculator::Day;
use chrono::Weekday;
use session_iter::day::Day;
pub mod session;
pub mod session_date_calculator;
pub mod webpage;
pub fn localize_day(day: &Day) -> String {
format!(
"{}, {}",
match day.weekday {
match day.weekday() {
Weekday::Mon => "Montag",
Weekday::Tue => "Dienstag",
Weekday::Wed => "Mittwoch",
@ -17,6 +15,6 @@ pub fn localize_day(day: &Day) -> String {
Weekday::Sat => "Samstag",
Weekday::Sun => "Sonntag",
},
day.date.format("%d.%m.%Y")
day.date().format("%d.%m.%Y")
)
}

View File

@ -1,243 +0,0 @@
use crate::session_date_calculator::{Day, NthWeekday};
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum DatedSession {
Regular {
session: RegularSession,
day: Day,
},
Extra(ExtraSession),
Cancelled {
session: RegularSession,
day: Day,
exception: CancelException,
},
Altered {
session: RegularSession,
day: Day,
exception: AlterException,
},
}
impl DatedSession {
pub fn date(&self) -> &NaiveDate {
match self {
DatedSession::Regular { day, .. } => &day.date,
DatedSession::Extra(ExtraSession { date, .. }) => date,
DatedSession::Cancelled { day, .. } => &day.date,
DatedSession::Altered { day, .. } => &day.date,
}
}
}
impl Noted for DatedSession {
fn get_note(&self) -> Option<&String> {
match self {
DatedSession::Regular { session, .. } => session.get_note(),
DatedSession::Extra(session) => session.get_note(),
DatedSession::Cancelled { exception, .. } => exception.get_note(),
DatedSession::Altered { exception, .. } => exception.get_note(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub enum Session {
Regular(RegularSession),
Extra(ExtraSession),
}
impl From<RegularSession> for Session {
fn from(value: RegularSession) -> Self {
Self::Regular(value)
}
}
impl From<ExtraSession> for Session {
fn from(value: ExtraSession) -> Self {
Self::Extra(value)
}
}
impl Session {
pub fn into_dated(self, day: Day) -> Result<DatedSession, Self> {
if !self.is_applicable_to(&day) {
return Err(self);
}
let dated_session = match self {
Session::Regular(session) => match session.except.get(&day.date) {
Some(exception) => match exception.clone() {
Exception::Cancel(exception) => DatedSession::Cancelled {
session,
day,
exception,
},
Exception::Alter(exception) => DatedSession::Altered {
session,
day,
exception,
},
},
None => DatedSession::Regular { session, day },
},
Session::Extra(session) => DatedSession::Extra(session),
};
Ok(dated_session)
}
pub fn is_applicable_to(&self, day: &Day) -> bool {
match *self {
Session::Regular(RegularSession { rule, .. }) => rule.matches(day),
Session::Extra(ExtraSession { date, .. }) => day.date == date,
}
}
}
impl Noted for Session {
fn get_note(&self) -> Option<&String> {
match self {
Session::Regular(session) => session.get_note(),
Session::Extra(session) => session.get_note(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct RegularSession {
pub rule: NthWeekday,
pub note: Option<String>,
pub except: HashMap<NaiveDate, Exception>,
}
impl From<NthWeekday> for RegularSession {
fn from(rule: NthWeekday) -> Self {
Self {
rule,
note: None,
except: Default::default(),
}
}
}
impl RegularSession {
pub fn with_exception<D, E>(mut self, date: D, exception: E) -> Self
where
D: Into<NaiveDate>,
E: Into<Exception>,
{
self.except.insert(date.into(), exception.into());
self
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct ExtraSession {
pub date: NaiveDate,
pub note: Option<String>,
}
impl From<NaiveDate> for ExtraSession {
fn from(date: NaiveDate) -> Self {
Self { date, note: None }
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Exception {
Cancel(CancelException),
Alter(AlterException),
}
impl From<CancelException> for Exception {
fn from(value: CancelException) -> Self {
Self::Cancel(value)
}
}
impl From<AlterException> for Exception {
fn from(value: AlterException) -> Self {
Self::Alter(value)
}
}
impl Noted for Exception {
fn get_note(&self) -> Option<&String> {
match self {
Exception::Cancel(exception) => exception.get_note(),
Exception::Alter(exception) => exception.get_note(),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct CancelException {
pub note: Option<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct AlterException {
pub date: Option<NaiveDate>,
pub note: Option<String>,
}
impl From<NaiveDate> for AlterException {
fn from(value: NaiveDate) -> Self {
Self {
date: Some(value),
..Self::default()
}
}
}
pub trait Noted {
fn get_note(&self) -> Option<&String>;
}
macro_rules! impl_noted {
($ty: ident) => {
impl Noted for $ty {
fn get_note(&self) -> Option<&String> {
self.note.as_ref()
}
}
};
}
impl_noted!(RegularSession);
impl_noted!(ExtraSession);
impl_noted!(AlterException);
impl_noted!(CancelException);
pub trait WithNote {
fn with_note<S>(self, note: S) -> Self
where
S: ToString;
}
macro_rules! impl_with_node {
($ty: ident) => {
impl WithNote for $ty {
fn with_note<S>(self, note: S) -> Self
where
S: ToString,
{
#[allow(clippy::needless_update)]
Self {
note: Some(note.to_string()),
..self
}
}
}
};
}
impl_with_node!(AlterException);
impl_with_node!(CancelException);
impl_with_node!(ExtraSession);
impl_with_node!(RegularSession);

View File

@ -1,140 +0,0 @@
use chrono::{Datelike, Days, Local, NaiveDate, ParseWeekdayError, Weekday};
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::num::ParseIntError;
use std::str::FromStr;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct NthWeekday {
pub n: u8,
pub weekday: Weekday,
}
impl NthWeekday {
pub fn new(n: u8, weekday: Weekday) -> Self {
Self { n, weekday }
}
pub fn matches(&self, day: &Day) -> bool {
self.weekday == day.weekday && self.n == day.week_of_month
}
}
impl FromStr for NthWeekday {
type Err = NthWeekdayParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (number, day) = s
.split_once(' ')
.ok_or(NthWeekdayParseError::InvalidFormat)?;
let weekday = Weekday::from_str(day)
.map_err(|e| NthWeekdayParseError::InvalidWeekday(day.to_string(), e))?;
let number = number
.trim_end_matches(|c: char| !c.is_ascii_digit())
.parse::<u8>()
.map_err(|e| NthWeekdayParseError::InvalidN(number.to_string(), e))?;
Ok(Self::new(number, weekday))
}
}
#[derive(Debug)]
pub enum NthWeekdayParseError {
InvalidFormat,
InvalidWeekday(String, ParseWeekdayError),
InvalidN(String, ParseIntError),
}
impl Display for NthWeekdayParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
NthWeekdayParseError::InvalidFormat => write!(f, "Invalid format, use e.g '3rd fri'"),
NthWeekdayParseError::InvalidWeekday(week_day, e) => {
write!(f, "Invalid weekday '{week_day}': {e}")
}
NthWeekdayParseError::InvalidN(number, e) => write!(f, "Invalid number '{number}': {e}"),
}
}
}
impl Error for NthWeekdayParseError {}
#[derive(Debug)]
pub struct DayIter {
date: Option<NaiveDate>,
}
impl From<NaiveDate> for DayIter {
fn from(value: NaiveDate) -> Self {
Self { date: value.into() }
}
}
impl From<Day> for DayIter {
fn from(value: Day) -> Self {
Self {
date: value.date.into(),
}
}
}
impl Default for DayIter {
fn default() -> Self {
Self {
date: today().into(),
}
}
}
impl Iterator for DayIter {
type Item = Day;
fn next(&mut self) -> Option<Self::Item> {
let date = self.date;
self.date = date.and_then(|date| date.checked_add_days(Days::new(1)));
date.map(Day::from)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct Day {
pub date: NaiveDate,
pub weekday: Weekday,
pub week_of_month: u8,
}
impl From<NaiveDate> for Day {
fn from(date: NaiveDate) -> Self {
Self {
date,
weekday: date.weekday(),
week_of_month: date.day0() as u8 / 7 + 1,
}
}
}
impl Default for Day {
fn default() -> Self {
Self::from(today())
}
}
fn today() -> NaiveDate {
Local::now().date_naive()
}
#[cfg(test)]
mod test {
use crate::session_date_calculator::DayIter;
use chrono::NaiveDate;
#[test]
fn test_day_iter() {
let mut day_iter = DayIter::from(NaiveDate::from_ymd_opt(2025, 2, 1).expect("valid date"));
for week_of_month in 1..=4 {
for _ in 0..7 {
assert_eq!(day_iter.next().unwrap().week_of_month, week_of_month)
}
}
}
}

View File

@ -1,12 +1,12 @@
use crate::localize_day;
use crate::session::{
AlterException, CancelException, DatedSession, ExtraSession, RegularSession, Session, WithNote,
};
use crate::session_date_calculator::{Day, DayIter, NthWeekday};
use chrono::{Datelike, Days, Months, NaiveDate, Weekday};
use leptos::prelude::*;
use leptos::server_fn::request::browser::Request;
use serde::{Deserialize, Serialize};
use session_iter::day::Day;
use session_iter::session::iter::{DatedSession, DatedSessionIter};
use session_iter::session::rule::WeekdayOfMonth;
use session_iter::session::{Alternation, Cancellation, Dated, ExtraSession, RegularSession, Session, WithNote};
#[component]
pub fn App() -> impl IntoView {
@ -42,13 +42,7 @@ pub fn App() -> impl IntoView {
#[component]
fn Sessions(config: SessionConfig) -> impl IntoView {
let mut session_iter = DayIter::default().flat_map(move |day| {
config
.sessions
.clone()
.into_iter()
.filter_map(move |session| session.clone().into_dated(day.clone()).ok())
});
let mut session_iter = DatedSessionIter::new(Day::default(), config.sessions);
let (dated_sessions, mut_dated_sessions) =
signal(session_iter.by_ref().take(2).collect::<Vec<_>>());
@ -60,7 +54,7 @@ fn Sessions(config: SessionConfig) -> impl IntoView {
</div>
<For
each=move || dated_sessions.get()
key=|session| *session.date()
key=|session| session.day()
children=move |session| {
view! {
<DatedSession session />
@ -93,26 +87,26 @@ fn DatedSession(session: DatedSession) -> impl IntoView {
DatedSession::Extra(session) => view! {
<div class="box highlight-background">
<p class="small-text">"<Extra Probe>"</p>
<p><b>{localize_day(&session.date.into())}</b></p>
<p><b>{localize_day(&session.day)}</b></p>
<p>{session.note}</p>
</div>
}
.into_any(),
DatedSession::Cancelled { session, day, .. } => view! {
DatedSession::Canceled { regular, day, .. } => view! {
<div class="box cancel-background">
<p class="small-text">"<Entfall>"</p>
<p class="strikethrough bold">{localize_day(&day)}</p>
<p>{session.note}</p>
<p>{regular.note}</p>
</div>
}
.into_any(),
DatedSession::Altered {
session,
regular,
day,
exception,
cause,
..
} => {
let day = move || match exception.date.map(Day::from) {
let day = move || match cause.new_day {
None => view! { {localize_day(&day)} }.into_any(),
Some(new_day) => view! {
<span class="strikethrough">{localize_day(&day)}</span>
@ -126,7 +120,7 @@ fn DatedSession(session: DatedSession) -> impl IntoView {
<div class="box change-background">
<p class="small-text">"<Änderung>"</p>
<p class="bold">{day}</p>
<p>{exception.note.or(session.note)}</p>
<p>{cause.note.or(regular.note)}</p>
</div>
}
.into_any()
@ -171,11 +165,10 @@ macro_rules! date {
}
impl Default for SessionConfig {
#[allow(clippy::zero_prefixed_literal)]
fn default() -> Self {
const YEAR: i32 = 2024;
const NEXT_TUE_SESSION: NaiveDate = date!(18, 02, YEAR);
const NEXT_SUN_SESSION: NaiveDate = date!(02, 03, YEAR);
const NEXT_TUE_SESSION: NaiveDate = date!(18, 2, YEAR);
const NEXT_SUN_SESSION: NaiveDate = date!(2, 3, YEAR);
let next_next_sun_session = {
let some_day_next_month = NEXT_SUN_SESSION.checked_add_months(Months::new(1)).unwrap();
NaiveDate::from_weekday_of_month_opt(
@ -190,26 +183,26 @@ impl Default for SessionConfig {
Self {
motd: Some("Jeder 1. Sonntag und 3. Dienstag im Monat".into()),
sessions: vec![
RegularSession::from(NthWeekday::new(1, Weekday::Sun))
RegularSession::from(WeekdayOfMonth::new(1, Weekday::Sun))
.with_note("10:00 Uhr")
.with_exception(
.except(
NEXT_SUN_SESSION,
AlterException {
date: Some(NEXT_SUN_SESSION.checked_sub_days(Days::new(1)).unwrap()),
Alternation {
new_day: Some(NEXT_SUN_SESSION.checked_sub_days(Days::new(1)).unwrap().into()),
..Default::default()
},
)
.with_exception(
.except(
next_next_sun_session,
AlterException {
Alternation {
note: Some("11 Uhr".into()),
..Default::default()
},
)
.into(),
RegularSession::from(NthWeekday::new(3, Weekday::Tue))
RegularSession::from(WeekdayOfMonth::new(3, Weekday::Tue))
.with_note("18:30 Uhr")
.with_exception(NEXT_TUE_SESSION, CancelException::default())
.except(NEXT_TUE_SESSION, Cancellation::new())
.into(),
ExtraSession::from(NEXT_TUE_SESSION.checked_add_days(Days::new(2)).unwrap())
.with_note("18 Uhr")

View File

@ -78,6 +78,8 @@ pub struct RegularSession {
pub except: BTreeMap<Day, Except>,
}
//TODO we need to implement serialize ourselves, since json doesn't support anything other than string keys
impl From<SessionRule> for RegularSession {
fn from(rule: SessionRule) -> Self {
Self {