0 |
/// Copyright (c) 2025 Contributors as noted in the AUTHORS.md file |
1 |
/// Licensed under the EUPL |
2 |
const std = @import("std"); |
3 |
const fs = std.fs; |
4 |
|
5 |
pub fn main() !u8 { |
6 |
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); |
7 |
defer arena.deinit(); |
8 |
const allocator = arena.allocator(); |
9 |
|
10 |
var seed: u64 = undefined; |
11 |
try std.posix.getrandom(std.mem.asBytes(&seed)); |
12 |
var prng = std.rand.DefaultPrng.init(seed); |
13 |
const random = prng.random(); |
14 |
|
15 |
const argv = try std.process.argsAlloc(allocator); |
16 |
defer std.process.argsFree(allocator, argv); |
17 |
|
18 |
const country = parseArguments(argv) catch |e| { |
19 |
printHelp(argv); |
20 |
return e; |
21 |
}; |
22 |
|
23 |
const global_path = "/usr/local/share/rig"; |
24 |
const home = std.posix.getenv("HOME"); |
25 |
const local_path = try std.fmt.allocPrint(allocator, "{s}/.local/share/rig", .{home.?}); |
26 |
defer allocator.free(local_path); |
27 |
|
28 |
const forenames_local_path = try std.fmt.allocPrint(allocator, "{s}/forenames/{s}.tsv", .{ local_path, country }); |
29 |
defer allocator.free(forenames_local_path); |
30 |
const forenames_global_path = try std.fmt.allocPrint(allocator, "{s}/forenames/{s}.tsv", .{ global_path, country }); |
31 |
defer allocator.free(forenames_global_path); |
32 |
const surnames_local_path = try std.fmt.allocPrint(allocator, "{s}/surnames/{s}.tsv", .{ local_path, country }); |
33 |
defer allocator.free(surnames_local_path); |
34 |
const surnames_global_path = try std.fmt.allocPrint(allocator, "{s}/surnames/{s}.tsv", .{ global_path, country }); |
35 |
defer allocator.free(surnames_global_path); |
36 |
const zip_codes_local_path = try std.fmt.allocPrint(allocator, "{s}/zips/{s}.tsv", .{ local_path, country }); |
37 |
defer allocator.free(zip_codes_local_path); |
38 |
const zip_codes_global_path = try std.fmt.allocPrint(allocator, "{s}/zips/{s}.tsv", .{ global_path, country }); |
39 |
defer allocator.free(zip_codes_global_path); |
40 |
|
41 |
const stdout_file = std.io.getStdOut().writer(); |
42 |
var bw = std.io.bufferedWriter(stdout_file); |
43 |
const stdout = bw.writer(); |
44 |
|
45 |
const forenames = try readData(allocator, forenames_local_path, forenames_global_path); |
46 |
defer forenames.deinit(); |
47 |
const forenames_count: u32 = @intCast(forenames.items.len - 1); |
48 |
const forename_index = calculateRandomEntry(random, forenames_count); |
49 |
const random_forename = forenames.items[forename_index]; |
50 |
defer allocator.free(random_forename); |
51 |
|
52 |
const surnames = try readData(allocator, surnames_local_path, surnames_global_path); |
53 |
defer surnames.deinit(); |
54 |
const surnames_count: u32 = @intCast(surnames.items.len - 1); |
55 |
const surname_index = calculateRandomEntry(random, surnames_count); |
56 |
const random_surname = surnames.items[surname_index]; |
57 |
defer allocator.free(random_surname); |
58 |
|
59 |
const zip_codes = try readData(allocator, zip_codes_local_path, zip_codes_global_path); |
60 |
defer zip_codes.deinit(); |
61 |
const zip_codes_count: u32 = @intCast(zip_codes.items.len - 1); |
62 |
const zip_code_index = calculateRandomEntry(random, zip_codes_count); |
63 |
const random_zip_code = zip_codes.items[zip_code_index]; |
64 |
defer allocator.free(random_zip_code); |
65 |
|
66 |
const random_address = try allocator.dupe(u8, random_zip_code); |
67 |
|
68 |
try stdout.print("{s} {s}, {s}\n", .{ random_forename, random_surname, random_address }); |
69 |
|
70 |
try bw.flush(); // don't forget to flush! |
71 |
|
72 |
return 0; |
73 |
} |
74 |
|
75 |
fn calculateRandomEntry(random: std.Random, max: u32) u32 { |
76 |
const result = random.intRangeAtMostBiased(u32, 0, max); |
77 |
return result; |
78 |
} |
79 |
|
80 |
/// Parse the given command line arguments and return the needed ones |
81 |
/// or an error. |
82 |
fn parseArguments(argv: [][]u8) error{ InvalidArgs, MissingArgs }![]const u8 { |
83 |
if (argv.len < 2) { |
84 |
return error.MissingArgs; |
85 |
} |
86 |
|
87 |
if (argv[1].len != 3 or argv.len > 2) { |
88 |
return error.InvalidArgs; |
89 |
} |
90 |
|
91 |
return argv[1]; |
92 |
} |
93 |
|
94 |
/// Print the help for the program. |
95 |
fn printHelp(argv: [][]u8) void { |
96 |
std.debug.print("Usage: {s} <ISO-3166-1 alpha-3 country code>\n", .{argv[0]}); |
97 |
return; |
98 |
} |
99 |
|
100 |
/// Try to return the content of a file by trying to read it from the given |
101 |
/// path and fall back to the alternate one if the first read fails. |
102 |
fn readData(allocator: std.mem.Allocator, path: []const u8, alternate_path: []const u8) !std.ArrayList([]const u8) { |
103 |
const file = fs.openFileAbsolute(path, .{}) catch try fs.openFileAbsolute(alternate_path, .{}); |
104 |
defer file.close(); |
105 |
|
106 |
var buf_reader = std.io.bufferedReader(file.reader()); |
107 |
const reader = buf_reader.reader(); |
108 |
|
109 |
var lines = std.ArrayList([]const u8).init(allocator); |
110 |
errdefer lines.deinit(); |
111 |
|
112 |
var line = std.ArrayList(u8).init(allocator); |
113 |
defer line.deinit(); |
114 |
|
115 |
const writer = line.writer(); |
116 |
while (reader.streamUntilDelimiter(writer, '\n', null)) { |
117 |
defer line.clearRetainingCapacity(); |
118 |
//std.debug.print("read line: '{s}'\n", .{line.items}); |
119 |
const str = try std.fmt.allocPrint(allocator, "{s}", .{line.items}); |
120 |
try lines.append(str); |
121 |
} else |e| switch (e) { |
122 |
error.EndOfStream => { |
123 |
if (line.items.len > 0) { |
124 |
//std.debug.print("read line: '{s}'\n", .{line.items}); |
125 |
const str = try std.fmt.allocPrint(allocator, "{s}", .{line.items}); |
126 |
try lines.append(str); |
127 |
} |
128 |
}, |
129 |
else => return e, // Escalate error. |
130 |
} |
131 |
return lines; |
132 |
} |
133 |
|
134 |
test "calculate random entry" { |
135 |
var seed: u64 = undefined; |
136 |
try std.posix.getrandom(std.mem.asBytes(&seed)); |
137 |
var prng = std.rand.DefaultPrng.init(seed); |
138 |
const random = prng.random(); |
139 |
|
140 |
const max = random.intRangeAtMostBiased(u32, 0, std.math.maxInt(u32)); |
141 |
|
142 |
const result = calculateRandomEntry(random, max); |
143 |
|
144 |
try std.testing.expect(result >= 0 and result <= max); |
145 |
} |