AI: add imperial/US default
This commit is contained in:
parent
4746ad81b9
commit
bcab444bca
5 changed files with 95 additions and 2 deletions
|
|
@ -157,7 +157,7 @@ curl wttr.in/London?Tnq
|
|||
|--------|-------------|
|
||||
| `A` | Force ANSI output (even for browsers) |
|
||||
| `n` | Narrow output (narrower terminal width) |
|
||||
| `m` | Use metric units |
|
||||
| `m` | Use metric units (force metric even for US) |
|
||||
| `M` | Use m/s for wind speed |
|
||||
| `u` | Use imperial units (Fahrenheit, mph) |
|
||||
| `I` | Inverted colors |
|
||||
|
|
@ -172,6 +172,14 @@ curl wttr.in/London?Tnq
|
|||
| `Q` | Super quiet (no city name) |
|
||||
| `F` | No follow line (no "Follow @igor_chubin") |
|
||||
|
||||
**Unit System Defaults:**
|
||||
|
||||
The service automatically selects units based on:
|
||||
1. Explicit query parameter (`?u` or `?m`) - highest priority
|
||||
2. Language parameter (`lang=us`) - forces imperial
|
||||
3. Client IP geolocation - US IPs default to imperial
|
||||
4. Default - metric for all other locations
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
curl wttr.in/London?T # Plain text, no ANSI
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ This directory contains comprehensive documentation for rewriting wttr.in in Zig
|
|||
- Static help pages (/:help, /:translation)
|
||||
- Error handling (404/500 status codes)
|
||||
- Configuration from environment variables
|
||||
- **Imperial units auto-detection**: Automatically uses imperial units (°F, mph) for US IP addresses and `lang=us`, with explicit `?u` and `?m` overrides
|
||||
|
||||
### Missing Features (To Be Implemented Later)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ pub const HandleWeatherOptions = struct {
|
|||
cache: *Cache,
|
||||
provider: WeatherProvider,
|
||||
resolver: *Resolver,
|
||||
geoip: *@import("../location/geoip.zig").GeoIP,
|
||||
};
|
||||
|
||||
pub fn handleWeather(
|
||||
|
|
@ -138,7 +139,25 @@ fn handleWeatherInternal(
|
|||
if (params.lang) |l| allocator.free(l);
|
||||
}
|
||||
|
||||
const use_imperial = if (params.units) |u| u == .uscs else false;
|
||||
// Determine if imperial units should be used
|
||||
// Priority: explicit ?u or ?m > lang=us > US IP > default metric
|
||||
const use_imperial = blk: {
|
||||
if (params.units) |u| {
|
||||
break :blk u == .uscs;
|
||||
}
|
||||
if (params.lang) |lang| {
|
||||
if (std.mem.eql(u8, lang, "us")) {
|
||||
break :blk true;
|
||||
}
|
||||
}
|
||||
const client_ip = getClientIP(req);
|
||||
if (client_ip.len > 0) {
|
||||
if (opts.geoip.isUSIP(client_ip)) {
|
||||
break :blk true;
|
||||
}
|
||||
}
|
||||
break :blk false;
|
||||
};
|
||||
|
||||
const output = if (params.format) |fmt| blk: {
|
||||
if (std.mem.eql(u8, fmt, "j1")) {
|
||||
|
|
@ -190,3 +209,25 @@ test "parseXForwardedFor trims whitespace" {
|
|||
test "parseXForwardedFor handles empty string" {
|
||||
try std.testing.expectEqualStrings("", parseXForwardedFor(""));
|
||||
}
|
||||
|
||||
test "imperial units selection logic" {
|
||||
// This test documents the priority order for unit selection:
|
||||
// 1. Explicit ?u or ?m parameter (highest priority)
|
||||
// 2. lang=us parameter
|
||||
// 3. US IP detection
|
||||
// 4. Default to metric
|
||||
|
||||
// The actual logic is tested through integration tests
|
||||
// This test just verifies the QueryParams parsing works
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
const params_u = try QueryParams.parse(allocator, "u");
|
||||
try std.testing.expectEqual(QueryParams.Units.uscs, params_u.units.?);
|
||||
|
||||
const params_m = try QueryParams.parse(allocator, "m");
|
||||
try std.testing.expectEqual(QueryParams.Units.metric, params_m.units.?);
|
||||
|
||||
const params_lang = try QueryParams.parse(allocator, "lang=us");
|
||||
defer allocator.free(params_lang.lang.?);
|
||||
try std.testing.expectEqualStrings("us", params_lang.lang.?);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,32 @@ pub const GeoIP = struct {
|
|||
return try self.extractCoordinates(result.entry);
|
||||
}
|
||||
|
||||
pub fn isUSIP(self: *GeoIP, ip: []const u8) bool {
|
||||
const ip_z = std.heap.c_allocator.dupeZ(u8, ip) catch return false;
|
||||
defer std.heap.c_allocator.free(ip_z);
|
||||
|
||||
var gai_error: c_int = 0;
|
||||
var mmdb_error: c_int = 0;
|
||||
|
||||
const result = MMDB_lookup_string(&self.mmdb, ip_z.ptr, &gai_error, &mmdb_error);
|
||||
|
||||
if (gai_error != 0 or mmdb_error != 0 or !result.found_entry) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var entry_mut = result.entry;
|
||||
var country_data: MMDBEntryData = undefined;
|
||||
const null_term: [*:0]const u8 = @ptrCast(&[_]u8{0});
|
||||
const status = MMDB_get_value(&entry_mut, &country_data, "country\x00", "iso_code\x00", null_term);
|
||||
|
||||
if (status != 0 or !country_data.has_data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const country_code = std.mem.span(country_data.utf8_string);
|
||||
return std.mem.eql(u8, country_code, "US");
|
||||
}
|
||||
|
||||
fn extractCoordinates(self: *GeoIP, entry: MMDBEntry) !Coordinates {
|
||||
_ = self;
|
||||
var entry_mut = entry;
|
||||
|
|
@ -143,3 +169,19 @@ test "GeoIP init with invalid path fails" {
|
|||
const result = GeoIP.init("/nonexistent/path.mmdb");
|
||||
try std.testing.expectError(error.CannotOpenDatabase, result);
|
||||
}
|
||||
|
||||
test "isUSIP detects US IPs" {
|
||||
var geoip = GeoIP.init("./GeoLite2-City.mmdb") catch {
|
||||
std.debug.print("Skipping test - GeoLite2-City.mmdb not found\n", .{});
|
||||
return error.SkipZigTest;
|
||||
};
|
||||
defer geoip.deinit();
|
||||
|
||||
// Test that the function doesn't crash with various IPs
|
||||
_ = geoip.isUSIP("8.8.8.8");
|
||||
_ = geoip.isUSIP("1.1.1.1");
|
||||
|
||||
// Test invalid IP returns false
|
||||
const invalid = geoip.isUSIP("invalid");
|
||||
try std.testing.expect(!invalid);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ pub fn main() !void {
|
|||
.cache = &cache,
|
||||
.provider = metno.provider(),
|
||||
.resolver = &resolver,
|
||||
.geoip = &geoip,
|
||||
}, &rate_limiter);
|
||||
|
||||
try server.listen();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue