eink-feed/src/eink_feed_render/apps/Departures/root.zig

131 lines
4.4 KiB
Zig

const std = @import("std");
const temp = @import("temp");
const zdt = @import("zdt");
const Dimensions = @import("../../Dimensions.zig");
const renderer = @import("../../renderer.zig");
pub const api = @import("../../api/root.zig");
const template = @import("template/root.zig");
const Self = @This();
allocator: std.mem.Allocator,
dimensions: Dimensions,
pub fn init(allocator: std.mem.Allocator, dimensions: Dimensions) Self {
return .{
.allocator = allocator,
.dimensions = dimensions,
};
}
pub const RenderOptions = struct {
now: *const zdt.Datetime,
tz: *const zdt.Timezone,
max_items: usize,
show_operator: bool,
};
pub fn render(self: *const Self, efa_dm_resp: *const api.efa.models.DmResponse, options: RenderOptions) ![]const u8 {
const now_local = try options.now.tzConvert(.{ .tz = options.tz });
var arena = std.heap.ArenaAllocator.init(self.allocator);
defer arena.deinit();
const departures = try arena.allocator().alloc(template.Departure, efa_dm_resp.stopEvents.len);
var dep_i: usize = 0;
for (efa_dm_resp.stopEvents) |dep| {
const dp_timetable = try zdt.Datetime.fromISO8601(dep.departureTimeBaseTimetable);
const dp_timetable_local = try dp_timetable.tzConvert(.{ .tz = options.tz });
const dp_planned = try zdt.Datetime.fromISO8601(
dep.departureTimeEstimated orelse dep.departureTimePlanned,
);
const dp_planned_local = try dp_planned.tzConvert(.{ .tz = options.tz });
const diff_min: i32 = @intFromFloat(dp_planned_local.diff(dp_timetable_local).totalMinutes());
const on_time: ?u32 = if (diff_min < 0) @abs(diff_min) else null;
const delayed: ?u32 = if (diff_min > 0) @abs(diff_min) else null;
const date: ?[]const u8 = if (dp_planned_local.dayOfYear() != now_local.dayOfYear())
try std.fmt.allocPrint(arena.allocator(), "{d}.{d}.{d}", .{
dp_planned_local.day,
dp_planned_local.month,
dp_planned_local.year,
})
else
null;
var infos: ?[]const []const u8 = null;
if (dep.hints) |hints| {
const arr = try arena.allocator().alloc([]const u8, hints.len + 1);
var i: usize = 0;
for (hints) |hint| {
if (std.mem.eql(u8, hint.type, "Timetable"))
continue;
arr[i] = hint.content;
i += 1;
}
infos = arr[0..i];
}
departures[dep_i] = template.Departure{
.time = try std.fmt.allocPrint(
arena.allocator(),
"{d}:{d:0>2}",
.{ dp_timetable_local.hour, dp_timetable_local.minute },
),
.time_unix = dp_timetable_local.toUnix(.second),
.line = dep.transportation.name,
.transportation_product_class = dep.transportation.product.class,
.operator = if (dep.transportation.operator) |op| op.name else null,
.destination = dep.transportation.destination.name,
.platform = dep.location.properties.platform,
.date = date,
.on_time = on_time,
.delayed = delayed,
.cancelled = dep.isCancelled,
.information = infos,
};
dep_i += 1;
}
std.mem.sort(
template.Departure,
departures,
{},
template.Departure.cmpByTimeUnix,
);
const context = template.Context{
.station = efa_dm_resp.locations[0].name,
.time = try std.fmt.allocPrint(
arena.allocator(),
"{d}:{d:0>2}",
.{ now_local.hour, now_local.minute },
),
.departures = departures[0..@min(departures.len, options.max_items)],
.show_operator = options.show_operator,
};
const escaped_context = try context.escape(arena.allocator());
var tmp_file = try temp.TempFile.create(arena.allocator(), .{
.pattern = "render-departures-*.html",
});
defer tmp_file.deinit();
const path = try tmp_file.parent_dir.realpathAlloc(arena.allocator(), tmp_file.basename);
{
const file = try tmp_file.open(.{ .mode = .write_only });
defer file.close();
const writer = file.writer();
try template.render(&escaped_context, writer.any());
}
return try renderer.render(self.allocator, path, self.dimensions);
}