Restructure render
90
src/eink_feed_render/api/efa/models.zig
Normal 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,
|
||||
};
|
|
@ -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 },
|
1
src/eink_feed_render/api/root.zig
Normal file
|
@ -0,0 +1 @@
|
|||
pub const efa = @import("efa/root.zig");
|
|
@ -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);
|
||||
|
|
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.4 KiB |
|
@ -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");
|
||||
|
|
|
@ -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(
|
||||
|
|
67
src/eink_feed_render/embed.zig
Normal 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{},
|
||||
} });
|
||||
}
|
|
@ -52,7 +52,7 @@ pub fn main() !void {
|
|||
return clap.help(writer, clap.Help, ¶ms, .{});
|
||||
}
|
||||
|
||||
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| {
|
||||
|
|
|
@ -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");
|
||||
|
|