zigeru/src/commands.zig
Jacob Jonsson 49a6b79fd9
refactor(commands): move user command tests
This commit just shuffles the test for user commands closer to the
definition of the user commands.
2026-03-15 13:51:26 +01:00

144 lines
5 KiB
Zig

const std = @import("std");
const Parser = @import("root.zig").parser.Parser;
/// UserCommand represents the commands that ordinary IRC users can use.
pub const UserCommand = union(enum) {
/// `s/<old-word>/<new-word>/`
substitute: struct { author: []const u8, needle: []const u8, replacement: []const u8, all: bool = false },
/// !help
help: void,
pub fn init_substitute(author: []const u8, needle: []const u8, replacement: []const u8, all: bool) UserCommand {
return .{ .substitute = .{
.author = author,
.needle = needle,
.replacement = replacement,
.all = all,
} };
}
pub fn parse(nick: []const u8, text: []const u8) ?UserCommand {
const original = Parser.init(text);
if (original.consume_str("!help")) |_| {
return .help;
}
if (original.consume_char('s')) |substitute| {
const log_prefix = "parsing substitute command";
var parser = substitute;
parser, const delim = parser.take_char();
if (std.ascii.isAlphanumeric(delim)) {
std.log.debug("{s}: delimiter cannot be a whitespace: \"{s}\"", .{ log_prefix, text });
return null;
}
var result = parser.take_until_char(delim);
if (result == null) {
std.log.debug(
"{s}: cannot find typo, expecting a message on the form 's{}TYPO{}CORRECTION{}', but got {s}",
.{ log_prefix, delim, delim, delim, text },
);
return null;
}
parser, const typo = result.?;
result = parser.consume_char(delim).?.take_until_char(delim);
if (result == null) {
std.log.debug("{s}: missing an ending '/' in \"{s}\"", .{ log_prefix, text });
return null;
}
parser, const correction = result.?;
return .init_substitute(nick, typo, correction, false);
}
return null;
}
};
test "can parse !help successful" {
try std.testing.expectEqual(
UserCommand.help,
UserCommand.parse("jassob", "!help"),
);
}
test "can parse s/hello/world/ successful" {
try std.testing.expectEqualDeep(
UserCommand{ .substitute = .{
.author = "jassob",
.needle = "hello",
.replacement = "world",
.all = false,
} },
UserCommand.parse("jassob", "s/hello/world/"),
);
}
test "can parse s/hello/world and report failure" {
try std.testing.expectEqualDeep(null, UserCommand.parse("jassob", "s/hello/world"));
}
test "can parse s/hello|world| and report failure" {
try std.testing.expectEqualDeep(null, UserCommand.parse("jassob", "s/hello/world"));
}
test "correctly ignores non-messages when trying to parse" {
try std.testing.expectEqualDeep(null, UserCommand.parse("jassob", "Hello, world"));
}
/// AdminCommand are commands useful for debugging zigeru, since they
/// are more spammy than others they are separated and only sent to
/// #eru-admin.
pub const AdminCommand = union(enum) {
backlog: struct { history: u16 },
status: void,
join: struct { channel: []const u8 },
err: struct { message: []const u8 },
pub fn parse(text: []const u8) ?AdminCommand {
const original = Parser.init(text);
if (original.consume_char('!')) |command| {
if (command.consume_str("status")) |_| {
return .status;
}
if (command.consume_str("join")) |join| {
if (join.consume_space()) |channel| {
if (channel.rest[0] != '#') {
return .{ .err = .{ .message = "channels must start with \"#\"" } };
}
return .{ .join = .{ .channel = join.rest } };
}
}
if (command.consume_str("backlog")) |backlog| {
if (backlog.consume_space()) |history| {
const historyOffset = std.fmt.parseInt(u16, history.rest, 10) catch |err| {
std.debug.print("failed to parse int ('{s}') with error: {}\n", .{ history.rest, err });
return null;
};
return .{ .backlog = .{ .history = historyOffset } };
}
}
std.log.debug("unknown command: \"{s}\"", .{command.rest});
}
return null;
}
};
test "parse admin commands" {
const cmd = AdminCommand.parse("!join badchannel") orelse unreachable;
try std.testing.expectEqual(
AdminCommand{ .err = .{ .message = "channels must start with \"#\"" } },
cmd,
);
}
test "parse backlog admin commands" {
const cmd = AdminCommand.parse("!backlog 1") orelse unreachable;
try std.testing.expectEqual(
AdminCommand{ .backlog = .{ .history = 1 } },
cmd,
);
}
test "parse unknown admin commands" {
const cmd = AdminCommand.parse("!history 1");
try std.testing.expectEqual(null, cmd);
}