implement Command interface
This commit is contained in:
parent
88252a151b
commit
8263ccad82
@ -1,51 +0,0 @@
|
|||||||
//!
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
///
|
|
||||||
pub const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
pub const Error = error {
|
|
||||||
///
|
|
||||||
AllocationFailure,
|
|
||||||
|
|
||||||
///
|
|
||||||
InvalidState,
|
|
||||||
|
|
||||||
///
|
|
||||||
Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
pub const VTable = struct {
|
|
||||||
///
|
|
||||||
deinit: *const fn(ctx: *anyopaque) void,
|
|
||||||
|
|
||||||
///
|
|
||||||
apply: *const fn(ctx: *anyopaque) Error!void,
|
|
||||||
|
|
||||||
///
|
|
||||||
revert: *const fn(ctx: *anyopaque) Error!void,
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
ctx: *anyopaque,
|
|
||||||
|
|
||||||
///
|
|
||||||
vtable: *const VTable,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn deinit(self: Self) void {
|
|
||||||
self.vtable.deinit(self.ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn apply(self: Self) !void {
|
|
||||||
return self.vtable.apply(self.ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn revert(self: Self) !void {
|
|
||||||
return self.vtable.revert(self.ctx);
|
|
||||||
}
|
|
@ -1,12 +1,12 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const Command = @import("./Command.zig");
|
const CommandIF = @import("./command/root.zig").Interface;
|
||||||
|
|
||||||
///
|
///
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
///
|
///
|
||||||
const CommandList = std.ArrayList(Command);
|
const CommandList = std.ArrayList(CommandIF);
|
||||||
|
|
||||||
///
|
///
|
||||||
commands: CommandList,
|
commands: CommandList,
|
||||||
@ -31,7 +31,7 @@ pub fn deinit(self: *Self) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn exec(self: *Self, command: Command) !void {
|
pub fn exec(self: *Self, command: CommandIF) !void {
|
||||||
if (self.head >= self.commands.items.len) {
|
if (self.head >= self.commands.items.len) {
|
||||||
try self.commands.append(command);
|
try self.commands.append(command);
|
||||||
} else {
|
} else {
|
||||||
@ -74,25 +74,8 @@ pub fn isRedoAvailable(self: *const Self) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
const Mock = struct {
|
var value1: i32 = 0;
|
||||||
const vt = Command.VTable {
|
var value2: i32 = 0;
|
||||||
.deinit = deinit_,
|
|
||||||
.apply = apply_,
|
|
||||||
.revert = revert_,
|
|
||||||
};
|
|
||||||
count: i32 = 0,
|
|
||||||
fn deinit_(_: *anyopaque) void { }
|
|
||||||
fn apply_(ctx: *anyopaque) Command.Error!void {
|
|
||||||
var self: *@This() = @ptrCast(@alignCast(ctx));
|
|
||||||
self.count += 1;
|
|
||||||
}
|
|
||||||
fn revert_(ctx: *anyopaque) Command.Error!void {
|
|
||||||
var self: *@This() = @ptrCast(@alignCast(ctx));
|
|
||||||
self.count -= 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var mock1 = Mock {};
|
|
||||||
var mock2 = Mock {};
|
|
||||||
|
|
||||||
var history = init(std.testing.allocator);
|
var history = init(std.testing.allocator);
|
||||||
defer history.deinit();
|
defer history.deinit();
|
||||||
@ -102,48 +85,50 @@ test {
|
|||||||
try std.testing.expectError(error.NoCommand, history.undo());
|
try std.testing.expectError(error.NoCommand, history.undo());
|
||||||
try std.testing.expectError(error.NoCommand, history.redo());
|
try std.testing.expectError(error.NoCommand, history.redo());
|
||||||
|
|
||||||
try history.exec(Command { .ctx = &mock1, .vtable = &Mock.vt, });
|
try history.exec(
|
||||||
|
try CommandIF.make(std.testing.allocator, CommandIF.Mock { .target = &value1, }));
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||||
try std.testing.expectEqual(mock1.count, 1);
|
try std.testing.expectEqual(value1, 1);
|
||||||
|
|
||||||
try history.undo();
|
try history.undo();
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||||
try std.testing.expectEqual(mock1.count, 0);
|
try std.testing.expectEqual(value1, 0);
|
||||||
|
|
||||||
try history.redo();
|
try history.redo();
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||||
try std.testing.expectEqual(mock1.count, 1);
|
try std.testing.expectEqual(value1, 1);
|
||||||
|
|
||||||
try history.exec(Command { .ctx = &mock2, .vtable = &Mock.vt, });
|
try history.exec(
|
||||||
|
try CommandIF.make(std.testing.allocator, CommandIF.Mock { .target = &value2, }));
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||||
try std.testing.expectEqual(mock1.count, 1);
|
try std.testing.expectEqual(value1, 1);
|
||||||
try std.testing.expectEqual(mock2.count, 1);
|
try std.testing.expectEqual(value2, 1);
|
||||||
|
|
||||||
try history.undo();
|
try history.undo();
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||||
try std.testing.expectEqual(mock1.count, 1);
|
try std.testing.expectEqual(value1, 1);
|
||||||
try std.testing.expectEqual(mock2.count, 0);
|
try std.testing.expectEqual(value2, 0);
|
||||||
|
|
||||||
try history.redo();
|
try history.redo();
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||||
try std.testing.expectEqual(mock1.count, 1);
|
try std.testing.expectEqual(value1, 1);
|
||||||
try std.testing.expectEqual(mock2.count, 1);
|
try std.testing.expectEqual(value2, 1);
|
||||||
|
|
||||||
try history.undo();
|
try history.undo();
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||||
try std.testing.expectEqual(mock1.count, 1);
|
try std.testing.expectEqual(value1, 1);
|
||||||
try std.testing.expectEqual(mock2.count, 0);
|
try std.testing.expectEqual(value2, 0);
|
||||||
|
|
||||||
try history.undo();
|
try history.undo();
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||||
try std.testing.expectEqual(mock1.count, 0);
|
try std.testing.expectEqual(value1, 0);
|
||||||
try std.testing.expectEqual(mock2.count, 0);
|
try std.testing.expectEqual(value2, 0);
|
||||||
}
|
}
|
||||||
|
@ -38,16 +38,14 @@ pub fn deinit(self: *Self) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn setSummary(self: *Self, v: []const u8) ![:0]const u8 {
|
pub fn setSummary(self: *Self, v: []const u8) !void {
|
||||||
self.alloc.free(self.summary);
|
self.alloc.free(self.summary);
|
||||||
self.summary = try self.alloc.dupeZ(u8, v);
|
self.summary = try self.alloc.dupeZ(u8, v);
|
||||||
return self.summary;
|
|
||||||
}
|
}
|
||||||
///
|
///
|
||||||
pub fn setDetails(self: *Self, v: []const u8) ![:0]const u8 {
|
pub fn setDetails(self: *Self, v: []const u8) !void {
|
||||||
self.alloc.free(self.details);
|
self.alloc.free(self.details);
|
||||||
self.details = try self.alloc.dupeZ(u8, v);
|
self.details = try self.alloc.dupeZ(u8, v);
|
||||||
return self.details;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@ -57,15 +55,15 @@ test {
|
|||||||
try std.testing.expectEqualStrings("", task.summary);
|
try std.testing.expectEqualStrings("", task.summary);
|
||||||
try std.testing.expectEqualStrings("", task.details);
|
try std.testing.expectEqualStrings("", task.details);
|
||||||
|
|
||||||
_ = try task.setSummary("helloworld");
|
try task.setSummary("helloworld");
|
||||||
try std.testing.expectEqualStrings("helloworld", task.summary);
|
try std.testing.expectEqualStrings("helloworld", task.summary);
|
||||||
try std.testing.expectEqualStrings("", task.details);
|
try std.testing.expectEqualStrings("", task.details);
|
||||||
|
|
||||||
_ = try task.setDetails("good afternoon");
|
try task.setDetails("good afternoon");
|
||||||
try std.testing.expectEqualStrings("helloworld", task.summary);
|
try std.testing.expectEqualStrings("helloworld", task.summary);
|
||||||
try std.testing.expectEqualStrings("good afternoon", task.details);
|
try std.testing.expectEqualStrings("good afternoon", task.details);
|
||||||
|
|
||||||
_ = try task.setSummary("goodbye");
|
try task.setSummary("goodbye");
|
||||||
try std.testing.expectEqualStrings("goodbye", task.summary);
|
try std.testing.expectEqualStrings("goodbye", task.summary);
|
||||||
try std.testing.expectEqualStrings("good afternoon", task.details);
|
try std.testing.expectEqualStrings("good afternoon", task.details);
|
||||||
}
|
}
|
||||||
|
134
src/hncore/command/Interface.zig
Normal file
134
src/hncore/command/Interface.zig
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
//!
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const Self = @This();
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const Error = error {
|
||||||
|
///
|
||||||
|
OutOfMemory,
|
||||||
|
|
||||||
|
///
|
||||||
|
InvalidState,
|
||||||
|
|
||||||
|
///
|
||||||
|
Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const VTable = struct {
|
||||||
|
///
|
||||||
|
deinit: *const fn(ctx: *anyopaque) void,
|
||||||
|
|
||||||
|
///
|
||||||
|
apply: *const fn(ctx: *anyopaque) Error!void,
|
||||||
|
|
||||||
|
///
|
||||||
|
revert: *const fn(ctx: *anyopaque) Error!void,
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const Mock = struct {
|
||||||
|
target : ?*i32 = null,
|
||||||
|
destroyed: ?*bool = null,
|
||||||
|
|
||||||
|
pub fn deinit(self: *@This(), _: anytype) void {
|
||||||
|
if (self.destroyed) |ptr| {
|
||||||
|
ptr.* = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn apply(self: *@This(), _: anytype) !void {
|
||||||
|
if (self.target) |ptr| {
|
||||||
|
ptr.* += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn revert(self: *@This(), _: anytype) !void {
|
||||||
|
if (self.target) |ptr| {
|
||||||
|
ptr.* -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
ctx: *anyopaque,
|
||||||
|
|
||||||
|
///
|
||||||
|
vtable: *const VTable,
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
self.vtable.deinit(self.ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn apply(self: Self) !void {
|
||||||
|
return self.vtable.apply(self.ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn revert(self: Self) !void {
|
||||||
|
return self.vtable.revert(self.ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn make(alloc: std.mem.Allocator, ctx: anytype) !Self {
|
||||||
|
const Target = @TypeOf(ctx);
|
||||||
|
const Wrapper = struct {
|
||||||
|
const vtable = VTable {
|
||||||
|
.deinit = @This().del,
|
||||||
|
.apply = @This().apply,
|
||||||
|
.revert = @This().revert,
|
||||||
|
};
|
||||||
|
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
ctx : Target,
|
||||||
|
|
||||||
|
fn new(alloc2: std.mem.Allocator, ctx2: Target) !*@This() {
|
||||||
|
const self = try alloc2.create(@This());
|
||||||
|
self.* = .{
|
||||||
|
.alloc = alloc2,
|
||||||
|
.ctx = ctx2,
|
||||||
|
};
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
fn del(selfp: *anyopaque) void {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(selfp));
|
||||||
|
self.ctx.deinit(self.alloc);
|
||||||
|
self.alloc.destroy(self);
|
||||||
|
}
|
||||||
|
fn apply(selfp: *anyopaque) Error!void {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(selfp));
|
||||||
|
return self.ctx.apply(self.alloc);
|
||||||
|
}
|
||||||
|
fn revert(selfp: *anyopaque) Error!void {
|
||||||
|
const self: *@This() = @ptrCast(@alignCast(selfp));
|
||||||
|
return self.ctx.revert(self.alloc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Self {
|
||||||
|
.ctx = try Wrapper.new(alloc, ctx),
|
||||||
|
.vtable = &Wrapper.vtable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
var value: i32 = 0;
|
||||||
|
var destroyed = false;
|
||||||
|
{
|
||||||
|
const cmd = try Self.make(std.testing.allocator, Mock {
|
||||||
|
.target = &value,
|
||||||
|
.destroyed = &destroyed,
|
||||||
|
});
|
||||||
|
defer cmd.deinit();
|
||||||
|
|
||||||
|
try cmd.apply();
|
||||||
|
try std.testing.expectEqual(value, 1);
|
||||||
|
|
||||||
|
try cmd.revert();
|
||||||
|
try std.testing.expectEqual(value, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try std.testing.expect(destroyed);
|
||||||
|
}
|
7
src/hncore/command/root.zig
Normal file
7
src/hncore/command/root.zig
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
pub const Interface = @import("./Interface.zig");
|
||||||
|
|
||||||
|
pub const task = @import("./task.zig");
|
||||||
|
|
||||||
|
test {
|
||||||
|
@import("std").testing.refAllDecls(@This());
|
||||||
|
}
|
67
src/hncore/command/task.zig
Normal file
67
src/hncore/command/task.zig
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Interface = @import("./Interface.zig");
|
||||||
|
|
||||||
|
const Task = @import("../Task.zig");
|
||||||
|
|
||||||
|
///
|
||||||
|
const SetSummary = struct {
|
||||||
|
///
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
///
|
||||||
|
target: *Task,
|
||||||
|
|
||||||
|
///
|
||||||
|
summary: [:0]const u8,
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn init(alloc: std.mem.Allocator, task: *Task, summary: []const u8) !Interface {
|
||||||
|
const summaryDup = try alloc.dupeZ(u8, summary);
|
||||||
|
errdefer alloc.free(summaryDup);
|
||||||
|
return try Interface.make(alloc, Self {
|
||||||
|
.target = task,
|
||||||
|
.summary = summaryDup,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn apply(self: *Self, alloc: std.mem.Allocator) Interface.Error!void {
|
||||||
|
const old = try alloc.dupeZ(u8, self.target.summary);
|
||||||
|
errdefer alloc.free(old);
|
||||||
|
|
||||||
|
try self.target.setSummary(self.summary);
|
||||||
|
alloc.free(self.summary);
|
||||||
|
|
||||||
|
self.summary = old;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn revert(self: *Self, alloc: std.mem.Allocator) Interface.Error!void {
|
||||||
|
return self.apply(alloc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
const TaskStore = @import("../TaskStore.zig");
|
||||||
|
|
||||||
|
var tasks = TaskStore.init(std.testing.allocator);
|
||||||
|
defer tasks.deinit();
|
||||||
|
|
||||||
|
var task = try tasks.add();
|
||||||
|
try task.setSummary("helloworld");
|
||||||
|
|
||||||
|
var sut = try SetSummary.init(std.testing.allocator, task, "goodbye");
|
||||||
|
defer sut.deinit();
|
||||||
|
|
||||||
|
try sut.apply();
|
||||||
|
try std.testing.expectEqualStrings("goodbye", task.summary);
|
||||||
|
|
||||||
|
try sut.revert();
|
||||||
|
try std.testing.expectEqualStrings("helloworld", task.summary);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub const Command = @import("./Command.zig");
|
pub const command = @import("./command/root.zig");
|
||||||
|
|
||||||
pub const CommandHistory = @import("./CommandHistory.zig");
|
pub const CommandHistory = @import("./CommandHistory.zig");
|
||||||
pub const Task = @import("./Task.zig");
|
pub const Task = @import("./Task.zig");
|
||||||
pub const TaskStore = @import("./TaskStore.zig");
|
pub const TaskStore = @import("./TaskStore.zig");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user