From 2f672ecf27b0a932d7ca5176a3720f7eaf032a5c Mon Sep 17 00:00:00 2001 From: Steppy Date: Tue, 11 Feb 2025 22:27:22 +0100 Subject: [PATCH] init --- .gitignore | 3 + Cargo.toml | 12 +++ README.md | 26 ++++++ Trunk.toml | 4 + index.html | 5 ++ leptosfmt.sh | 2 + rustfmt.toml | 1 + src/lib.rs | 28 +++++++ src/main.rs | 9 +++ src/session_date_calculator.rs | 139 +++++++++++++++++++++++++++++++++ src/webpage.rs | 11 +++ 11 files changed, 240 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 Trunk.toml create mode 100644 index.html create mode 100755 leptosfmt.sh create mode 100644 rustfmt.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/session_date_calculator.rs create mode 100644 src/webpage.rs 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}

+ } +}