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 Command = @import("./Command.zig");
|
||||
const CommandIF = @import("./command/root.zig").Interface;
|
||||
|
||||
///
|
||||
const Self = @This();
|
||||
|
||||
///
|
||||
const CommandList = std.ArrayList(Command);
|
||||
const CommandList = std.ArrayList(CommandIF);
|
||||
|
||||
///
|
||||
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) {
|
||||
try self.commands.append(command);
|
||||
} else {
|
||||
@ -74,25 +74,8 @@ pub fn isRedoAvailable(self: *const Self) bool {
|
||||
}
|
||||
|
||||
test {
|
||||
const Mock = struct {
|
||||
const vt = Command.VTable {
|
||||
.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 value1: i32 = 0;
|
||||
var value2: i32 = 0;
|
||||
|
||||
var history = init(std.testing.allocator);
|
||||
defer history.deinit();
|
||||
@ -102,48 +85,50 @@ test {
|
||||
try std.testing.expectError(error.NoCommand, history.undo());
|
||||
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.isRedoAvailable(), false);
|
||||
try std.testing.expectEqual(mock1.count, 1);
|
||||
try std.testing.expectEqual(value1, 1);
|
||||
|
||||
try history.undo();
|
||||
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||
try std.testing.expectEqual(mock1.count, 0);
|
||||
try std.testing.expectEqual(value1, 0);
|
||||
|
||||
try history.redo();
|
||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||
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.isRedoAvailable(), false);
|
||||
try std.testing.expectEqual(mock1.count, 1);
|
||||
try std.testing.expectEqual(mock2.count, 1);
|
||||
try std.testing.expectEqual(value1, 1);
|
||||
try std.testing.expectEqual(value2, 1);
|
||||
|
||||
try history.undo();
|
||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||
try std.testing.expectEqual(mock1.count, 1);
|
||||
try std.testing.expectEqual(mock2.count, 0);
|
||||
try std.testing.expectEqual(value1, 1);
|
||||
try std.testing.expectEqual(value2, 0);
|
||||
|
||||
try history.redo();
|
||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||
try std.testing.expectEqual(mock1.count, 1);
|
||||
try std.testing.expectEqual(mock2.count, 1);
|
||||
try std.testing.expectEqual(value1, 1);
|
||||
try std.testing.expectEqual(value2, 1);
|
||||
|
||||
try history.undo();
|
||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||
try std.testing.expectEqual(mock1.count, 1);
|
||||
try std.testing.expectEqual(mock2.count, 0);
|
||||
try std.testing.expectEqual(value1, 1);
|
||||
try std.testing.expectEqual(value2, 0);
|
||||
|
||||
try history.undo();
|
||||
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||
try std.testing.expectEqual(mock1.count, 0);
|
||||
try std.testing.expectEqual(mock2.count, 0);
|
||||
try std.testing.expectEqual(value1, 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.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.details = try self.alloc.dupeZ(u8, v);
|
||||
return self.details;
|
||||
}
|
||||
|
||||
test {
|
||||
@ -57,15 +55,15 @@ test {
|
||||
try std.testing.expectEqualStrings("", task.summary);
|
||||
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("", task.details);
|
||||
|
||||
_ = try task.setDetails("good afternoon");
|
||||
try task.setDetails("good afternoon");
|
||||
try std.testing.expectEqualStrings("helloworld", task.summary);
|
||||
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("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 Task = @import("./Task.zig");
|
||||
pub const TaskStore = @import("./TaskStore.zig");
|
||||
|
Loading…
x
Reference in New Issue
Block a user