From 899d5fdfc58e8fc98cae9da3c0293294bbd48fcb Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Thu, 18 Sep 2025 11:02:58 -0700 Subject: [PATCH] some cleanup --- sed-lite/src/main.zig | 115 ++++++++++++++++++++++++++++++++---------- sed-lite/test.txt | 5 ++ 2 files changed, 92 insertions(+), 28 deletions(-) create mode 100644 sed-lite/test.txt diff --git a/sed-lite/src/main.zig b/sed-lite/src/main.zig index e20c0ff..315db37 100644 --- a/sed-lite/src/main.zig +++ b/sed-lite/src/main.zig @@ -1,31 +1,47 @@ const std = @import("std"); -const print = std.debug.print; -pub fn main() !void { +pub fn main() !u8 { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); + var stdout_buffer: [1024]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer); + var stderr_writer = std.fs.File.stdout().writer(&.{}); + const stdout = &stdout_writer.interface; + defer stdout.flush() catch {}; + const stderr = &stderr_writer.interface; const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); if (args.len < 2) { - print("Usage: {s} \n", .{args[0]}); - std.process.exit(1); + try stdout.print("Usage: {s} \n", .{args[0]}); + return 1; } const operation = args[1]; const file_path = args[args.len - 1]; if (std.mem.eql(u8, operation, "-sL")) { - if (args.len != 5) { - print("Usage: {s} -sL \"original string\" \"replacement string\" \n", .{args[0]}); - std.process.exit(1); + if (args.len < 5 or args.len % 2 != 1) { + try stderr.print("Usage: {s} -sL <\"original string\" \"replacement string\">... \n", .{args[0]}); + return 2; } - const file = std.fs.cwd().openFile(file_path, .{}) catch |err| { - print("Error opening file '{s}': {}\n", .{ file_path, err }); - std.process.exit(1); + const num_substitutions = (args.len - 3) / 2; + const substitutions = try allocator.alloc(Substitution, num_substitutions); + defer allocator.free(substitutions); + + for (0..num_substitutions) |i| { + substitutions[i] = Substitution{ + .original = args[2 + i * 2], + .replacement = args[3 + i * 2], + }; + } + + const file = std.fs.cwd().openFile(file_path, .{ .mode = .read_only }) catch |err| { + try stderr.print("Error opening file '{s}': {}\n", .{ file_path, err }); + return 1; }; defer file.close(); @@ -35,8 +51,8 @@ pub fn main() !void { defer allocator.free(temp_path); const temp_file = std.fs.cwd().createFile(temp_path, .{}) catch |err| { - print("Error creating temp file: {}\n", .{err}); - std.process.exit(1); + try stderr.print("Error creating temp file: {}\n", .{err}); + return 1; }; defer temp_file.close(); @@ -46,29 +62,40 @@ pub fn main() !void { try substituteLines( &reader, &writer, - args[2], - args[3], + substitutions, ); try writer.flush(); std.fs.cwd().rename(temp_path, file_path) catch |err| { - print("Error moving temp file: {}\n", .{err}); - std.process.exit(1); + try stderr.print("Error moving temp file: {}\n", .{err}); + return 1; }; } else { - print("Error: Unknown operation '{s}'\n", .{operation}); - std.process.exit(1); + try stderr.print("Error: Unknown operation '{s}'\n", .{operation}); + return 1; } + return 0; } -fn substituteLines(reader: *std.Io.Reader, writer: *std.Io.Writer, original: []const u8, replacement: []const u8) !void { +const Substitution = struct { + original: []const u8, + replacement: []const u8, +}; + +fn substituteLines(reader: *std.Io.Reader, writer: *std.Io.Writer, substitutions: []const Substitution) !void { var line_buf: [1024]u8 = undefined; var line = std.Io.Writer.fixed(&line_buf); while ((try reader.streamDelimiterEnding(&line, '\n')) > 0) : (_ = line.consumeAll()) { const line_content = line.buffered(); - if (std.mem.eql(u8, line_content, original)) - try writer.writeAll(replacement) - else + var substituted = false; + for (substitutions) |sub| { + if (std.mem.eql(u8, line_content, sub.original)) { + try writer.writeAll(sub.replacement); + substituted = true; + break; + } + } + if (!substituted) try writer.writeAll(line_content); // Write our \n @@ -86,11 +113,14 @@ test "substitute lines exact match" { var output_buf: [1024]u8 = undefined; var output_stream = std.Io.Writer.fixed(&output_buf); + const substitutions = [_]Substitution{ + .{ .original = "replace_me", .replacement = "new_line" }, + }; + try substituteLines( &input_stream, &output_stream, - "replace_me", - "new_line", + &substitutions, ); const result = output_stream.buffered(); @@ -104,11 +134,14 @@ test "no match found" { var output_buf: [1024]u8 = undefined; var output_stream = std.Io.Writer.fixed(&output_buf); + const substitutions = [_]Substitution{ + .{ .original = "nonexistent", .replacement = "replacement" }, + }; + try substituteLines( &input_stream, &output_stream, - "nonexistent", - "replacement", + &substitutions, ); const result = output_stream.buffer[0..output_stream.end]; @@ -123,11 +156,37 @@ test "partial match not replaced" { var output_buf: [1024]u8 = undefined; var output_stream = std.Io.Writer.fixed(&output_buf); + const substitutions = [_]Substitution{ + .{ .original = "match", .replacement = "replaced" }, + }; + try substituteLines( &input_stream, &output_stream, - "match", - "replaced", + &substitutions, + ); + + const result = output_stream.buffered(); + try std.testing.expectEqualStrings(expected, result); +} + +test "multiple substitutions" { + const test_content = "line1\nreplace_me\nline3\nreplace_also\nline5"; + const expected = "line1\nnew_line\nline3\nalso_new\nline5"; + + var input_stream = std.Io.Reader.fixed(test_content); + var output_buf: [1024]u8 = undefined; + var output_stream = std.Io.Writer.fixed(&output_buf); + + const substitutions = [_]Substitution{ + .{ .original = "replace_me", .replacement = "new_line" }, + .{ .original = "replace_also", .replacement = "also_new" }, + }; + + try substituteLines( + &input_stream, + &output_stream, + &substitutions, ); const result = output_stream.buffered(); diff --git a/sed-lite/test.txt b/sed-lite/test.txt new file mode 100644 index 0000000..16b0b87 --- /dev/null +++ b/sed-lite/test.txt @@ -0,0 +1,5 @@ +line1 +old_text +line3 +another_old +line5