diff --git a/Cargo.lock b/Cargo.lock index 19fd449..2735f4b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,16 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atomic-write-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb1e2c1d58618bea806ccca5bbe65dc4e868be16f69ff118a39049389687548" +dependencies = [ + "nix", + "rand", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -185,6 +195,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.41" @@ -972,6 +988,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nom" version = "8.0.0" @@ -1131,6 +1159,15 @@ dependencies = [ "zerovec", ] +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.96" @@ -1177,6 +1214,36 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + [[package]] name = "regex" version = "1.11.1" @@ -1990,6 +2057,7 @@ name = "wide-angle-calendar" version = "0.1.0" dependencies = [ "anyhow", + "atomic-write-file", "base64", "camino", "chrono", @@ -2302,6 +2370,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index 9b506eb..cce04d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] anyhow = "1.0.98" +atomic-write-file = "0.2.3" base64 = "0.22.1" camino = { version = "1.1.11", features = ["serde1"] } chrono = "0.4.41" diff --git a/src/output.rs b/src/output.rs index 250cb08..90f4219 100644 --- a/src/output.rs +++ b/src/output.rs @@ -66,12 +66,55 @@ fn calendar_link(calendar_ui: &crate::CalendarUi) -> maud::PreEscaped { } } -pub(crate) fn write_html( +fn calendars_page(upstreams: &[crate::CalendarUi], now: DateTime) -> String { + let description = "A list of upstream calendars used by this Wide-Angle Calendar instance"; + let title = "Upstream calendars"; + + maud::html! { + (maud::PreEscaped("")) + 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() +} + +fn index_page( config: &Config, - upstreams: &[crate::CalendarUi], instances: &[EventInstance], now: DateTime, -) -> Result<()> { +) -> Result { let today = now.date_naive(); let mut last_month_printed: Option = None; let mut last_date_printed = None; @@ -170,100 +213,59 @@ pub(crate) fn write_html( }); } + let description = &config.description; + let title = &config.title; + + let s = maud::html! { + (maud::PreEscaped("")) + 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(); + Ok(s) +} + +fn atomic_write(path: &str, content: &str) -> Result<()> { + let mut file = atomic_write_file::AtomicWriteFile::options().open(path)?; + file.write_all(content.as_bytes())?; + file.commit()?; + Ok(()) +} + +pub(crate) fn write_html( + config: &Config, + upstreams: &[crate::CalendarUi], + instances: &[EventInstance], + now: DateTime, +) -> Result<()> { 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("")) - 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("")) - 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)?; - } + atomic_write("output/calendars.html", &calendars_page(upstreams, now))?; + atomic_write("output/index.html", &index_page(config, instances, now)?)?; Ok(()) }