use regex::Regex;
use std::collections::HashMap;
use crate::platform::Platform;
use crate::release::PinnedRelease;
use crate::Error;
const RELEASES_URL: &str = "https://tableau.github.io/hyper-db/docs/releases";
pub fn scrape_latest(platform: Platform) -> Result<PinnedRelease, Error> {
tracing::info!(url = RELEASES_URL, "scraping latest release");
let client = reqwest::blocking::Client::builder()
.user_agent(concat!("hyperd-bootstrap/", env!("CARGO_PKG_VERSION")))
.build()
.map_err(Error::Http)?;
let resp = client.get(RELEASES_URL).send().map_err(Error::Http)?;
if !resp.status().is_success() {
return Err(Error::http_status(RELEASES_URL, resp.status().as_u16()));
}
let html = resp.text().map_err(Error::Http)?;
parse_latest(&html, platform)
}
fn parse_latest(html: &str, platform: Platform) -> Result<PinnedRelease, Error> {
let h3_re =
Regex::new(r"<h3[^>]*>\s*([0-9]+(?:\.[0-9]+){1,3})\s*\[[^\]]+\]").expect("valid regex");
let version = h3_re
.captures(html)
.and_then(|c| c.get(1))
.map(|m| m.as_str().to_string())
.ok_or(Error::ScrapeFailed(
"no <h3>VERSION [DATE]</h3> heading found",
))?;
let href_re = Regex::new(&format!(
r"tableauhyperapi-cxx-{plat}-release-main\.{ver}\.(rc[a-z0-9]+)\.zip",
plat = regex::escape(platform.slug()),
ver = regex::escape(&version),
))
.expect("valid regex");
let build_id = href_re
.captures(html)
.and_then(|c| c.get(1))
.map(|m| m.as_str().to_string())
.ok_or(Error::ScrapeFailed(
"no matching cxx zip href for scraped version",
))?;
Ok(PinnedRelease {
version,
build_id,
sha256: HashMap::new(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_real_page_snippet() {
let html = r#"
<h3>0.0.24457 [February 12 2026]</h3>
<ul><li>Release notes</li></ul>
<a href="https://downloads.tableau.com/tssoftware//tableauhyperapi-cxx-macos-arm64-release-main.0.0.24457.rc36858b6.zip">C++ (macOS arm64)</a>
<a href="https://downloads.tableau.com/tssoftware//tableauhyperapi-cxx-linux-x86_64-release-main.0.0.24457.rc36858b6.zip">C++ (Linux)</a>
<h3>0.0.20000 [January 1 2025]</h3>
"#;
let release = parse_latest(html, Platform::MacosArm64).unwrap();
assert_eq!(release.version, "0.0.24457");
assert_eq!(release.build_id, "rc36858b6");
}
#[test]
fn parse_errors_when_no_heading() {
let html = "<p>nothing here</p>";
assert!(matches!(
parse_latest(html, Platform::LinuxX86_64),
Err(Error::ScrapeFailed(_))
));
}
#[test]
fn parse_errors_when_no_matching_href() {
let html = r"<h3>0.0.24457 [Feb 12 2026]</h3>";
assert!(matches!(
parse_latest(html, Platform::WindowsX86_64),
Err(Error::ScrapeFailed(_))
));
}
}