use anyhow::Result;
use quote::quote;
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeMap, HashMap},
env, fs, io,
path::Path,
};
#[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(Clone, Deserialize, Serialize)]
struct VersionDetail {
version: String,
global_usage: f32,
release_date: Option<i64>,
}
#[derive(Deserialize)]
struct Feature {
stats: HashMap<String, HashMap<String, String>>,
}
fn main() -> Result<()> {
#[cfg(feature = "node")]
{
napi_build::setup();
}
generate_browser_names_cache()?;
build_electron_to_chromium()?;
build_node_versions()?;
build_node_release_schedule()?;
build_caniuse_global()?;
build_caniuse_region()?;
Ok(())
}
fn generate_browser_names_cache() -> Result<()> {
string_cache_codegen::AtomType::new(
"data::browser_name::BrowserNameAtom",
"browser_name_atom!",
)
.atoms(&[
"edge", "firefox", "chrome", "safari", "opera", "ios_saf", "op_mini", "android", "bb",
"op_mob", "and_chr", "and_ff", "ie_mob", "and_uc", "samsung", "and_qq", "baidu", "kaios",
])
.write_to_file(&Path::new(&env::var("OUT_DIR")?).join("browser_name_atom.rs"))?;
Ok(())
}
fn build_electron_to_chromium() -> Result<()> {
println!("cargo:rerun-if-changed=vendor/electron-to-chromium/versions.json");
let path = format!("{}/electron-to-chromium.json", env::var("OUT_DIR")?);
let mut data = serde_json::from_slice::<BTreeMap<String, String>>(&fs::read(format!(
"{}/vendor/electron-to-chromium/versions.json",
env::var("CARGO_MANIFEST_DIR")?
))?)?
.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 build_node_versions() -> Result<()> {
#[derive(Deserialize)]
struct NodeRelease {
version: String,
}
println!("cargo:rerun-if-changed=vendor/node-releases/data/processed/envs.json");
let path = format!("{}/node-versions.json", env::var("OUT_DIR")?);
let releases: Vec<NodeRelease> = serde_json::from_slice(&fs::read(format!(
"{}/vendor/node-releases/data/processed/envs.json",
env::var("CARGO_MANIFEST_DIR")?
))?)?;
fs::write(
path,
&serde_json::to_string(
&releases
.into_iter()
.map(|release| release.version)
.collect::<Vec<_>>(),
)?,
)?;
Ok(())
}
fn build_node_release_schedule() -> Result<()> {
println!(
"cargo:rerun-if-changed=vendor/node-releases/data/release-schedule/release-schedule.json"
);
#[derive(Deserialize)]
struct NodeRelease {
start: String,
end: String,
}
let path = format!("{}/node-release-schedule.json", env::var("OUT_DIR")?);
let schedule: HashMap<String, NodeRelease> = serde_json::from_slice(&fs::read(format!(
"{}/vendor/node-releases/data/release-schedule/release-schedule.json",
env::var("CARGO_MANIFEST_DIR")?
))?)?;
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 build_caniuse_global() -> Result<()> {
#[derive(Serialize)]
struct BrowserStat {
name: String,
version_list: Vec<VersionDetail>,
}
let out_dir = env::var("OUT_DIR")?;
let data = parse_caniuse_global()?;
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-global-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 features = data.data.keys().collect::<Vec<_>>();
let tokens = quote! {{
use once_cell::sync::Lazy;
use serde_json::from_str;
use crate::data::browser_name::BrowserNameAtom;
match name {
#( #features => {
static STAT: Lazy<Vec<(BrowserNameAtom, &'static str)>> = Lazy::new(|| {
from_str(include_str!(concat!(env!("OUT_DIR"), "/features/", #features, ".json"))).unwrap()
});
Some(&*STAT)
}, )*
_ => None,
}
}};
fs::write(
format!("{}/caniuse-feature-matching.rs", &out_dir),
tokens.to_string(),
)?;
Ok(())
}
fn parse_caniuse_global() -> Result<Caniuse> {
println!("cargo:rerun-if-changed=vendor/caniuse/fulldata-json/data-2.0.json");
Ok(serde_json::from_slice(&fs::read(format!(
"{}/vendor/caniuse/fulldata-json/data-2.0.json",
env::var("CARGO_MANIFEST_DIR")?
))?)?)
}
fn build_caniuse_region() -> Result<()> {
#[derive(Deserialize)]
struct RegionData {
data: HashMap<String, HashMap<String, Option<f32>>>,
}
let files = fs::read_dir(format!(
"{}/vendor/caniuse/region-usage-json",
env::var("CARGO_MANIFEST_DIR")?
))?
.map(|entry| entry.map_err(anyhow::Error::from))
.collect::<Result<Vec<_>>>()?;
files.iter().for_each(|entry| {
println!(
"cargo:rerun-if-changed=vendor/caniuse/region-usage-json/{}",
entry.file_name().into_string().unwrap()
)
});
let out_dir = env::var("OUT_DIR")?;
let Caniuse { agents, .. } = parse_caniuse_global()?;
let region_dir = format!("{}/region", &out_dir);
if matches!(fs::File::open(®ion_dir), Err(e) if e.kind() == io::ErrorKind::NotFound) {
fs::create_dir(®ion_dir)?;
}
for file in &files {
let RegionData { data } = serde_json::from_slice(&fs::read(file.path())?)?;
let mut usage = data
.into_iter()
.map(|(name, stat)| {
dbg!(&name);
let agent = agents.get(&name).unwrap();
stat.into_iter().filter_map(move |(version, usage)| {
let version = if version.as_str() == "0" {
agent.version_list.last().unwrap().version.clone()
} else {
version
};
usage.map(|usage| (name.clone(), version, usage))
})
})
.flatten()
.collect::<Vec<_>>();
usage.sort_unstable_by(|(_, _, a), (_, _, b)| b.partial_cmp(a).unwrap());
fs::write(
format!("{}/region/{}", &out_dir, file.file_name().to_str().unwrap()),
serde_json::to_string(&usage)?,
)?;
}
let regions = files
.iter()
.map(|entry| {
entry
.path()
.file_stem()
.unwrap()
.to_str()
.map(|s| s.to_owned())
.unwrap()
})
.collect::<Vec<_>>();
let tokens = quote! {{
use once_cell::sync::Lazy;
use serde_json::from_str;
use crate::data::browser_name::BrowserNameAtom;
match region {
#( #regions => {
static USAGE: Lazy<Vec<(BrowserNameAtom, &'static str, f32)>> = Lazy::new(|| {
from_str(include_str!(concat!(env!("OUT_DIR"), "/region/", #regions, ".json"))).unwrap()
});
Some(&*USAGE)
}, )*
_ => None,
}
}};
fs::write(
format!("{}/caniuse-region-matching.rs", &out_dir),
tokens.to_string(),
)?;
Ok(())
}