This commit is contained in:
Leonard Steppy 2025-02-11 22:27:22 +01:00
commit 2f672ecf27
11 changed files with 240 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
Cargo.lock
/target
/dist

12
Cargo.toml Normal file
View File

@ -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"

26
README.md Normal file
View File

@ -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
```

4
Trunk.toml Normal file
View File

@ -0,0 +1,4 @@
[build]
filehash = false
dist = "target/dist"
public_url = "."

5
index.html Normal file
View File

@ -0,0 +1,5 @@
<!DOCTYPE html>
<html>
<head></head>
<body></body>
</html>

2
leptosfmt.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
leptosfmt -t 2 -- **/*.rs

1
rustfmt.toml Normal file
View File

@ -0,0 +1 @@
tab_spaces = 2

28
src/lib.rs Normal file
View File

@ -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<NthWeekday>
}
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")
)
}

9
src/main.rs Normal file
View File

@ -0,0 +1,9 @@
mod webpage;
use leptos::prelude::*;
fn main() {
console_error_panic_hook::set_once();
mount_to_body(webpage::App);
}

View File

@ -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<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)]
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)
}
}
}
}

11
src/webpage.rs Normal file
View File

@ -0,0 +1,11 @@
use leptos::prelude::*;
#[component]
pub fn App() -> impl IntoView {
let (count, set_count) = signal(0);
view! {
<button on:click=move |_| { *set_count.write() += 1 }>"Click me: " {count}</button>
<p>"Double count: " {move || count.get() * 2}</p>
}
}