~jan0sch/rig

~jan0sch/rig/src/main.zig
 ..
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 }