Compare commits
No commits in common. "ef0d32f0b7d8194d04f6c5bbb0d6bb7a0b11b66a" and "dd0ad8d538f817797793e43f45cb7cdfca1da662" have entirely different histories.
ef0d32f0b7
...
dd0ad8d538
4 changed files with 229 additions and 306 deletions
254
src/main.rs
254
src/main.rs
|
@ -1,13 +1,12 @@
|
||||||
use chrono::DateTime;
|
use chrono::DateTime;
|
||||||
use clap::Parser as _;
|
use clap::Parser as _;
|
||||||
use std::time::Duration;
|
use std::{collections::BTreeSet, io::Write as _, time::Duration};
|
||||||
|
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
mod output;
|
|
||||||
mod prelude;
|
mod prelude;
|
||||||
mod wac_campfire;
|
mod wac_campfire;
|
||||||
mod wac_common_ninja;
|
mod wac_common_ninja;
|
||||||
|
@ -31,12 +30,31 @@ struct CalendarUi {
|
||||||
short_name: String,
|
short_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ConfigOutput {
|
||||||
|
/// Used as the OpenGraph description in meta tags
|
||||||
|
description: String,
|
||||||
|
|
||||||
|
hide_summaries: BTreeSet<String>,
|
||||||
|
|
||||||
|
/// Hide all these UIDs from the final output
|
||||||
|
hide_uids: BTreeSet<String>,
|
||||||
|
|
||||||
|
/// Timezone to use for output (e.g. "Antarctica/South_Pole")
|
||||||
|
///
|
||||||
|
/// <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
|
||||||
|
timezone: chrono_tz::Tz,
|
||||||
|
|
||||||
|
/// Used as the page title and OpenGraph title in meta tags
|
||||||
|
title: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Config {
|
struct Config {
|
||||||
campfires: Vec<wac_campfire::Config>,
|
campfires: Vec<wac_campfire::Config>,
|
||||||
common_ninjas: Vec<wac_common_ninja::Config>,
|
common_ninjas: Vec<wac_common_ninja::Config>,
|
||||||
icals: Vec<wac_ical::Config>,
|
icals: Vec<wac_ical::Config>,
|
||||||
output: output::Config,
|
output: ConfigOutput,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
|
@ -51,26 +69,6 @@ impl Config {
|
||||||
)
|
)
|
||||||
.chain(self.icals.iter().map(|ical| ical.dl.clone()))
|
.chain(self.icals.iter().map(|ical| ical.dl.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upstreams(&self) -> Vec<CalendarUi> {
|
|
||||||
let Self {
|
|
||||||
campfires,
|
|
||||||
common_ninjas,
|
|
||||||
icals,
|
|
||||||
output: _,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
let mut upstreams: Vec<_> = campfires
|
|
||||||
.iter()
|
|
||||||
.map(|cfg| &cfg.ui)
|
|
||||||
.cloned()
|
|
||||||
.chain(common_ninjas.iter().map(|cfg| &cfg.ui).cloned())
|
|
||||||
.chain(icals.iter().map(|cfg| &cfg.ui).cloned())
|
|
||||||
.collect();
|
|
||||||
upstreams.sort_by_key(|ui| ui.short_name.clone());
|
|
||||||
|
|
||||||
upstreams
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(clap::Parser)]
|
#[derive(clap::Parser)]
|
||||||
|
@ -154,6 +152,22 @@ struct EventInstance {
|
||||||
url: Option<String>,
|
url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl EventInstance {
|
||||||
|
fn filter(&self, config_output: &ConfigOutput) -> bool {
|
||||||
|
if let Some(uid) = &self.uid
|
||||||
|
&& config_output.hide_uids.contains(uid)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if let Some(summary) = &self.summary
|
||||||
|
&& config_output.hide_summaries.contains(summary)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Data {
|
struct Data {
|
||||||
campfires: Vec<wac_campfire::Calendar>,
|
campfires: Vec<wac_campfire::Calendar>,
|
||||||
|
@ -183,7 +197,7 @@ fn read_data_from_disk(config: &Config) -> Result<Data> {
|
||||||
|
|
||||||
fn process_data<'a>(
|
fn process_data<'a>(
|
||||||
data: &'a Data,
|
data: &'a Data,
|
||||||
config_output: &'a output::Config,
|
config_output: &'a ConfigOutput,
|
||||||
now: DateTime<chrono_tz::Tz>,
|
now: DateTime<chrono_tz::Tz>,
|
||||||
) -> Result<Vec<EventInstance>> {
|
) -> Result<Vec<EventInstance>> {
|
||||||
let params = Parameters::new(now)?;
|
let params = Parameters::new(now)?;
|
||||||
|
@ -194,7 +208,7 @@ fn process_data<'a>(
|
||||||
for ev in campfire
|
for ev in campfire
|
||||||
.event_instances(¶ms)?
|
.event_instances(¶ms)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| config_output.filter(x))
|
.filter(|x| x.filter(config_output))
|
||||||
{
|
{
|
||||||
instances.push(ev);
|
instances.push(ev);
|
||||||
}
|
}
|
||||||
|
@ -204,7 +218,7 @@ fn process_data<'a>(
|
||||||
for ev in common_ninja
|
for ev in common_ninja
|
||||||
.event_instances(¶ms)?
|
.event_instances(¶ms)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| config_output.filter(x))
|
.filter(|x| x.filter(config_output))
|
||||||
{
|
{
|
||||||
instances.push(ev);
|
instances.push(ev);
|
||||||
}
|
}
|
||||||
|
@ -214,7 +228,7 @@ fn process_data<'a>(
|
||||||
for ev in ical
|
for ev in ical
|
||||||
.event_instances(¶ms)?
|
.event_instances(¶ms)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| config_output.filter(x))
|
.filter(|x| x.filter(config_output))
|
||||||
{
|
{
|
||||||
instances.push(ev);
|
instances.push(ev);
|
||||||
}
|
}
|
||||||
|
@ -224,6 +238,187 @@ fn process_data<'a>(
|
||||||
Ok(instances)
|
Ok(instances)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn output_html(
|
||||||
|
config: &ConfigOutput,
|
||||||
|
instances: &[EventInstance],
|
||||||
|
now: DateTime<chrono_tz::Tz>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let today = now.date_naive();
|
||||||
|
let mut last_month_printed: Option<String> = None;
|
||||||
|
let mut last_date_printed = None;
|
||||||
|
let mut html_list = vec![];
|
||||||
|
let mut day_list = vec![];
|
||||||
|
for ei in instances {
|
||||||
|
let date = ei.dtstart.date_naive();
|
||||||
|
let past = date < today;
|
||||||
|
let month = date.format("%B").to_string();
|
||||||
|
match last_month_printed {
|
||||||
|
Some(ref x) if *x == month => {}
|
||||||
|
None | Some(_) => {
|
||||||
|
// FIXME: De-dupe
|
||||||
|
if !day_list.is_empty() {
|
||||||
|
html_list.push(maud::html! {
|
||||||
|
ul { @for entry in day_list {
|
||||||
|
(entry)
|
||||||
|
} }
|
||||||
|
});
|
||||||
|
day_list = vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
html_list.push(maud::html! {
|
||||||
|
h2 { (month) }
|
||||||
|
});
|
||||||
|
last_month_printed = Some(month);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if last_date_printed != Some(date) {
|
||||||
|
// FIXME: De-dupe
|
||||||
|
if !day_list.is_empty() {
|
||||||
|
html_list.push(maud::html! {
|
||||||
|
ul { @for entry in day_list {
|
||||||
|
(entry)
|
||||||
|
} }
|
||||||
|
});
|
||||||
|
day_list = vec![];
|
||||||
|
}
|
||||||
|
if past {
|
||||||
|
html_list.push(maud::html! {
|
||||||
|
p class="past"{ s { (date.format("%-d %A")) } }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let date_s = date.format("%-d %A");
|
||||||
|
let id = date.format("%F");
|
||||||
|
html_list.push(maud::html! {
|
||||||
|
h3 id=(id) { (date_s) " " a href=(format!("#{id}")) title=(format!("Permalink to {date_s}")) {"🔗"} }
|
||||||
|
hr{}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
last_date_printed = Some(date);
|
||||||
|
}
|
||||||
|
let time = ei.dtstart.time();
|
||||||
|
let time = time
|
||||||
|
.map(|t| t.format("%l:%M %P").to_string())
|
||||||
|
.unwrap_or_else(|| "No time listed".to_string());
|
||||||
|
|
||||||
|
let summary = ei
|
||||||
|
.summary
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("Data error BXH45NAR - No summary in event");
|
||||||
|
let summary = if let Some(url) = &ei.url {
|
||||||
|
maud::html! {a href=(url) {(summary)}}
|
||||||
|
} else {
|
||||||
|
maud::html! {(summary)}
|
||||||
|
};
|
||||||
|
|
||||||
|
if past {
|
||||||
|
day_list.push(maud::html! {
|
||||||
|
li class="past" { (time) " - " (summary) }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let calendar_link = if let Some(html_url) = &ei.calendar_ui.html_url {
|
||||||
|
maud::html! { a href=(html_url) { (ei.calendar_ui.short_name) } }
|
||||||
|
} else {
|
||||||
|
maud::html! { (ei.calendar_ui.short_name)}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is where the main stuff happens
|
||||||
|
|
||||||
|
tracing::debug!(uid = ei.uid, summary = ei.summary);
|
||||||
|
day_list.push(maud::html! {
|
||||||
|
li { details {
|
||||||
|
summary { (time) " - " (summary) }
|
||||||
|
ul {
|
||||||
|
li { (calendar_link) " calendar" }
|
||||||
|
@if let Some(location) = &ei.location {
|
||||||
|
li { "Location: " (location) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// FIXME: De-dupe
|
||||||
|
if !day_list.is_empty() {
|
||||||
|
html_list.push(maud::html! {
|
||||||
|
ul { @for entry in day_list {
|
||||||
|
(entry)
|
||||||
|
} }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::create_dir_all("output")?;
|
||||||
|
{
|
||||||
|
let temp_path = "output/calendars.html.tmp";
|
||||||
|
let final_path = "output/calendars.html";
|
||||||
|
let mut f = std::fs::File::create(temp_path)?;
|
||||||
|
|
||||||
|
f.write_all("".as_bytes())?;
|
||||||
|
std::fs::rename(temp_path, final_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let temp_path = "output/index.html.tmp";
|
||||||
|
let final_path = "output/index.html";
|
||||||
|
let mut f = std::fs::File::create(temp_path)?;
|
||||||
|
let css = r#"
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 14pt;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
.past {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let description = &config.description;
|
||||||
|
let title = &config.title;
|
||||||
|
|
||||||
|
let s = maud::html! {
|
||||||
|
(maud::PreEscaped("<!DOCTYPE html>"))
|
||||||
|
html lang="en" {
|
||||||
|
head {
|
||||||
|
meta http-equiv="Content-Type" content="text/html; charset=utf-8" {}
|
||||||
|
meta name="viewport" content="width=device-width, initial-scale=1" {}
|
||||||
|
(maud::PreEscaped(css))
|
||||||
|
|
||||||
|
meta property="og:locale" content="en" {}
|
||||||
|
meta property="og:type" content="website" {}
|
||||||
|
|
||||||
|
meta name="description" content=(description) {}
|
||||||
|
meta property="description" content=(description) {}
|
||||||
|
meta property="og:description" content=(description) {}
|
||||||
|
|
||||||
|
title { (title) }
|
||||||
|
met property="og:title" content=(title) {}
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
h1 { (title) }
|
||||||
|
img src="hero.webp" width="700" height="233" {}
|
||||||
|
p { "Written at: " (now.format("%F %T")) }
|
||||||
|
@for entry in html_list {
|
||||||
|
(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_string();
|
||||||
|
|
||||||
|
f.write_all(s.as_bytes())?;
|
||||||
|
std::fs::rename(temp_path, final_path)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
static APP_USER_AGENT: &str = concat!(
|
static APP_USER_AGENT: &str = concat!(
|
||||||
env!("CARGO_PKG_NAME"),
|
env!("CARGO_PKG_NAME"),
|
||||||
"_Z7FSRRA7/",
|
"_Z7FSRRA7/",
|
||||||
|
@ -258,7 +453,7 @@ async fn do_everything(cli: &CliAuto) -> Result<()> {
|
||||||
|
|
||||||
let data = read_data_from_disk(&config)?;
|
let data = read_data_from_disk(&config)?;
|
||||||
let instances = process_data(&data, &config.output, now)?;
|
let instances = process_data(&data, &config.output, now)?;
|
||||||
output::write_html(&config.output, &config.upstreams(), &instances, now)?;
|
output_html(&config.output, &instances, now)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,8 +491,7 @@ fn main_debug_output(cli: CliDebugOutput) -> Result<()> {
|
||||||
let tz = &config.output.timezone;
|
let tz = &config.output.timezone;
|
||||||
let now = Utc::now().with_timezone(tz);
|
let now = Utc::now().with_timezone(tz);
|
||||||
let instances = process_data(&data, &config.output, now).context("Failed to process data")?;
|
let instances = process_data(&data, &config.output, now).context("Failed to process data")?;
|
||||||
output::write_html(&config.output, &config.upstreams(), &instances, now)
|
output_html(&config.output, &instances, now).context("Failed to output HTML")?;
|
||||||
.context("Failed to output HTML")?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
269
src/output.rs
269
src/output.rs
|
@ -1,269 +0,0 @@
|
||||||
//! The code that writes HTML for web browsers to read
|
|
||||||
|
|
||||||
use chrono::DateTime;
|
|
||||||
use std::{collections::BTreeSet, io::Write as _};
|
|
||||||
|
|
||||||
use crate::{EventInstance, prelude::*};
|
|
||||||
|
|
||||||
const CSS: &str = r#"
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 14pt;
|
|
||||||
line-height: 1.6;
|
|
||||||
max-width: 700px;
|
|
||||||
}
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
.past {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub(crate) struct Config {
|
|
||||||
/// Used as the OpenGraph description in meta tags
|
|
||||||
description: String,
|
|
||||||
|
|
||||||
hide_summaries: BTreeSet<String>,
|
|
||||||
|
|
||||||
/// Hide all these UIDs from the final output
|
|
||||||
hide_uids: BTreeSet<String>,
|
|
||||||
|
|
||||||
/// Timezone to use for output (e.g. "Antarctica/South_Pole")
|
|
||||||
///
|
|
||||||
/// <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>
|
|
||||||
pub(crate) timezone: chrono_tz::Tz,
|
|
||||||
|
|
||||||
/// Used as the page title and OpenGraph title in meta tags
|
|
||||||
title: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub(crate) fn filter(&self, instance: &EventInstance) -> bool {
|
|
||||||
if let Some(uid) = &instance.uid
|
|
||||||
&& self.hide_uids.contains(uid)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if let Some(summary) = &instance.summary
|
|
||||||
&& self.hide_summaries.contains(summary)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calendar_link(calendar_ui: &crate::CalendarUi) -> maud::PreEscaped<String> {
|
|
||||||
if let Some(html_url) = &calendar_ui.html_url {
|
|
||||||
maud::html! { a href=(html_url) { (calendar_ui.short_name) } }
|
|
||||||
} else {
|
|
||||||
maud::html! { (calendar_ui.short_name)}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn write_html(
|
|
||||||
config: &Config,
|
|
||||||
upstreams: &[crate::CalendarUi],
|
|
||||||
instances: &[EventInstance],
|
|
||||||
now: DateTime<chrono_tz::Tz>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let today = now.date_naive();
|
|
||||||
let mut last_month_printed: Option<String> = None;
|
|
||||||
let mut last_date_printed = None;
|
|
||||||
let mut html_list = vec![];
|
|
||||||
let mut day_list = vec![];
|
|
||||||
for ei in instances {
|
|
||||||
let date = ei.dtstart.date_naive();
|
|
||||||
let past = date < today;
|
|
||||||
let month = date.format("%B").to_string();
|
|
||||||
match last_month_printed {
|
|
||||||
Some(ref x) if *x == month => {}
|
|
||||||
None | Some(_) => {
|
|
||||||
// FIXME: De-dupe
|
|
||||||
if !day_list.is_empty() {
|
|
||||||
html_list.push(maud::html! {
|
|
||||||
ul { @for entry in day_list {
|
|
||||||
(entry)
|
|
||||||
} }
|
|
||||||
});
|
|
||||||
day_list = vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
html_list.push(maud::html! {
|
|
||||||
h2 { (month) }
|
|
||||||
});
|
|
||||||
last_month_printed = Some(month);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if last_date_printed != Some(date) {
|
|
||||||
// FIXME: De-dupe
|
|
||||||
if !day_list.is_empty() {
|
|
||||||
html_list.push(maud::html! {
|
|
||||||
ul { @for entry in day_list {
|
|
||||||
(entry)
|
|
||||||
} }
|
|
||||||
});
|
|
||||||
day_list = vec![];
|
|
||||||
}
|
|
||||||
if past {
|
|
||||||
html_list.push(maud::html! {
|
|
||||||
p class="past"{ s { (date.format("%-d %A")) } }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let date_s = date.format("%-d %A");
|
|
||||||
let id = date.format("%F");
|
|
||||||
html_list.push(maud::html! {
|
|
||||||
h3 id=(id) { (date_s) " " a href=(format!("#{id}")) title=(format!("Permalink to {date_s}")) {"🔗"} }
|
|
||||||
hr{}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
last_date_printed = Some(date);
|
|
||||||
}
|
|
||||||
let time = ei.dtstart.time();
|
|
||||||
let time = time
|
|
||||||
.map(|t| t.format("%l:%M %P").to_string())
|
|
||||||
.unwrap_or_else(|| "No time listed".to_string());
|
|
||||||
|
|
||||||
let summary = ei
|
|
||||||
.summary
|
|
||||||
.as_deref()
|
|
||||||
.unwrap_or("Data error BXH45NAR - No summary in event");
|
|
||||||
let summary = if let Some(url) = &ei.url {
|
|
||||||
maud::html! {a href=(url) {(summary)}}
|
|
||||||
} else {
|
|
||||||
maud::html! {(summary)}
|
|
||||||
};
|
|
||||||
|
|
||||||
if past {
|
|
||||||
day_list.push(maud::html! {
|
|
||||||
li class="past" { (time) " - " (summary) }
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// This is where the main stuff happens
|
|
||||||
|
|
||||||
tracing::debug!(uid = ei.uid, summary = ei.summary);
|
|
||||||
day_list.push(maud::html! {
|
|
||||||
li { details {
|
|
||||||
summary { (time) " - " (summary) }
|
|
||||||
ul {
|
|
||||||
li { (calendar_link(&ei.calendar_ui)) " calendar" }
|
|
||||||
@if let Some(location) = &ei.location {
|
|
||||||
li { "Location: " (location) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// FIXME: De-dupe
|
|
||||||
if !day_list.is_empty() {
|
|
||||||
html_list.push(maud::html! {
|
|
||||||
ul { @for entry in day_list {
|
|
||||||
(entry)
|
|
||||||
} }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fs::create_dir_all("output")?;
|
|
||||||
{
|
|
||||||
let temp_path = "output/calendars.html.tmp";
|
|
||||||
let final_path = "output/calendars.html";
|
|
||||||
let mut f = std::fs::File::create(temp_path)?;
|
|
||||||
|
|
||||||
let description = "A list of upstream calendars used by this Wide-Angle Calendar instance";
|
|
||||||
let title = "Upstream calendars";
|
|
||||||
|
|
||||||
let s = maud::html! {
|
|
||||||
(maud::PreEscaped("<!DOCTYPE html>"))
|
|
||||||
html lang="en" {
|
|
||||||
head {
|
|
||||||
meta http-equiv="Content-Type" content="text/html; charset=utf-8" {}
|
|
||||||
meta name="viewport" content="width=device-width, initial-scale=1" {}
|
|
||||||
(maud::PreEscaped(CSS))
|
|
||||||
|
|
||||||
meta property="og:locale" content="en" {}
|
|
||||||
meta property="og:type" content="website" {}
|
|
||||||
|
|
||||||
meta name="description" content=(description) {}
|
|
||||||
meta property="description" content=(description) {}
|
|
||||||
meta property="og:description" content=(description) {}
|
|
||||||
|
|
||||||
title { (title) }
|
|
||||||
met property="og:title" content=(title) {}
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
h1 { (title) }
|
|
||||||
p {
|
|
||||||
a href="index.html" { "Wide-Angle Calendar" }
|
|
||||||
" / "
|
|
||||||
a href="calendars.html" { (title) }
|
|
||||||
}
|
|
||||||
|
|
||||||
p { "Written at: " (now.format("%F %T")) }
|
|
||||||
p { "These are the calendars that Wide-Angle Calendar pulls from." }
|
|
||||||
|
|
||||||
ol {
|
|
||||||
@for upstream in upstreams {
|
|
||||||
li { (calendar_link(upstream)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.into_string();
|
|
||||||
|
|
||||||
f.write_all(s.as_bytes())?;
|
|
||||||
std::fs::rename(temp_path, final_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let temp_path = "output/index.html.tmp";
|
|
||||||
let final_path = "output/index.html";
|
|
||||||
let mut f = std::fs::File::create(temp_path)?;
|
|
||||||
|
|
||||||
let description = &config.description;
|
|
||||||
let title = &config.title;
|
|
||||||
|
|
||||||
let s = maud::html! {
|
|
||||||
(maud::PreEscaped("<!DOCTYPE html>"))
|
|
||||||
html lang="en" {
|
|
||||||
head {
|
|
||||||
meta http-equiv="Content-Type" content="text/html; charset=utf-8" {}
|
|
||||||
meta name="viewport" content="width=device-width, initial-scale=1" {}
|
|
||||||
(maud::PreEscaped(CSS))
|
|
||||||
|
|
||||||
meta property="og:locale" content="en" {}
|
|
||||||
meta property="og:type" content="website" {}
|
|
||||||
|
|
||||||
meta name="description" content=(description) {}
|
|
||||||
meta property="description" content=(description) {}
|
|
||||||
meta property="og:description" content=(description) {}
|
|
||||||
|
|
||||||
title { (title) }
|
|
||||||
met property="og:title" content=(title) {}
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
h1 { (title) }
|
|
||||||
img src="hero.webp" width="700" height="233" {}
|
|
||||||
p { "Written at: " (now.format("%F %T")) }
|
|
||||||
p { a href = "calendars.html" { "Upstream calendars" } }
|
|
||||||
@for entry in html_list {
|
|
||||||
(entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.into_string();
|
|
||||||
|
|
||||||
f.write_all(s.as_bytes())?;
|
|
||||||
std::fs::rename(temp_path, final_path)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -19,12 +19,11 @@ pub(crate) struct Config {
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Event {
|
struct Event {
|
||||||
#[serde(alias = "description")]
|
description: String,
|
||||||
_description: String,
|
|
||||||
#[serde(alias = "endDate")]
|
#[serde(alias = "endDate")]
|
||||||
_end_date: Option<String>,
|
end_date: Option<String>,
|
||||||
#[serde(alias = "endTime")]
|
#[serde(alias = "endTime")]
|
||||||
_end_time: Option<String>,
|
end_time: Option<String>,
|
||||||
#[serde(alias = "eventName")]
|
#[serde(alias = "eventName")]
|
||||||
event_name: String,
|
event_name: String,
|
||||||
location: String,
|
location: String,
|
||||||
|
|
|
@ -36,11 +36,10 @@ impl Config {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct Event {
|
struct Event {
|
||||||
#[serde(alias = "durationInMinutes")]
|
#[serde(alias = "durationInMinutes")]
|
||||||
_duration_in_minutes: u32,
|
duration_in_minutes: u32,
|
||||||
timestamp: i64,
|
timestamp: i64,
|
||||||
title: String,
|
title: String,
|
||||||
#[serde(alias = "description")]
|
description: String,
|
||||||
_description: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue