an http based roguelike rpg about jelqing
| 1 | use std::fmt::Write as _; |
| 2 | |
| 3 | use super::{run::RunResource, runs}; |
| 4 | use crate::game::{Branch, Run, SHOP}; |
| 5 | |
| 6 | pub const REL_BASE: &str = "https://jelq.dev/rels/"; |
| 7 | |
| 8 | /// One RFC 8288 link. `allow` and `lubricant` are extension attributes (§3), so a client never |
| 9 | /// has to guess what a target accepts. |
| 10 | struct Link<'a> { |
| 11 | href: &'a str, |
| 12 | rel: &'a str, |
| 13 | allow: &'a str, |
| 14 | title: Option<&'a str>, |
| 15 | lubricant: Option<&'a str>, |
| 16 | } |
| 17 | |
| 18 | impl Link<'_> { |
| 19 | fn write(&self, out: &mut String) { |
| 20 | if !out.is_empty() { |
| 21 | out.push_str(", "); |
| 22 | } |
| 23 | let _ = write!(out, "<{}>; rel=\"{REL_BASE}{}\"", self.href, self.rel); |
| 24 | if let Some(title) = self.title { |
| 25 | let _ = write!(out, "; title=\"{title}\""); |
| 26 | } |
| 27 | let _ = write!(out, "; allow=\"{}\"", self.allow); |
| 28 | if let Some(rule) = self.lubricant { |
| 29 | let _ = write!(out, "; lubricant=\"{rule}\""); |
| 30 | } |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | pub fn header(run: &Run) -> String { |
| 35 | let reach = run.reach(); |
| 36 | let mut out = String::new(); |
| 37 | let resources: [(&str, &RunResource, bool); 15] = [ |
| 38 | ("", &runs::RUN, true), |
| 39 | ("floor", &runs::FLOOR, true), |
| 40 | ("length", &runs::LENGTH, true), |
| 41 | ("foreskin", &runs::FORESKIN, reach.foreskin), |
| 42 | ("inventory", &runs::INVENTORY, true), |
| 43 | ("techniques", &runs::TECHNIQUES, true), |
| 44 | ("drawer", &runs::DRAWER, reach.drawer), |
| 45 | ("kitchen", &runs::KITCHEN, reach.kitchen), |
| 46 | ("thread", &runs::THREAD, reach.thread), |
| 47 | ("grip", &runs::GRIP, true), |
| 48 | ("shop", &runs::SHOP, reach.shop), |
| 49 | ("portal", &runs::PORTAL, reach.portal), |
| 50 | ("elder", &runs::ELDER, reach.elder), |
| 51 | ("footage", &runs::FOOTAGE, reach.footage), |
| 52 | ("diagnosis", &runs::DIAGNOSIS, reach.diagnosis), |
| 53 | ]; |
| 54 | for (rel, target, _) in resources.iter().filter(|(_, _, relevant)| *relevant) { |
| 55 | let href = if rel.is_empty() { |
| 56 | run.path("") |
| 57 | } else { |
| 58 | run.path(&format!("/{rel}")) |
| 59 | }; |
| 60 | Link { |
| 61 | href: &href, |
| 62 | rel: if rel.is_empty() { "run" } else { rel }, |
| 63 | allow: &target.resource.allow(run), |
| 64 | title: None, |
| 65 | lubricant: None, |
| 66 | } |
| 67 | .write(&mut out); |
| 68 | } |
| 69 | if reach.branch_pending { |
| 70 | for (branch, title) in [ |
| 71 | (Branch::Forum, "Read the forum"), |
| 72 | (Branch::CloseTab, "Close the tab"), |
| 73 | ] { |
| 74 | Link { |
| 75 | href: &run.path(&format!("/branches/{}", branch.slug())), |
| 76 | rel: "branch", |
| 77 | allow: "POST", |
| 78 | title: Some(title), |
| 79 | lubricant: None, |
| 80 | } |
| 81 | .write(&mut out); |
| 82 | } |
| 83 | } |
| 84 | let run_path = run.path(""); |
| 85 | for technique in run.techniques_now() { |
| 86 | let name = technique.name(); |
| 87 | Link { |
| 88 | href: &format!("{run_path}?technique={name}"), |
| 89 | rel: "technique", |
| 90 | allow: "JELQ", |
| 91 | title: Some(name), |
| 92 | lubricant: technique.lubricant(), |
| 93 | } |
| 94 | .write(&mut out); |
| 95 | } |
| 96 | if reach.shopping { |
| 97 | for item in SHOP { |
| 98 | let Some(price) = run.price(item) else { |
| 99 | continue; |
| 100 | }; |
| 101 | Link { |
| 102 | href: &run.path(&format!("/shop/{}", item.slug())), |
| 103 | rel: "shop-item", |
| 104 | allow: "POST", |
| 105 | title: Some(&format!("{} for {price}", item.slug())), |
| 106 | lubricant: None, |
| 107 | } |
| 108 | .write(&mut out); |
| 109 | } |
| 110 | } |
| 111 | out |
| 112 | } |