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>,
economic_unions: Option<Vec<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,
}
#[derive(Debug, serde::Deserialize)]
struct RawPort {
name: String,
city: String,
country: String,
province: Option<String>,
coordinates: Vec<f64>,
timezone: Option<String>,
unlocs: Option<Vec<String>>,
code: Option<String>,
port_code: Option<String>,
alias: Option<Vec<String>>,
regions: Option<Vec<String>>,
}
#[derive(Debug, serde::Deserialize)]
struct PortFeature {
properties: RawPort,
}
#[derive(Debug, serde::Deserialize)]
struct PortsGeoJson {
features: Vec<PortFeature>,
}
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("economic_unions") {
let list = c
.economic_unions
.as_ref()
.map(|v| {
v.iter()
.map(|s| format!("\"{}\"", escape(s)))
.collect::<Vec<_>>()
.join(", ")
})
.unwrap_or_default();
out.push_str(&format!(" economic_unions: &[{}],\n", list));
}
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");
if feature("ports") {
let ports_json_path = PathBuf::from("data/ports.json");
println!("cargo:rerun-if-changed={}", ports_json_path.display());
let ports_data = fs::read_to_string(&ports_json_path).expect("Failed to read data/ports.json");
let geojson: PortsGeoJson = serde_json::from_str(&ports_data).expect("Failed to parse ports.json");
let mut ports_out = String::new();
ports_out.push_str("// @generated by build.rs. Do not edit.\n");
ports_out.push_str("/// Static array of all ports in the database.\n");
ports_out.push_str("pub static PORTS: &[crate::definitions::Port] = &[\n");
for feature in &geojson.features {
let p = &feature.properties;
if p.coordinates.len() >= 2 {
let longitude = p.coordinates[0];
let latitude = p.coordinates[1];
let state = p.province.as_deref().unwrap_or("");
let timezone = p.timezone.as_deref().unwrap_or("");
let code = p.code.as_deref().unwrap_or("");
let port_code = p.port_code.as_deref().unwrap_or("");
let unlocs = p.unlocs.as_ref()
.map(|v| v.iter().map(|s| format!("\"{}\"", escape(s))).collect::<Vec<_>>().join(", "))
.unwrap_or_default();
let alias = p.alias.as_ref()
.map(|v| v.iter().map(|s| format!("\"{}\"", escape(s))).collect::<Vec<_>>().join(", "))
.unwrap_or_default();
let regions = p.regions.as_ref()
.map(|v| v.iter().map(|s| format!("\"{}\"", escape(s))).collect::<Vec<_>>().join(", "))
.unwrap_or_default();
ports_out.push_str(" crate::definitions::Port {\n");
ports_out.push_str(&format!(" name: \"{}\",\n", escape(&p.name)));
ports_out.push_str(&format!(" city: \"{}\",\n", escape(&p.city)));
ports_out.push_str(&format!(" state: \"{}\",\n", escape(state)));
ports_out.push_str(&format!(" country: \"{}\",\n", escape(&p.country)));
ports_out.push_str(&format!(" latitude: {},\n", fmt_f64(latitude)));
ports_out.push_str(&format!(" longitude: {},\n", fmt_f64(longitude)));
ports_out.push_str(&format!(" timezone: \"{}\",\n", escape(timezone)));
ports_out.push_str(&format!(" unlocs: &[{}],\n", unlocs));
ports_out.push_str(&format!(" code: \"{}\",\n", escape(code)));
ports_out.push_str(&format!(" port_code: \"{}\",\n", escape(port_code)));
ports_out.push_str(&format!(" alias: &[{}],\n", alias));
ports_out.push_str(&format!(" regions: &[{}],\n", regions));
ports_out.push_str(" },\n");
}
}
ports_out.push_str("];\n");
fs::write(out_dir.join("ports.rs"), ports_out).expect("write ports.rs");
}
}