use anyhow::Result;
use curl::easy::Easy;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{
collections::{BTreeMap, HashMap},
env, fs, io,
};
const E2C: &str = "1.4.4";
const NODE: &str = "2.0.1";
const CANIUSE: &str = "1.0.30001283";
fn fetch_json<T: DeserializeOwned, S: AsRef<str>>(url: S) -> Result<T> {
let mut dst = Vec::new();
let mut easy = Easy::new();
easy.url(url.as_ref())?;
{
let mut transfer = easy.transfer();
transfer.write_function(|data| {
dst.extend_from_slice(data);
Ok(data.len())
})?;
transfer.perform()?;
}
serde_json::from_slice(&dst).map_err(anyhow::Error::from)
}
fn main() -> Result<()> {
#[cfg(feature = "node")]
{
napi_build::setup();
}
println!("cargo:rerun-if-changed=build.rs");
fetch_electron_to_chromium()?;
fetch_node_versions()?;
fetch_node_release_schedule()?;
fetch_caniuse_global()?;
Ok(())
}
fn fetch_electron_to_chromium() -> Result<()> {
let path = format!("{}/electron-to-chromium.json", env::var("OUT_DIR")?);
if env::var("DOCS_RS").is_ok() {
fs::write(path, "[]")?;
return Ok(());
}
let mut data = fetch_json::<BTreeMap<String, String>, _>(format!(
"https://cdn.jsdelivr.net/npm/electron-to-chromium@{}/versions.json",
E2C
))?
.into_iter()
.map(|(electron_version, chromium_version)| {
(electron_version.parse::<f32>().unwrap(), chromium_version)
})
.collect::<Vec<_>>();
data.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap());
fs::write(path, &serde_json::to_string(&data)?)?;
Ok(())
}
fn fetch_node_versions() -> Result<()> {
#[derive(Deserialize)]
struct NodeRelease {
version: String,
}
let path = format!("{}/node-versions.json", env::var("OUT_DIR")?);
if env::var("DOCS_RS").is_ok() {
fs::write(path, "[]")?;
return Ok(());
}
let releases: Vec<NodeRelease> = fetch_json(format!(
"https://cdn.jsdelivr.net/npm/node-releases@{}/data/processed/envs.json",
NODE
))?;
fs::write(
path,
&serde_json::to_string(
&releases
.into_iter()
.map(|release| release.version)
.collect::<Vec<_>>(),
)?,
)?;
Ok(())
}
fn fetch_node_release_schedule() -> Result<()> {
#[derive(Deserialize)]
struct NodeRelease {
start: String,
end: String,
}
let path = format!("{}/node-release-schedule.json", env::var("OUT_DIR")?);
if env::var("DOCS_RS").is_ok() {
fs::write(path, "{}")?;
return Ok(());
}
let schedule: HashMap<String, NodeRelease> = fetch_json(format!(
"https://cdn.jsdelivr.net/npm/node-releases@{}/data/release-schedule/release-schedule.json",
NODE
))?;
fs::write(
path,
&serde_json::to_string(
&schedule
.into_iter()
.map(|(version, release)| {
(
version.trim_start_matches('v').to_owned(),
(release.start, release.end),
)
})
.collect::<HashMap<_, _>>(),
)?,
)?;
Ok(())
}
fn fetch_caniuse_global() -> Result<()> {
use itertools::Itertools;
#[derive(Deserialize)]
struct Caniuse {
agents: HashMap<String, Agent>,
data: BTreeMap<String, Feature>,
}
#[derive(Deserialize)]
struct Agent {
usage_global: HashMap<String, f32>,
version_list: Vec<VersionDetail>,
}
#[derive(Serialize)]
struct BrowserStat {
name: String,
version_list: Vec<VersionDetail>,
}
#[derive(Clone, Deserialize, Serialize)]
struct VersionDetail {
version: String,
global_usage: f32,
release_date: Option<i64>,
}
#[derive(Deserialize)]
struct Feature {
stats: HashMap<String, HashMap<String, String>>,
}
let out_dir = env::var("OUT_DIR")?;
if env::var("DOCS_RS").is_ok() {
fs::write(format!("{}/caniuse-browsers.json", &out_dir), "{}")?;
fs::write(format!("{}/caniuse-usage.json", &out_dir), "[]")?;
fs::write(
format!("{}/caniuse-feature-matching.rs", &out_dir),
"match name { _ => unreachable!() }",
)?;
fs::write(format!("{}/caniuse-features-list.json", &out_dir), "[]")?;
return Ok(());
}
let data: Caniuse = fetch_json(format!(
"https://cdn.jsdelivr.net/npm/caniuse-db@{}/fulldata-json/data-2.0.json",
CANIUSE
))?;
let browsers = data
.agents
.iter()
.map(|(name, agent)| {
(
name,
BrowserStat {
name: name.to_string(),
version_list: agent.version_list.clone(),
},
)
})
.collect::<HashMap<_, _>>();
fs::write(
format!("{}/caniuse-browsers.json", &out_dir),
&serde_json::to_string(&browsers)?,
)?;
let mut global_usage = data
.agents
.iter()
.map(|(name, agent)| {
agent
.usage_global
.iter()
.map(|(version, usage)| (name.clone(), version.clone(), usage))
})
.flatten()
.collect::<Vec<_>>();
global_usage.sort_unstable_by(|(_, _, a), (_, _, b)| b.partial_cmp(a).unwrap());
fs::write(
format!("{}/caniuse-usage.json", &out_dir),
&serde_json::to_string(&global_usage)?,
)?;
let features_dir = format!("{}/features", &out_dir);
if matches!(fs::File::open(&features_dir), Err(e) if e.kind() == io::ErrorKind::NotFound) {
fs::create_dir(&features_dir)?;
}
for (name, feature) in &data.data {
fs::write(
format!("{}/{}.json", &features_dir, name),
serde_json::to_string(
&feature
.stats
.iter()
.map(|(name, versions)| {
versions
.iter()
.filter(|(_, stat)| stat.starts_with('y') || stat.starts_with('a'))
.map(|(version, _)| (name.clone(), version.clone()))
})
.flatten()
.collect::<Vec<_>>(),
)?,
)?;
}
let arms = data
.data
.keys()
.map(|name| {
format!(
r#" "{0}" => include_str!(concat!(env!("OUT_DIR"), "/features/{0}.json")),"#,
name
)
})
.join("\n");
let caniuse_features_matching =
format!("match name {{\n{}\n _ => unreachable!()\n}}", &arms);
fs::write(
format!("{}/caniuse-feature-matching.rs", &out_dir),
caniuse_features_matching,
)?;
fs::write(
format!("{}/caniuse-features-list.json", &out_dir),
serde_json::to_string(&data.data.keys().collect::<Vec<_>>())?,
)?;
Ok(())
}