commit 2f672ecf27b0a932d7ca5176a3720f7eaf032a5c Author: Steppy Date: Tue Feb 11 22:27:22 2025 +0100 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c38871 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +Cargo.lock +/target +/dist \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..09b6b0e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "jana_sessions_webpage" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +chrono = "0.4.39" +clap = { version = "4.5.28", features = ["derive"] } +leptos = { version = "0.7.5", features = ["csr"] } +console_error_panic_hook = "0.1.7" diff --git a/README.md b/README.md new file mode 100644 index 0000000..7f67d2f --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Jana Sessions Webpage + +The webpage for Jana-Sessions (unofficial name), fully written in Rust. + +## Building + +The project currently uses leptos, so you'll want to install trunk (`cargo install trunk`). + +You can build the projekt with +```bash +trunk build --release +``` +which will create the app in the `target/dist` folder. + +Alternatively you can serve it locally with +```bash +trunk serve --open +``` + +## Deployment + +Just use pythons webserver and point it to the dist folder +```bash +python3 -m http.server 8080 --directoy target/dist +``` + diff --git a/Trunk.toml b/Trunk.toml new file mode 100644 index 0000000..4c2681b --- /dev/null +++ b/Trunk.toml @@ -0,0 +1,4 @@ +[build] +filehash = false +dist = "target/dist" +public_url = "." \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..25f83eb --- /dev/null +++ b/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/leptosfmt.sh b/leptosfmt.sh new file mode 100755 index 0000000..7bcbad7 --- /dev/null +++ b/leptosfmt.sh @@ -0,0 +1,2 @@ +#!/bin/bash +leptosfmt -t 2 -- **/*.rs \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..6f2e075 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +tab_spaces = 2 \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..74dc6dd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,28 @@ +use crate::session_date_calculator::{Day, NthWeekday}; +use chrono::Weekday; +use clap::Parser; + +pub mod session_date_calculator; + +#[derive(Debug, Parser)] +pub struct StartArgs { + /// on which days of the month there are sessions + #[arg(long = "sessions", num_args = 1..)] + pub session_days: Vec +} + +pub fn localize_day(day: &Day) -> String { + format!( + "{}, {}", + match day.weekday { + Weekday::Mon => "Montag", + Weekday::Tue => "Dienstag", + Weekday::Wed => "Mittwoch", + Weekday::Thu => "Donnerstag", + Weekday::Fri => "Freitag", + Weekday::Sat => "Samstag", + Weekday::Sun => "Sonntag", + }, + day.date.format("%d.%m.%Y") + ) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..58cb77a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,9 @@ +mod webpage; + +use leptos::prelude::*; + +fn main() { + console_error_panic_hook::set_once(); + + mount_to_body(webpage::App); +} diff --git a/src/session_date_calculator.rs b/src/session_date_calculator.rs new file mode 100644 index 0000000..68015a5 --- /dev/null +++ b/src/session_date_calculator.rs @@ -0,0 +1,139 @@ +use chrono::{Datelike, Days, Local, NaiveDate, ParseWeekdayError, Weekday}; +use std::error::Error; +use std::fmt::{Display, Formatter}; +use std::num::ParseIntError; +use std::str::FromStr; + +#[derive(Debug, Copy, Clone)] +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 { + 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::() + .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, +} + +impl From for DayIter { + fn from(value: NaiveDate) -> Self { + Self { date: value.into() } + } +} + +impl From 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 { + 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)] +pub struct Day { + pub date: NaiveDate, + pub weekday: Weekday, + pub week_of_month: u8, +} + +impl From 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) + } + } + } +} diff --git a/src/webpage.rs b/src/webpage.rs new file mode 100644 index 0000000..b5ef2a8 --- /dev/null +++ b/src/webpage.rs @@ -0,0 +1,11 @@ +use leptos::prelude::*; + +#[component] +pub fn App() -> impl IntoView { + let (count, set_count) = signal(0); + + view! { + +

"Double count: " {move || count.get() * 2}

+ } +}