Restructure render

This commit is contained in:
Dominic Grimm 2025-06-20 15:13:43 +02:00
parent 9b0c60d42d
commit 16fd5b0b95
Signed by: dergrimm
SSH key fingerprint: SHA256:0uoWpcqOtkyvQ+ZqBjNYiDqIZY+9s8VeZkkJ/4ryB4E
18 changed files with 207 additions and 133 deletions

View file

@ -0,0 +1,90 @@
pub const TransportationProductClass = enum(u32) {
zug = 0,
s_bahn = 1,
u_bahn = 2,
stadtbahn = 3,
strassen_trambahn = 4,
stadtbus = 5,
regionalbus = 6,
schnellbus = 7,
seil_zahnradbahn = 8,
schiff = 9,
anruf_sammel_taxi = 10,
sonstige = 11,
flugzeug = 12,
zug_nv = 13,
zug_fv = 14,
zug_fv_m_zuschlag = 15,
zug_fv_m_spez_fpr = 16,
sev = 17,
zug_shuttle = 18,
buergerbus = 19,
rufbus_liniengebunden = 20,
rufbus = 21,
_,
pub fn toString(self: TransportationProductClass) ?[]const u8 {
return switch (self) {
.zug => "Zug",
.s_bahn => "S-Bahn",
.u_bahn => "U-Bahn",
.stadtbahn => "Stadtbahn",
.strassen_trambahn => "Straßen-/Trambahn",
.stadtbus => "Stadtbus",
.regionalbus => "Regionalbus",
.schnellbus => "Schnellbus",
.seil_zahnradbahn => "Seil-/Zahnradbahn",
.schiff => "Schiff",
.anruf_sammel_taxi => "Anruf-Sammel-Taxi",
.sonstige => "Sonstige",
.flugzeug => "Flugzeug",
.zug_nv => "Zug (Nahverkehr)",
.zug_fv => "Zug (Fernverkehr)",
.zug_fv_m_zuschlag => "Zug (Fernverkehr) mit Zuschlag",
.zug_fv_m_spez_fpr => "Zug (Fernverkehr) mit spezifischer Fpr",
.sev => "SEV Schnienenersatzverkehr",
.zug_shuttle => "Zug Shuttle",
.buergerbus => "Bürgerbus",
.rufbus_liniengebunden => "Rufbus (liniengebunden)",
.rufbus => "Rufbus",
_ => null,
};
}
};
pub const Location = struct {
id: []const u8,
name: []const u8,
properties: struct {
platform: ?[]const u8 = null,
},
};
pub const StopEvent = struct {
location: Location,
departureTimePlanned: []const u8,
departureTimeBaseTimetable: []const u8,
departureTimeEstimated: ?[]const u8 = null,
transportation: struct {
name: []const u8,
product: struct {
class: TransportationProductClass,
},
operator: ?struct {
name: []const u8,
} = null,
destination: struct {
name: []const u8,
},
},
hints: ?[]const struct {
content: []const u8,
type: []const u8,
} = null,
isCancelled: bool = false,
};
pub const DmResponse = struct {
locations: []const Location,
stopEvents: []const StopEvent,
};

View file

@ -2,99 +2,10 @@ const std = @import("std");
const escaper = @import("../../escaper.zig");
pub const models = @import("models.zig");
const log = std.log.scoped(.efa);
pub const ApiTransportationProductClass = enum(u32) {
zug = 0,
s_bahn = 1,
u_bahn = 2,
stadtbahn = 3,
strassen_trambahn = 4,
stadtbus = 5,
regionalbus = 6,
schnellbus = 7,
seil_zahnradbahn = 8,
schiff = 9,
anruf_sammel_taxi = 10,
sonstige = 11,
flugzeug = 12,
zug_nv = 13,
zug_fv = 14,
zug_fv_m_zuschlag = 15,
zug_fv_m_spez_fpr = 16,
sev = 17,
zug_shuttle = 18,
buergerbus = 19,
rufbus_liniengebunden = 20,
rufbus = 21,
_,
pub fn toString(self: ApiTransportationProductClass) ?[]const u8 {
return switch (self) {
.zug => "Zug",
.s_bahn => "S-Bahn",
.u_bahn => "U-Bahn",
.stadtbahn => "Stadtbahn",
.strassen_trambahn => "Straßen-/Trambahn",
.stadtbus => "Stadtbus",
.regionalbus => "Regionalbus",
.schnellbus => "Schnellbus",
.seil_zahnradbahn => "Seil-/Zahnradbahn",
.schiff => "Schiff",
.anruf_sammel_taxi => "Anruf-Sammel-Taxi",
.sonstige => "Sonstige",
.flugzeug => "Flugzeug",
.zug_nv => "Zug (Nahverkehr)",
.zug_fv => "Zug (Fernverkehr)",
.zug_fv_m_zuschlag => "Zug (Fernverkehr) mit Zuschlag",
.zug_fv_m_spez_fpr => "Zug (Fernverkehr) mit spezifischer Fpr",
.sev => "SEV Schnienenersatzverkehr",
.zug_shuttle => "Zug Shuttle",
.buergerbus => "Bürgerbus",
.rufbus_liniengebunden => "Rufbus (liniengebunden)",
.rufbus => "Rufbus",
_ => null,
};
}
};
pub const ApiLocation = struct {
id: []const u8,
name: []const u8,
properties: struct {
platform: ?[]const u8 = null,
},
};
pub const ApiStopEvent = struct {
location: ApiLocation,
departureTimePlanned: []const u8,
departureTimeBaseTimetable: []const u8,
departureTimeEstimated: ?[]const u8 = null,
transportation: struct {
name: []const u8,
product: struct {
class: ApiTransportationProductClass,
},
operator: ?struct {
name: []const u8,
} = null,
destination: struct {
name: []const u8,
},
},
hints: ?[]const struct {
content: []const u8,
type: []const u8,
} = null,
isCancelled: bool = false,
};
pub const DmResponse = struct {
locations: []const ApiLocation,
stopEvents: []const ApiStopEvent,
};
pub const Client = struct {
pub const Error = error{
InvalidResponse,
@ -170,7 +81,7 @@ pub const Client = struct {
try std.io.getStdOut().writeAll(body);
}
pub fn getDepartures(self: *const Self, stop_id: []const u8) !std.json.Parsed(DmResponse) {
pub fn getDepartures(self: *const Self, stop_id: []const u8) !std.json.Parsed(models.DmResponse) {
const escaped_stop_id = try escaper.escapeUriComponent(self.allocator, stop_id);
defer if (escaped_stop_id) |s| self.allocator.free(s);
const url_stop_id = escaped_stop_id orelse stop_id;
@ -204,7 +115,7 @@ pub const Client = struct {
defer self.allocator.free(body);
const response = try std.json.parseFromSlice(
DmResponse,
models.DmResponse,
self.allocator,
body,
.{ .allocate = .alloc_always, .ignore_unknown_fields = true },

View file

@ -0,0 +1 @@
pub const efa = @import("efa/root.zig");

View file

@ -5,7 +5,7 @@ const zdt = @import("zdt");
const Dimensions = @import("../../Dimensions.zig");
const renderer = @import("../../renderer.zig");
pub const efa = @import("efa.zig");
pub const api = @import("../../api/root.zig");
const template = @import("template/root.zig");
@ -28,7 +28,7 @@ pub const RenderOptions = struct {
show_operator: bool,
};
pub fn render(self: *const Self, efa_dm_resp: *const efa.DmResponse, options: RenderOptions) ![]const u8 {
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);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,8 +1,31 @@
const std = @import("std");
const efa = @import("../../efa.zig");
const api = @import("../../../../api/root.zig");
const embed = @import("../../../../embed.zig");
pub fn iconFromTransportationProductClass(class: efa.ApiTransportationProductClass) ?[]const u8 {
pub const data = embed.embedBase64Files(
(struct {
fn inner(comptime path: []const u8) []const u8 {
return @embedFile(path);
}
}).inner,
"image/svg+xml",
"svg",
"svg",
&.{
"train",
"subway",
"tram",
"bus",
"shuttle",
"gondola",
"flight",
"boat",
"no_transfer",
},
){};
pub fn iconFromTransportationProductClass(class: api.efa.models.TransportationProductClass) ?embed.EncodedFile {
return switch (class) {
.zug,
.s_bahn,
@ -11,11 +34,11 @@ pub fn iconFromTransportationProductClass(class: efa.ApiTransportationProductCla
.zug_fv_m_zuschlag,
.zug_fv_m_spez_fpr,
.zug_shuttle,
=> train,
=> data.train,
.u_bahn => subway,
.u_bahn => data.subway,
.stadtbahn, .strassen_trambahn => tram,
.stadtbahn, .strassen_trambahn => data.tram,
.stadtbus,
.regionalbus,
@ -24,40 +47,18 @@ pub fn iconFromTransportationProductClass(class: efa.ApiTransportationProductCla
.rufbus_liniengebunden,
.rufbus,
.anruf_sammel_taxi,
=> bus,
=> data.bus,
.seil_zahnradbahn => gondola,
.seil_zahnradbahn => data.gondola,
.schiff => boat,
.schiff => data.boat,
.sonstige => null,
.flugzeug => flight,
.flugzeug => data.flight,
.sev => no_transfer,
.sev => data.no_transfer,
_ => null,
};
}
fn encodePng(comptime path: []const u8) []const u8 {
const raw = @embedFile(path);
const len = std.base64.standard.Encoder.calcSize(raw.len);
var buf: [len]u8 = undefined;
{
@setEvalBranchQuota(1_000_000);
_ = std.base64.standard.Encoder.encode(&buf, raw);
}
const final = buf;
return &final;
}
pub const train = encodePng("img/train.png");
pub const subway = encodePng("img/subway.png");
pub const tram = encodePng("img/tram.png");
pub const bus = encodePng("img/bus.png");
pub const shuttle = encodePng("img/shuttle.png");
pub const gondola = encodePng("img/gondola.png");
pub const flight = encodePng("img/flight.png");
pub const boat = encodePng("img/boat.png");
pub const no_transfer = encodePng("img/no_transfer.png");

View file

@ -1,7 +1,7 @@
const std = @import("std");
const escaper = @import("../../../escaper.zig");
const efa = @import("../efa.zig");
const api = @import("../../../api/root.zig");
const icons = @import("icons/root.zig");
@ -9,7 +9,7 @@ pub const Departure = struct {
time: []const u8,
time_unix: i128,
line: []const u8,
transportation_product_class: efa.ApiTransportationProductClass,
transportation_product_class: api.efa.models.TransportationProductClass,
operator: ?[]const u8,
destination: []const u8,
platform: ?[]const u8,
@ -188,13 +188,16 @@ pub fn render(context: *const Context, writer: anytype) !void {
);
}
if (icons.iconFromTransportationProductClass(departure.transportation_product_class)) |img| {
if (icons.iconFromTransportationProductClass(departure.transportation_product_class)) |file| {
try writer.print(
\\ <td>
\\ <img class="icon" src="data:image/png;charset=utf-8;base64,{[img]s}" />
\\ <img class="icon" src="data:{[content_type]s};charset=utf-8;base64,{[data]s}" />
\\ </td>
,
.{ .img = img },
.{
.content_type = file.content_type,
.data = file.data,
},
);
} else {
try writer.writeAll(

View file

@ -0,0 +1,67 @@
const std = @import("std");
pub const EncodedFile = struct {
content_type: []const u8,
data: []const u8,
};
pub const EmbedFn = *const fn (comptime path: []const u8) []const u8;
pub fn encodeFileBase64(
comptime embed_fn: EmbedFn,
comptime content_type: []const u8,
comptime path: []const u8,
) EncodedFile {
const raw = embed_fn(path);
const len = std.base64.standard.Encoder.calcSize(raw.len);
var buf: [len]u8 = undefined;
{
@setEvalBranchQuota(1_000_000);
_ = std.base64.standard.Encoder.encode(&buf, raw);
}
const final = buf;
return .{
.content_type = content_type,
.data = &final,
};
}
pub fn embedBase64Files(
comptime embed_fn: EmbedFn,
comptime content_type: []const u8,
comptime extension: []const u8,
comptime path: []const u8,
comptime names: []const []const u8,
) type {
var fields: [names.len]std.builtin.Type.StructField = undefined;
for (names, 0..) |name, i| {
const encoded = encodeFileBase64(
embed_fn,
content_type,
std.fmt.comptimePrint(
"{s}/{s}.{s}",
.{ path, name, extension },
),
);
var name_z: [name.len:0]u8 = undefined;
@memcpy(&name_z, name);
fields[i] = std.builtin.Type.StructField{
.name = &name_z,
.type = EncodedFile,
.is_comptime = true,
.alignment = 0,
.default_value_ptr = &encoded,
};
}
return @Type(.{ .@"struct" = .{
.layout = .auto,
.is_tuple = false,
.fields = &fields,
.decls = &[_]std.builtin.Type.Declaration{},
} });
}

View file

@ -52,7 +52,7 @@ pub fn main() !void {
return clap.help(writer, clap.Help, &params, .{});
}
const efa = try render.apps.Departures.efa.Client.init(allocator, res.args.efa.?);
const efa = try render.api.efa.Client.init(allocator, res.args.efa.?);
defer efa.deinit();
if (res.args.stopfinder) |query| {

View file

@ -1,3 +1,4 @@
pub const api = @import("api/root.zig");
pub const apps = @import("apps/root.zig");
pub const renderer = @import("renderer.zig");
pub const FeedClient = @import("FeedClient.zig");