add(zig-interface): dependency to check interface contracts at comptime
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 53s
All checks were successful
Zig Project Action / Lint, Spell-check and test zig project (push) Successful in 53s
The described interfaces for `Widget` and `Layout` are now defined and correspondingly checked at comptime.
This commit is contained in:
10
build.zig
10
build.zig
@@ -15,13 +15,21 @@ pub fn build(b: *std.Build) void {
|
|||||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
const zg = b.dependency("zg", .{});
|
const zg = b.dependency("zg", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
const interface = b.dependency("interface", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
const lib = b.addModule("zterm", .{
|
const lib = b.addModule("zterm", .{
|
||||||
.root_source_file = b.path("src/zterm.zig"),
|
.root_source_file = b.path("src/zterm.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
lib.addImport("interface", interface.module("interface"));
|
||||||
lib.addImport("code_point", zg.module("code_point"));
|
lib.addImport("code_point", zg.module("code_point"));
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
.url = "https://codeberg.org/dude_the_builder/zg/archive/v0.13.2.tar.gz",
|
.url = "https://codeberg.org/dude_the_builder/zg/archive/v0.13.2.tar.gz",
|
||||||
.hash = "122055beff332830a391e9895c044d33b15ea21063779557024b46169fb1984c6e40",
|
.hash = "122055beff332830a391e9895c044d33b15ea21063779557024b46169fb1984c6e40",
|
||||||
},
|
},
|
||||||
|
.interface = .{
|
||||||
|
.url = "git+https://github.com/nilslice/zig-interface#c6ca205de75969fdcf04542f48d813d529196594",
|
||||||
|
.hash = "1220401627a97a7b429acd084bd3447fe5838122d71bbca57061906a2c3baf1c2e98",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
|||||||
@@ -22,9 +22,18 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
return struct {
|
const Type = struct {
|
||||||
const LayoutType = @This();
|
const LayoutType = @This();
|
||||||
|
const Element = union(enum) {
|
||||||
|
layout: LayoutType,
|
||||||
|
widget: @import("widget.zig").Widget(Event, Renderer),
|
||||||
|
};
|
||||||
const Ptr = usize;
|
const Ptr = usize;
|
||||||
|
pub const Interface = @import("interface").Interface(.{
|
||||||
|
.handle = fn (anytype, Event) anyerror!*Events,
|
||||||
|
.render = fn (anytype, *Renderer) anyerror!void,
|
||||||
|
.deinit = fn (anytype) void,
|
||||||
|
}, .{});
|
||||||
|
|
||||||
const VTable = struct {
|
const VTable = struct {
|
||||||
handle: *const fn (this: *LayoutType, event: Event) anyerror!*Events,
|
handle: *const fn (this: *LayoutType, event: Event) anyerror!*Events,
|
||||||
@@ -79,9 +88,17 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// import and export of `Layout` implementations
|
// import and export of `Layout` implementations
|
||||||
pub const HStack = @import("layout/HStack.zig").Layout(Event, Renderer);
|
pub const HStack = @import("layout/HStack.zig").Layout(Event, Element, Renderer);
|
||||||
pub const VStack = @import("layout/VStack.zig").Layout(Event, Renderer);
|
pub const VStack = @import("layout/VStack.zig").Layout(Event, Element, Renderer);
|
||||||
pub const Padding = @import("layout/Padding.zig").Layout(Event, Renderer);
|
pub const Padding = @import("layout/Padding.zig").Layout(Event, Element, Renderer);
|
||||||
pub const Framing = @import("layout/Framing.zig").Layout(Event, Renderer);
|
pub const Framing = @import("layout/Framing.zig").Layout(Event, Element, Renderer);
|
||||||
};
|
};
|
||||||
|
// test widget implementation satisfies the interface
|
||||||
|
comptime Type.Interface.satisfiedBy(Type);
|
||||||
|
// TODO: there is a dependency loop (due to the necessary `Layout` type for the `Element`)
|
||||||
|
comptime Type.Interface.satisfiedBy(Type.HStack);
|
||||||
|
comptime Type.Interface.satisfiedBy(Type.VStack);
|
||||||
|
comptime Type.Interface.satisfiedBy(Type.Padding);
|
||||||
|
comptime Type.Interface.satisfiedBy(Type.Framing);
|
||||||
|
return Type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,14 +12,13 @@ const Style = terminal.Cell.Style;
|
|||||||
|
|
||||||
const log = std.log.scoped(.layout_framing);
|
const log = std.log.scoped(.layout_framing);
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
pub fn Layout(comptime Event: type, comptime Element: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type, comptime Element: type, comptime Renderer: type)` is not of type `union(enum)`.");
|
||||||
|
}
|
||||||
|
if (!isTaggedUnion(Element)) {
|
||||||
|
@compileError("Provided type `Element` for `Layout(comptime Event: type, comptime Element: type, comptime Renderer: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Element = union(enum) {
|
|
||||||
layout: @import("../layout.zig").Layout(Event, Renderer),
|
|
||||||
widget: @import("../widget.zig").Widget(Event, Renderer),
|
|
||||||
};
|
|
||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
return struct {
|
return struct {
|
||||||
size: terminal.Size = undefined,
|
size: terminal.Size = undefined,
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ const Key = terminal.Key;
|
|||||||
|
|
||||||
const log = std.log.scoped(.layout_hstack);
|
const log = std.log.scoped(.layout_hstack);
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
pub fn Layout(comptime Event: type, comptime Element: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type, comptime Element: type, comptime Renderer: type)` is not of type `union(enum)`.");
|
||||||
|
}
|
||||||
|
if (!isTaggedUnion(Element)) {
|
||||||
|
@compileError("Provided type `Element` for `Layout(comptime Event: type, comptime Element: type, comptime Renderer: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Widget = @import("../widget.zig").Widget(Event, Renderer);
|
|
||||||
const Lay = @import("../layout.zig").Layout(Event, Renderer);
|
|
||||||
const Element = union(enum) {
|
|
||||||
layout: Lay,
|
|
||||||
widget: Widget,
|
|
||||||
};
|
|
||||||
const Elements = std.ArrayList(Element);
|
const Elements = std.ArrayList(Element);
|
||||||
|
// TODO: expect name of field to be corresponding to the type
|
||||||
|
const LayoutType = @typeInfo(Element).Union.fields[0].type;
|
||||||
|
const WidgetType = @typeInfo(Element).Union.fields[1].type;
|
||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
return struct {
|
return struct {
|
||||||
// TODO: current focused `Element`?
|
// TODO: current focused `Element`?
|
||||||
@@ -40,15 +40,15 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
inline for (comptime fields_info) |field| {
|
inline for (comptime fields_info) |field| {
|
||||||
const child = @field(children, field.name);
|
const child = @field(children, field.name);
|
||||||
const ChildType = @TypeOf(child);
|
const ChildType = @TypeOf(child);
|
||||||
if (ChildType == Widget) {
|
if (ChildType == WidgetType) {
|
||||||
elements.append(.{ .widget = child }) catch {};
|
elements.append(.{ .widget = child }) catch {};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ChildType == Lay) {
|
if (ChildType == LayoutType) {
|
||||||
elements.append(.{ .layout = child }) catch {};
|
elements.append(.{ .layout = child }) catch {};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(Lay) ++ " or " ++ @typeName(Widget) ++ " but " ++ @typeName(ChildType));
|
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(WidgetType) ++ " or " ++ @typeName(LayoutType) ++ " but " ++ @typeName(ChildType));
|
||||||
}
|
}
|
||||||
return .{
|
return .{
|
||||||
.elements = elements,
|
.elements = elements,
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ const Key = terminal.Key;
|
|||||||
|
|
||||||
const log = std.log.scoped(.layout_padding);
|
const log = std.log.scoped(.layout_padding);
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
pub fn Layout(comptime Event: type, comptime Element: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type, comptime Element: type, comptime Renderer: type)` is not of type `union(enum)`.");
|
||||||
|
}
|
||||||
|
if (!isTaggedUnion(Element)) {
|
||||||
|
@compileError("Provided type `Element` for `Layout(comptime Event: type, comptime Element: type, comptime Renderer: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Element = union(enum) {
|
|
||||||
layout: @import("../layout.zig").Layout(Event, Renderer),
|
|
||||||
widget: @import("../widget.zig").Widget(Event, Renderer),
|
|
||||||
};
|
|
||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
return struct {
|
return struct {
|
||||||
size: terminal.Size = undefined,
|
size: terminal.Size = undefined,
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ const Key = terminal.Key;
|
|||||||
|
|
||||||
const log = std.log.scoped(.layout_vstack);
|
const log = std.log.scoped(.layout_vstack);
|
||||||
|
|
||||||
pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
pub fn Layout(comptime Event: type, comptime Element: type, comptime Renderer: type) type {
|
||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type, comptime Element: type, comptime Renderer: type)` is not of type `union(enum)`.");
|
||||||
|
}
|
||||||
|
if (!isTaggedUnion(Element)) {
|
||||||
|
@compileError("Provided type `Element` for `Layout(comptime Event: type, comptime Element: type, comptime Renderer: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
const Widget = @import("../widget.zig").Widget(Event, Renderer);
|
|
||||||
const Lay = @import("../layout.zig").Layout(Event, Renderer);
|
|
||||||
const Element = union(enum) {
|
|
||||||
layout: Lay,
|
|
||||||
widget: Widget,
|
|
||||||
};
|
|
||||||
const Elements = std.ArrayList(Element);
|
const Elements = std.ArrayList(Element);
|
||||||
|
// TODO: expect name of field to be corresponding to the type
|
||||||
|
const LayoutType = @typeInfo(Element).Union.fields[0].type;
|
||||||
|
const WidgetType = @typeInfo(Element).Union.fields[1].type;
|
||||||
const Events = std.ArrayList(Event);
|
const Events = std.ArrayList(Event);
|
||||||
return struct {
|
return struct {
|
||||||
// TODO: current focused `Element`?
|
// TODO: current focused `Element`?
|
||||||
@@ -40,15 +40,15 @@ pub fn Layout(comptime Event: type, comptime Renderer: type) type {
|
|||||||
inline for (comptime fields_info) |field| {
|
inline for (comptime fields_info) |field| {
|
||||||
const child = @field(children, field.name);
|
const child = @field(children, field.name);
|
||||||
const ChildType = @TypeOf(child);
|
const ChildType = @TypeOf(child);
|
||||||
if (ChildType == Widget) {
|
if (ChildType == WidgetType) {
|
||||||
elements.append(.{ .widget = child }) catch {};
|
elements.append(.{ .widget = child }) catch {};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ChildType == Lay) {
|
if (ChildType == LayoutType) {
|
||||||
elements.append(.{ .layout = child }) catch {};
|
elements.append(.{ .layout = child }) catch {};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(Lay) ++ " or " ++ @typeName(Widget) ++ " but " ++ @typeName(ChildType));
|
@compileError("child: " ++ field.name ++ " is not of type " ++ @typeName(WidgetType) ++ " or " ++ @typeName(LayoutType) ++ " but " ++ @typeName(ChildType));
|
||||||
}
|
}
|
||||||
return .{
|
return .{
|
||||||
.elements = elements,
|
.elements = elements,
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
//! Dynamic dispatch for widget implementations.
|
//! Dynamic dispatch for widget implementations.
|
||||||
//! Each widget should at least implement these functions:
|
//! Each `Widget` has to implement the `WidgetInterface`
|
||||||
//! - handle(this: *@This(), event: Event) ?Event {}
|
|
||||||
//! - render(this: *@This(), renderer: *Renderer) !void {}
|
|
||||||
//! - deinit(this: *@This()) void {}
|
|
||||||
//!
|
//!
|
||||||
//! Create a `Widget` using `createFrom(object: anytype)` and use them through
|
//! Create a `Widget` using `createFrom(object: anytype)` and use them through
|
||||||
//! the defined interface. The widget will take care of calling the correct
|
//! the defined interface. The widget will take care of calling the correct
|
||||||
@@ -21,9 +18,14 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
if (!isTaggedUnion(Event)) {
|
if (!isTaggedUnion(Event)) {
|
||||||
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
@compileError("Provided user event `Event` for `Layout(comptime Event: type)` is not of type `union(enum)`.");
|
||||||
}
|
}
|
||||||
return struct {
|
const Type = struct {
|
||||||
const WidgetType = @This();
|
const WidgetType = @This();
|
||||||
const Ptr = usize;
|
const Ptr = usize;
|
||||||
|
pub const Interface = @import("interface").Interface(.{
|
||||||
|
.handle = fn (anytype, Event) ?Event,
|
||||||
|
.render = fn (anytype, *Renderer) anyerror!void,
|
||||||
|
.deinit = fn (anytype) void,
|
||||||
|
}, .{});
|
||||||
|
|
||||||
const VTable = struct {
|
const VTable = struct {
|
||||||
handle: *const fn (this: *WidgetType, event: Event) ?Event,
|
handle: *const fn (this: *WidgetType, event: Event) ?Event,
|
||||||
@@ -88,8 +90,13 @@ pub fn Widget(comptime Event: type, comptime Renderer: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: import and export of `Widget` implementations (with corresponding initialization using `Event`)
|
// import and export of `Widget` implementations
|
||||||
pub const RawText = @import("widget/RawText.zig").Widget(Event, Renderer);
|
pub const RawText = @import("widget/RawText.zig").Widget(Event, Renderer);
|
||||||
pub const Spacer = @import("widget/Spacer.zig").Widget(Event, Renderer);
|
pub const Spacer = @import("widget/Spacer.zig").Widget(Event, Renderer);
|
||||||
};
|
};
|
||||||
|
// test widget implementation satisfies the interface
|
||||||
|
comptime Type.Interface.satisfiedBy(Type);
|
||||||
|
comptime Type.Interface.satisfiedBy(Type.RawText);
|
||||||
|
comptime Type.Interface.satisfiedBy(Type.Spacer);
|
||||||
|
return Type;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user