Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408const std = @import("std");const vaxis = @import("vaxis");const irc = @import("irc.zig");
const mem = std.mem;
const ColorState = enum { ground, fg, bg,};
const LinkState = enum { h, t1, t2, p, s, colon, slash, consume,};
/// generate vaxis.Segments for the message contentpub fn message(segments: *std.ArrayList(vaxis.Segment), user: *const irc.User, msg: irc.Message) !void { var iter = msg.paramIterator(); // skip the first param, this is the receiver of the message _ = iter.next() orelse return error.InvalidMessage; const content = iter.next() orelse return error.InvalidMessage;
var start: usize = 0; var i: usize = 0; var style: vaxis.Style = .{}; while (i < content.len) : (i += 1) { const b = content[i]; switch (b) { 0x01 => { if (i == 0 and content.len > 7 and mem.startsWith(u8, content[1..], "ACTION")) { style.italic = true; const user_style: vaxis.Style = .{ .fg = user.color, .italic = true, }; try segments.append(.{ .text = user.nick, .style = user_style, }); i += 6; // "ACTION" } else { try segments.append(.{ .text = content[start..i], .style = style, }); } start = i + 1; }, 0x02 => { if (i > start) { try segments.append(.{ .text = content[start..i], .style = style, }); } style.bold = !style.bold; start = i + 1; }, 0x03 => { if (i > start) { try segments.append(.{ .text = content[start..i], .style = style, }); } i += 1; var state: ColorState = .ground; var fg_idx: ?u8 = null; var bg_idx: ?u8 = null; while (i < content.len) : (i += 1) { const d = content[i]; switch (state) { .ground => { switch (d) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { state = .fg; fg_idx = d - '0'; }, else => { style.fg = .default; style.bg = .default; start = i; break; }, } }, .fg => { switch (d) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { const fg = fg_idx orelse 0; if (fg > 9) { style.fg = irc.toVaxisColor(fg); start = i; break; } else { fg_idx = fg * 10 + (d - '0'); } }, else => { if (fg_idx) |fg| { style.fg = irc.toVaxisColor(fg); start = i; } if (d == ',') state = .bg else break; }, } }, .bg => { switch (d) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { const bg = bg_idx orelse 0; if (i - start == 2) { style.bg = irc.toVaxisColor(bg); start = i; break; } else { bg_idx = bg * 10 + (d - '0'); } }, else => { if (bg_idx) |bg| { style.bg = irc.toVaxisColor(bg); start = i; } break; }, } }, } } }, 0x0F => { if (i > start) { try segments.append(.{ .text = content[start..i], .style = style, }); } style = .{}; start = i + 1; }, 0x16 => { if (i > start) { try segments.append(.{ .text = content[start..i], .style = style, }); } style.reverse = !style.reverse; start = i + 1; }, 0x1D => { if (i > start) { try segments.append(.{ .text = content[start..i], .style = style, }); } style.italic = !style.italic; start = i + 1; }, 0x1E => { if (i > start) { try segments.append(.{ .text = content[start..i], .style = style, }); } style.strikethrough = !style.strikethrough; start = i + 1; }, 0x1F => { if (i > start) { try segments.append(.{ .text = content[start..i], .style = style, }); }
style.ul_style = if (style.ul_style == .off) .single else .off; start = i + 1; }, else => { if (b == 'h') { var state: LinkState = .h; const h_start = i; // consume until a space or EOF i += 1; while (i < content.len) : (i += 1) { const b1 = content[i]; switch (state) { .h => { if (b1 == 't') state = .t1 else break; }, .t1 => { if (b1 == 't') state = .t2 else break; }, .t2 => { if (b1 == 'p') state = .p else break; }, .p => { if (b1 == 's') state = .s else if (b1 == ':') state = .colon else break; }, .s => { if (b1 == ':') state = .colon else break; }, .colon => { if (b1 == '/') state = .slash else break; }, .slash => { if (b1 == '/') { state = .consume; if (h_start > start) { try segments.append(.{ .text = content[start..h_start], .style = style, }); } start = h_start; } else break; }, .consume => { switch (b1) { 0x00...0x20, 0x7F => { try segments.append(.{ .text = content[h_start..i], .style = .{ .fg = .{ .index = 4 }, }, .link = .{ .uri = content[h_start..i], }, }); start = i; // backup one i -= 1; break; }, else => { if (i == content.len - 1) { try segments.append(.{ .text = content[h_start..], .style = .{ .fg = .{ .index = 4 }, }, .link = .{ .uri = content[h_start..], }, }); return; } }, } }, } } } }, } } if (start < i and start < content.len) { try segments.append(.{ .text = content[start..], .style = style, }); }}
test "format.zig: no format" { const user: irc.User = .{ .nick = "rockorager" }; const msg: irc.Message = .{ .bytes = "PRIVMSG #comlink :foo" };
var list = std.ArrayList(vaxis.Segment).init(std.testing.allocator); defer list.deinit(); try message(&list, &user, msg); try std.testing.expectEqual(1, list.items.len); const expected: vaxis.Segment = .{ .text = "foo" }; try std.testing.expectEqualDeep(expected, list.items[0]);}
test "format.zig: bold" { const user: irc.User = .{ .nick = "rockorager" }; const msg: irc.Message = .{ .bytes = "PRIVMSG #comlink :\x02foo\x02" };
var list = std.ArrayList(vaxis.Segment).init(std.testing.allocator); defer list.deinit(); try message(&list, &user, msg); try std.testing.expectEqual(1, list.items.len); const expected: vaxis.Segment = .{ .text = "foo", .style = .{ .bold = true } }; try std.testing.expectEqualDeep(expected, list.items[0]);}
test "format.zig: italic" { const user: irc.User = .{ .nick = "rockorager" }; const msg: irc.Message = .{ .bytes = "PRIVMSG #comlink :\x1dfoo\x1d" };
var list = std.ArrayList(vaxis.Segment).init(std.testing.allocator); defer list.deinit(); try message(&list, &user, msg); try std.testing.expectEqual(1, list.items.len); const expected: vaxis.Segment = .{ .text = "foo", .style = .{ .italic = true } }; try std.testing.expectEqualDeep(expected, list.items[0]);}
test "format.zig: strikethrough, reverse, underline" { const user: irc.User = .{ .nick = "rockorager" }; const msg: irc.Message = .{ .bytes = "PRIVMSG #comlink :\x16foo\x16\x1Dbar\x1D\x1Ebaz\x1E\x1Ffoo\x1F", };
var list = std.ArrayList(vaxis.Segment).init(std.testing.allocator); defer list.deinit(); try message(&list, &user, msg); const expected: []const vaxis.Segment = &.{ .{ .text = "foo", .style = .{ .reverse = true } }, .{ .text = "bar", .style = .{ .italic = true } }, .{ .text = "baz", .style = .{ .strikethrough = true } }, .{ .text = "foo", .style = .{ .ul_style = .single } }, }; try std.testing.expectEqual(expected.len, list.items.len); for (expected, 0..) |seg, i| { try std.testing.expectEqualDeep(seg, list.items[i]); }}
test "format.zig: format without closer" { const user: irc.User = .{ .nick = "rockorager" }; const msg: irc.Message = .{ .bytes = "PRIVMSG #comlink :\x16foo\x16\x1Dbar\x1D\x1Ebaz\x1E\x1Ffoo", };
var list = std.ArrayList(vaxis.Segment).init(std.testing.allocator); defer list.deinit(); try message(&list, &user, msg); const expected: []const vaxis.Segment = &.{ .{ .text = "foo", .style = .{ .reverse = true } }, .{ .text = "bar", .style = .{ .italic = true } }, .{ .text = "baz", .style = .{ .strikethrough = true } }, .{ .text = "foo", .style = .{ .ul_style = .single } }, }; try std.testing.expectEqual(expected.len, list.items.len); for (expected, 0..) |seg, i| { try std.testing.expectEqualDeep(seg, list.items[i]); }}
test "format.zig: hyperlink" { const user: irc.User = .{ .nick = "rockorager" }; const msg: irc.Message = .{ .bytes = "PRIVMSG #comlink :https://example.org", };
var list = std.ArrayList(vaxis.Segment).init(std.testing.allocator); defer list.deinit(); try message(&list, &user, msg); const expected: []const vaxis.Segment = &.{ .{ .text = "https://example.org", .style = .{ .fg = .{ .index = 4 } }, .link = .{ .uri = "https://example.org" }, }, }; try std.testing.expectEqual(expected.len, list.items.len); for (expected, 0..) |seg, i| { try std.testing.expectEqualDeep(seg, list.items[i]); }}
test "format.zig: more than hyperlink" { const user: irc.User = .{ .nick = "rockorager" }; const msg: irc.Message = .{ .bytes = "PRIVMSG #comlink :look https://example.org here", };
var list = std.ArrayList(vaxis.Segment).init(std.testing.allocator); defer list.deinit(); try message(&list, &user, msg); const expected: []const vaxis.Segment = &.{ .{ .text = "look " }, .{ .text = "https://example.org", .style = .{ .fg = .{ .index = 4 } }, .link = .{ .uri = "https://example.org" }, }, .{ .text = " here" }, }; try std.testing.expectEqual(expected.len, list.items.len); for (expected, 0..) |seg, i| { try std.testing.expectEqualDeep(seg, list.items[i]); }}