1//! The code host's front page: every repository the Worker serves, each with 2//! the command that clones it and a button that copies it. Pure: the 3//! listings in, markup out. Which repositories there are is not decided 4//! here; `clone::Repo::ALL` is, and the Worker hands that over. 5 6use maud::{DOCTYPE, Markup, PreEscaped, html}; 7 8use super::code::{Meta, Project, preview}; 9use super::{JS, clone_line_as, head, icon, icons, nav}; 10use crate::host::Host; 11 12/// One repository, as the page lists it. 13pub struct Listing { 14 pub project: Project, 15 /// What to run to clone it, from the code host's address. 16 pub command: String, 17 /// What GitHub says about it; `None` if GitHub could not be read. 18 pub meta: Option<Meta>, 19} 20 21/// What the copy button of a repository copies: `clone-lmjtfy.git`. 22fn copied(project: &Project) -> String { 23 format!("clone-{}", project.served) 24} 25 26/// `GET /` at the code host. `origin` is where the page is, for its picture. 27pub fn page(origin: &str, listings: &[Listing]) -> Markup { 28 let about = "Every repository served here, and the command to clone it. No GitHub account needed."; 29 html! { 30 (DOCTYPE) 31 html lang="en" { 32 (head(preview(origin, "lmjtfy.git", "LMJTFY · the code", about, "main"))) 33 body { 34 div .sheet { 35 div .wrap { 36 (nav(Host::Code)) 37 div .headline { 38 p .eyebrow { "git clone ............. code.lmjtfy.fun" } 39 h1 { "The Code" } 40 } 41 div .stack { 42 @for listing in listings { 43 (card(listing)) 44 } 45 p .meta { 46 "It can be cloned and pulled, and nothing else. Open a repository to read it. " 47 "The site itself is at " a href=(Host::Code.home_link()) { "lmjtfy.fun" } "." 48 } 49 } 50 } 51 } 52 script { (PreEscaped(JS)) } 53 } 54 } 55 } 56} 57 58fn card(listing: &Listing) -> Markup { 59 let project = &listing.project; 60 html! { 61 div .win { 62 div .bar-title { 63 (icon(&icons::JEV, "")) 64 a .dark href={ "/" (project.served) } { (project.served) } 65 span .meta { (project.branch) } 66 } 67 div .body { 68 @if let Some(meta) = &listing.meta { 69 @if !meta.about().is_empty() { p { (meta.about()) } } 70 (meta.details()) 71 } 72 (clone_line_as(&copied(project), &listing.command, html! { a href={ "/" (project.served) } { "Browse:" } })) 73 } 74 } 75 } 76} 77 78#[cfg(test)] 79mod tests { 80 use super::*; 81 use crate::clone::Repo; 82 83 fn listings(origin: &str) -> Vec<Listing> { 84 Repo::ALL.iter().map(|repo| Listing { project: repo.project(), command: repo.command(origin), meta: None }).collect() 85 } 86 87 /// The page is what the Worker serves, and nothing else: every repository 88 /// in `Repo::ALL` is on it, once, with its own command and its own copy 89 /// button, and a repository is on it only if it can be cloned. 90 #[test] 91 fn the_page_lists_every_served_repository_and_only_those() { 92 let html = page("https://code.x.test", &listings("https://code.x.test")).into_string(); 93 for repo in Repo::ALL { 94 let served = repo.served(); 95 let command = format!("<code id=\"clone-{served}\">{}</code>", repo.command("https://code.x.test")); 96 assert_eq!(html.matches(&command).count(), 1, "{served}: {html}"); 97 assert!(html.contains(&format!("data-copy=\"clone-{served}\"")), "{served}"); 98 assert!(html.contains(&format!("href=\"/{served}\"")), "{served}"); 99 assert_eq!(Repo::named(served), Some(repo)); 100 } 101 // Every command on the page is one of them. 102 assert_eq!(html.matches("<code id=\"clone-").count(), Repo::ALL.len(), "{html}"); 103 assert_eq!(html.matches("data-copy=").count(), Repo::ALL.len()); 104 // A name that is not served is not offered. 105 assert!(!html.contains("nosuchrepo"), "{html}"); 106 } 107 108 #[test] 109 fn a_card_shows_what_github_says_and_nothing_when_it_says_nothing() { 110 let mut all = listings("https://code.x.test"); 111 all[0].meta = Meta::parse(r#"{"description":"Says <hi>.","homepage":"https://w.test","topics":["rust"]}"#); 112 let html = page("https://code.x.test", &all).into_string(); 113 assert!(html.contains("<p>Says <hi>.</p>"), "{html}"); 114 assert!(html.contains(r#"<span class="topic">rust</span>"#), "{html}"); 115 assert!(html.contains(r#"href="https://w.test""#), "{html}"); 116 // The other cards had no answer: no paragraph, no chips. 117 assert_eq!(html.matches("class=\"topic\"").count(), 1); 118 assert_eq!(html.matches("<p>").count(), 1, "{html}"); 119 } 120 121 #[test] 122 fn a_repository_with_a_submodule_is_cloned_with_it() { 123 let html = page("https://code.x.test", &listings("https://code.x.test")).into_string(); 124 assert!(html.contains("git clone --recurse-submodules https://code.x.test/lmjtfy.git"), "{html}"); 125 assert!(html.contains("git clone https://code.x.test/jevcrates.git<"), "{html}"); 126 } 127 128 #[test] 129 fn the_page_does_not_reach_for_the_live_routes() { 130 let html = page("https://code.x.test", &[]).into_string(); 131 // No place for page.js to open a socket into, and the title leads 132 // home to the other address. 133 assert!(!html.contains("id=\"online\""), "{html}"); 134 assert!(!html.contains("id=\"toasts\""), "{html}"); 135 assert!(html.contains("<a href=\"https://lmjtfy.fun/\">LMJTFY</a>"), "{html}"); 136 } 137}