hyperdb_bootstrap/
scrape.rs1use regex::Regex;
14use std::collections::HashMap;
15
16use crate::platform::Platform;
17use crate::release::PinnedRelease;
18use crate::Error;
19
20const RELEASES_URL: &str = "https://tableau.github.io/hyper-db/docs/releases";
21
22pub fn scrape_latest(platform: Platform) -> Result<PinnedRelease, Error> {
35 tracing::info!(url = RELEASES_URL, "scraping latest release");
36 let client = reqwest::blocking::Client::builder()
37 .user_agent(concat!("hyperd-bootstrap/", env!("CARGO_PKG_VERSION")))
38 .build()
39 .map_err(Error::Http)?;
40 let resp = client.get(RELEASES_URL).send().map_err(Error::Http)?;
41 if !resp.status().is_success() {
42 return Err(Error::HttpStatus {
43 url: RELEASES_URL.to_string(),
44 status: resp.status().as_u16(),
45 });
46 }
47 let html = resp.text().map_err(Error::Http)?;
48 parse_latest(&html, platform)
49}
50
51fn parse_latest(html: &str, platform: Platform) -> Result<PinnedRelease, Error> {
52 let h3_re =
55 Regex::new(r"<h3[^>]*>\s*([0-9]+(?:\.[0-9]+){1,3})\s*\[[^\]]+\]").expect("valid regex");
56 let version = h3_re
57 .captures(html)
58 .and_then(|c| c.get(1))
59 .map(|m| m.as_str().to_string())
60 .ok_or(Error::ScrapeFailed(
61 "no <h3>VERSION [DATE]</h3> heading found",
62 ))?;
63
64 let href_re = Regex::new(&format!(
67 r"tableauhyperapi-cxx-{plat}-release-main\.{ver}\.(rc[a-z0-9]+)\.zip",
68 plat = regex::escape(platform.slug()),
69 ver = regex::escape(&version),
70 ))
71 .expect("valid regex");
72 let build_id = href_re
73 .captures(html)
74 .and_then(|c| c.get(1))
75 .map(|m| m.as_str().to_string())
76 .ok_or(Error::ScrapeFailed(
77 "no matching cxx zip href for scraped version",
78 ))?;
79
80 Ok(PinnedRelease {
81 version,
82 build_id,
83 sha256: HashMap::new(),
84 })
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn parse_real_page_snippet() {
93 let html = r#"
94 <h3>0.0.24457 [February 12 2026]</h3>
95 <ul><li>Release notes</li></ul>
96 <a href="https://downloads.tableau.com/tssoftware//tableauhyperapi-cxx-macos-arm64-release-main.0.0.24457.rc36858b6.zip">C++ (macOS arm64)</a>
97 <a href="https://downloads.tableau.com/tssoftware//tableauhyperapi-cxx-linux-x86_64-release-main.0.0.24457.rc36858b6.zip">C++ (Linux)</a>
98 <h3>0.0.20000 [January 1 2025]</h3>
99 "#;
100 let release = parse_latest(html, Platform::MacosArm64).unwrap();
101 assert_eq!(release.version, "0.0.24457");
102 assert_eq!(release.build_id, "rc36858b6");
103 }
104
105 #[test]
106 fn parse_errors_when_no_heading() {
107 let html = "<p>nothing here</p>";
108 assert!(matches!(
109 parse_latest(html, Platform::LinuxX86_64),
110 Err(Error::ScrapeFailed(_))
111 ));
112 }
113
114 #[test]
115 fn parse_errors_when_no_matching_href() {
116 let html = r"<h3>0.0.24457 [Feb 12 2026]</h3>";
117 assert!(matches!(
118 parse_latest(html, Platform::WindowsX86_64),
119 Err(Error::ScrapeFailed(_))
120 ));
121 }
122}