use core::panic;
use std::{env, fs, path::PathBuf};
#[derive(Debug, serde::Deserialize)]
struct RawCountry {
iso_code: Option<String>,
alpha3: Option<String>,
continent: Option<String>,
country_code: Option<String>,
currency_code: Option<String>,
distance_unit: Option<String>,
gec: Option<String>,
geo: Option<RawGeo>,
international_prefix: Option<String>,
ioc: Option<String>,
iso_long_name: Option<String>,
iso_short_name: Option<String>,
languages_official: Option<Vec<String>>,
languages_spoken: Option<Vec<String>>,
national_destination_code_lengths: Option<Vec<u8>>,
national_number_lengths: Option<Vec<u8>>,
national_prefix: Option<String>,
nationality: Option<String>,
number: Option<String>,
postal_code: Option<bool>,
postal_code_format: Option<String>,
region: Option<String>,
start_of_week: Option<String>,
subregion: Option<String>,
un_locode: Option<String>,
unofficial_names: Option<Vec<String>>,
world_region: Option<String>,
}
#[derive(Debug, serde::Deserialize)]
struct RawGeo {
latitude: f64,
longitude: f64,
max_latitude: f64,
max_longitude: f64,
min_latitude: f64,
min_longitude: f64,
bounds: RawBounds,
}
#[derive(Debug, serde::Deserialize)]
struct RawBounds {
northeast: RawLatLng,
southwest: RawLatLng,
}
#[derive(Debug, serde::Deserialize)]
struct RawLatLng {
lat: f64,
lng: f64,
}
fn feature(name: &str) -> bool {
env::var(format!("CARGO_FEATURE_{}", name.to_ascii_uppercase())).is_ok()
}
fn escape(s: &str) -> String {
let j = serde_json::to_string(s).unwrap();
j[1..j.len() - 1].to_string()
}
fn fmt_f64(v: f64) -> String {
let s = v.to_string();
if s.contains('.') || s.contains('e') || s.contains('E') {
s
} else {
format!("{}.0", s)
}
}
fn continent_code(continent: &str) -> &str {
match continent {
"Africa" => "AF",
"Antarctica" => "AN",
"Asia" => "AS",
"Europe" => "EU",
"North America" => "NA",
"Oceania" => "OC",
"South America" => "SA",
_ => panic!("Unknown continent: {}", continent),
}
}
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let json_path = PathBuf::from("data/countries.json");
println!("cargo:rerun-if-changed={}", json_path.display());
let data = fs::read_to_string(&json_path).expect("Failed to read data/countries.json");
let raw: Vec<RawCountry> = serde_json::from_str(&data).expect("Failed to parse countries.json");
let mut out = String::new();
out.push_str("// @generated by build.rs. Do not edit.\n");
out.push_str("use crate::definitions::{Country");
if feature("geo") {
out.push_str(", Geo, Bounds, LatLng");
}
out.push_str("};\n\n");
out.push_str("pub static COUNTRIES: &[Country] = &[\n");
for c in &raw {
out.push_str(" Country {\n");
if feature("iso_code")
&& let Some(v) = &c.iso_code
{
out.push_str(&format!(" iso_code: \"{}\",\n", escape(v)));
}
if feature("alpha3")
&& let Some(v) = &c.alpha3
{
out.push_str(&format!(" alpha3: \"{}\",\n", escape(v)));
}
if feature("continent")
&& let Some(v) = &c.continent
{
out.push_str(&format!(" continent: \"{}\",\n", escape(v)));
out.push_str(&format!(
" continent_code: \"{}\",\n",
escape(continent_code(v))
));
}
if feature("country_code")
&& let Some(v) = &c.country_code
{
out.push_str(&format!(" country_code: \"{}\",\n", escape(v)));
}
if feature("currency_code")
&& let Some(v) = &c.currency_code
{
out.push_str(&format!(" currency_code: \"{}\",\n", escape(v)));
}
if feature("distance_unit")
&& let Some(v) = &c.distance_unit
{
out.push_str(&format!(" distance_unit: \"{}\",\n", escape(v)));
}
if feature("gec")
&& let Some(v) = &c.gec
{
out.push_str(&format!(" gec: \"{}\",\n", escape(v)));
}
if feature("geo")
&& let Some(g) = &c.geo
{
out.push_str(" geo: Geo {\n");
out.push_str(&format!(" latitude: {},\n", fmt_f64(g.latitude)));
out.push_str(&format!(
" longitude: {},\n",
fmt_f64(g.longitude)
));
out.push_str(&format!(
" max_latitude: {},\n",
fmt_f64(g.max_latitude)
));
out.push_str(&format!(
" max_longitude: {},\n",
fmt_f64(g.max_longitude)
));
out.push_str(&format!(
" min_latitude: {},\n",
fmt_f64(g.min_latitude)
));
out.push_str(&format!(
" min_longitude: {},\n",
fmt_f64(g.min_longitude)
));
out.push_str(" bounds: Bounds {\n");
out.push_str(&format!(
" northeast: LatLng {{ lat: {}, lng: {} }},\n",
fmt_f64(g.bounds.northeast.lat),
fmt_f64(g.bounds.northeast.lng)
));
out.push_str(&format!(
" southwest: LatLng {{ lat: {}, lng: {} }},\n",
fmt_f64(g.bounds.southwest.lat),
fmt_f64(g.bounds.southwest.lng)
));
out.push_str(" },\n");
out.push_str(" },\n");
}
if feature("international_prefix")
&& let Some(v) = &c.international_prefix
{
out.push_str(&format!(
" international_prefix: \"{}\",\n",
escape(v)
));
}
if feature("ioc")
&& let Some(v) = &c.ioc
{
out.push_str(&format!(" ioc: \"{}\",\n", escape(v)));
}
if feature("iso_long_name")
&& let Some(v) = &c.iso_long_name
{
out.push_str(&format!(" iso_long_name: \"{}\",\n", escape(v)));
}
if feature("iso_short_name")
&& let Some(v) = &c.iso_short_name
{
out.push_str(&format!(" iso_short_name: \"{}\",\n", escape(v)));
}
if feature("languages_official") {
let list = c
.languages_official
.as_ref()
.map(|v| {
v.iter()
.map(|s| format!("\"{}\"", escape(s)))
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_default();
out.push_str(&format!(" languages_official: &[{}],\n", list));
}
if feature("languages_spoken") {
let list = c
.languages_spoken
.as_ref()
.map(|v| {
v.iter()
.map(|s| format!("\"{}\"", escape(s)))
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_default();
out.push_str(&format!(" languages_spoken: &[{}],\n", list));
}
if feature("national_destination_code_lengths")
&& let Some(v) = &c.national_destination_code_lengths
{
let list = v
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
out.push_str(&format!(
" national_destination_code_lengths: &[{}],\n",
list
));
}
if feature("national_number_lengths")
&& let Some(v) = &c.national_number_lengths
{
let list = v
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
out.push_str(&format!(" national_number_lengths: &[{}],\n", list));
}
if feature("national_prefix") {
match &c.national_prefix {
Some(v) => out.push_str(&format!(
" national_prefix: Some(\"{}\"),\n",
escape(v)
)),
None => out.push_str(" national_prefix: None,\n"),
}
}
if feature("nationality")
&& let Some(v) = &c.nationality
{
out.push_str(&format!(" nationality: \"{}\",\n", escape(v)));
}
if feature("number")
&& let Some(v) = &c.number
{
out.push_str(&format!(" number: \"{}\",\n", escape(v)));
}
if feature("postal_code")
&& let Some(v) = c.postal_code
{
out.push_str(&format!(" postal_code: {},\n", v));
}
if feature("postal_code_format")
&& let Some(v) = &c.postal_code_format
{
out.push_str(&format!(" postal_code_format: \"{}\",\n", escape(v)));
}
if feature("region")
&& let Some(v) = &c.region
{
out.push_str(&format!(" region: \"{}\",\n", escape(v)));
}
if feature("start_of_week")
&& let Some(v) = &c.start_of_week
{
out.push_str(&format!(" start_of_week: \"{}\",\n", escape(v)));
}
if feature("subregion")
&& let Some(v) = &c.subregion
{
out.push_str(&format!(" subregion: \"{}\",\n", escape(v)));
}
if feature("un_locode")
&& let Some(v) = &c.un_locode
{
out.push_str(&format!(" un_locode: \"{}\",\n", escape(v)));
}
if feature("unofficial_names") {
let list = c
.unofficial_names
.as_ref()
.map(|v| {
v.iter()
.map(|s| format!("\"{}\"", escape(s)))
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_default();
out.push_str(&format!(" unofficial_names: &[{}],\n", list));
}
if feature("world_region")
&& let Some(v) = &c.world_region
{
out.push_str(&format!(" world_region: \"{}\",\n", escape(v)));
}
out.push_str(" },\n");
}
out.push_str("];\n\n");
if feature("phf") && feature("iso_code") {
let mut map = phf_codegen::Map::new();
for (i, c) in raw.iter().enumerate() {
if let Some(code) = &c.iso_code {
map.entry(code, &i.to_string());
}
}
out.push_str("#[cfg(feature = \"iso_code\")]\n");
out.push_str("pub static ISO_CODE_INDEX: phf::Map<&'static str, usize> = ");
out.push_str(&map.build().to_string());
out.push_str(";\n");
}
fs::write(out_dir.join("constants.rs"), out).expect("write constants.rs");
}