feat: +njalla module

This commit is contained in:
2026-07-13 23:37:14 +00:00
parent 7e8c6884db
commit ef7d1b29f4
12 changed files with 2825 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
mod geo;
#[cfg(feature = "geoip")]
mod geoip;
mod render;
use clap::Parser;
#[derive(Parser)]
#[command(name = "which-country-rs", version, about = "Detect your country by IP and render an ASCII world map")]
struct Args {
/// Map width in characters
#[arg(short = 'W', long, default_value_t = 80)]
width: usize,
/// Map height in characters
#[arg(short = 'H', long, default_value_t = 24)]
height: usize,
/// Country code to display (e.g. "US", "FR", "JP") — skips IP lookup
#[arg(short, long)]
country: Option<String>,
/// Latitude (requires --lon too) — skips IP lookup, derives country from coordinates
#[arg(long, requires = "lon", allow_hyphen_values = true)]
lat: Option<f64>,
/// Longitude (requires --lat too)
#[arg(long, requires = "lat", allow_hyphen_values = true)]
lon: Option<f64>,
}
static GEOJSON: &str = include_str!("../data/countries.geojson");
fn main() {
let args = Args::parse();
let countries = geo::load_countries(GEOJSON);
let (country_name, country_code, lat, lon) = if let Some(code) = &args.country {
// Direct country code — find it in the data
let code_upper = code.to_uppercase();
let c = countries
.iter()
.find(|c| c.iso_a2 == code_upper)
.unwrap_or_else(|| {
eprintln!("Unknown country code: {code_upper}");
std::process::exit(1);
});
let (label_lon, label_lat) = c.label_pos;
(c.name.clone(), code_upper, label_lat, label_lon)
} else if let (Some(lat), Some(lon)) = (args.lat, args.lon) {
// Coordinates provided — find which country contains the point
let idx = geo::find_country(lon, lat, &countries)
.unwrap_or_else(|| {
eprintln!("No country found at {lat}, {lon} (ocean?)");
std::process::exit(1);
});
(
countries[idx].name.clone(),
countries[idx].iso_a2.clone(),
lat,
lon,
)
} else {
// IP geolocation
#[cfg(feature = "geoip")]
{
eprint!("Looking up your location... ");
match geoip::lookup() {
Ok(loc) => {
eprintln!("done.");
(loc.country, loc.country_code, loc.lat, loc.lon)
}
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}
#[cfg(not(feature = "geoip"))]
{
eprintln!("IP geolocation requires the 'geoip' feature. Use --country or --lat/--lon instead.");
std::process::exit(1);
}
};
let map = render::render_map(&countries, &country_code, args.width, args.height);
println!("You appear to be in: {country_name} ({country_code})");
println!();
println!("{map}");
println!();
println!(
"Coordinates: {:.2}\u{00b0}{}, {:.2}\u{00b0}{}",
lat.abs(),
if lat >= 0.0 { "N" } else { "S" },
lon.abs(),
if lon >= 0.0 { "E" } else { "W" },
);
}