const std = @import("std"); const COLOR_RED = "\x1b[0;31m"; const COLOR_RESET = "\x1b[0m"; fn print_usage(exec: [*:0]u8) void { std.debug.print("Usage: {s} FILE PATTERN\n", .{exec}); } fn string_contains_substring(allocator: std.mem.Allocator, string: []u8, substring: []u8) !std.ArrayList([2]usize) { var list = std.ArrayList([2]usize).init(allocator); var start: ?usize = null; var substring_index: usize = 0; for (string, 0..) |c, i| { if (c == substring[substring_index]) { substring_index += 1; if (substring_index == substring.len) { try list.append(.{ start orelse i, i }); start = null; substring_index = 0; continue; } if (start == null) { start = i; } } else if (start != null) { start = null; substring_index = 0; } } return list; } pub fn main() !void { if (std.os.argv.len != 3) { print_usage(std.os.argv[0]); std.process.exit(1); unreachable; } const stdout = std.io.getStdOut().writer(); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const allocator = gpa.allocator(); defer { const status = gpa.deinit(); if (status == .leak) @panic("there are leaks"); } const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); const file = try std.fs.cwd().openFile(args[1], .{}); defer file.close(); const reader = file.reader(); while (true) { const line = reader.readUntilDelimiterAlloc(allocator, '\n', 1024 * 1024) catch break; defer allocator.free(line); const contains = try string_contains_substring(allocator, line, args[2]); defer contains.deinit(); if (contains.items.len == 0) { try stdout.print(" {s}\n", .{line}); } else { try stdout.print("> ", .{}); var previous: usize = 0; for (contains.items) |element| { try stdout.print("{s}{s}{s}{s}", .{ line[previous..element[0]], COLOR_RED, line[element[0] .. element[1] + 1], COLOR_RESET }); previous = element[1] + 1; } try stdout.print("{s}\n", .{line[previous..]}); } } }