we will need to patch utilities.c. Quick program for to do this
This commit is contained in:
parent
ffdf1bd0f1
commit
fb9bae8525
4 changed files with 208 additions and 0 deletions
39
sed-lite/README.md
Normal file
39
sed-lite/README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# sed-lite
|
||||
|
||||
A minimal command line program for in-place file editing with exact line substitution.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
sed-lite -sL "original string" "replacement string" <file>
|
||||
```
|
||||
|
||||
## Operations
|
||||
|
||||
- `-sL` (substitute line): Replaces entire lines that exactly match the original string
|
||||
|
||||
## Features
|
||||
|
||||
- In-place editing using temporary files
|
||||
- Exact line matching (no regex or wildcards)
|
||||
- Safe atomic file replacement
|
||||
- Comprehensive error handling
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
zig build
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
zig build test
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
# Replace all lines containing exactly "old text" with "new text"
|
||||
./sed-lite -sL "old text" "new text" myfile.txt
|
||||
```
|
27
sed-lite/build.zig
Normal file
27
sed-lite/build.zig
Normal file
|
@ -0,0 +1,27 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "sed-lite",
|
||||
.root_module = b.addModule("main", .{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
b.installArtifact(exe);
|
||||
|
||||
const tests = b.addTest(.{
|
||||
.root_module = b.addModule("test", .{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
|
||||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&b.addRunArtifact(tests).step);
|
||||
}
|
7
sed-lite/build.zig.zon
Normal file
7
sed-lite/build.zig.zon
Normal file
|
@ -0,0 +1,7 @@
|
|||
.{
|
||||
.name = .sed_lite,
|
||||
.version = "0.0.1",
|
||||
.minimum_zig_version = "0.15.1",
|
||||
.paths = .{""},
|
||||
.fingerprint = 0xf728ae3f808521e3,
|
||||
}
|
135
sed-lite/src/main.zig
Normal file
135
sed-lite/src/main.zig
Normal file
|
@ -0,0 +1,135 @@
|
|||
const std = @import("std");
|
||||
const print = std.debug.print;
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
const args = try std.process.argsAlloc(allocator);
|
||||
defer std.process.argsFree(allocator, args);
|
||||
|
||||
if (args.len < 2) {
|
||||
print("Usage: {s} <operation> <operation args...> <file>\n", .{args[0]});
|
||||
std.process.exit(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\" <file>\n", .{args[0]});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const file = std.fs.cwd().openFile(file_path, .{}) catch |err| {
|
||||
print("Error opening file '{s}': {}\n", .{ file_path, err });
|
||||
std.process.exit(1);
|
||||
};
|
||||
defer file.close();
|
||||
|
||||
var buffer: [4096]u8 = undefined;
|
||||
|
||||
const temp_path = try std.fmt.allocPrint(allocator, "{s}.tmp", .{file_path});
|
||||
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);
|
||||
};
|
||||
defer temp_file.close();
|
||||
|
||||
var output_buffer: [4096]u8 = undefined;
|
||||
var reader = file.reader(&buffer).interface;
|
||||
var writer = temp_file.writer(&output_buffer).interface;
|
||||
try substituteLines(
|
||||
&reader,
|
||||
&writer,
|
||||
args[2],
|
||||
args[3],
|
||||
);
|
||||
try writer.flush();
|
||||
std.fs.cwd().rename(temp_path, file_path) catch |err| {
|
||||
print("Error moving temp file: {}\n", .{err});
|
||||
std.process.exit(1);
|
||||
};
|
||||
} else {
|
||||
print("Error: Unknown operation '{s}'\n", .{operation});
|
||||
std.process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn substituteLines(reader: *std.Io.Reader, writer: *std.Io.Writer, original: []const u8, replacement: []const u8) !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
|
||||
try writer.writeAll(line_content);
|
||||
|
||||
// Write our \n
|
||||
if (reader.takeByte()) |b|
|
||||
try writer.writeByte(b)
|
||||
else |_| {} // end of stream
|
||||
}
|
||||
}
|
||||
|
||||
test "substitute lines exact match" {
|
||||
const test_content = "line1\nreplace_me\nline3\nreplace_me\nline5";
|
||||
const expected = "line1\nnew_line\nline3\nnew_line\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);
|
||||
|
||||
try substituteLines(
|
||||
&input_stream,
|
||||
&output_stream,
|
||||
"replace_me",
|
||||
"new_line",
|
||||
);
|
||||
|
||||
const result = output_stream.buffered();
|
||||
try std.testing.expectEqualStrings(expected, result);
|
||||
}
|
||||
|
||||
test "no match found" {
|
||||
const test_content = "line1\nline2\nline3";
|
||||
|
||||
var input_stream = std.Io.Reader.fixed(test_content);
|
||||
var output_buf: [1024]u8 = undefined;
|
||||
var output_stream = std.Io.Writer.fixed(&output_buf);
|
||||
|
||||
try substituteLines(
|
||||
&input_stream,
|
||||
&output_stream,
|
||||
"nonexistent",
|
||||
"replacement",
|
||||
);
|
||||
|
||||
const result = output_stream.buffer[0..output_stream.end];
|
||||
try std.testing.expectEqualStrings(test_content, result);
|
||||
}
|
||||
|
||||
test "partial match not replaced" {
|
||||
const test_content = "prefix_match_suffix\nmatch\nline3";
|
||||
const expected = "prefix_match_suffix\nreplaced\nline3";
|
||||
|
||||
var input_stream = std.Io.Reader.fixed(test_content);
|
||||
var output_buf: [1024]u8 = undefined;
|
||||
var output_stream = std.Io.Writer.fixed(&output_buf);
|
||||
|
||||
try substituteLines(
|
||||
&input_stream,
|
||||
&output_stream,
|
||||
"match",
|
||||
"replaced",
|
||||
);
|
||||
|
||||
const result = output_stream.buffered();
|
||||
try std.testing.expectEqualStrings(expected, result);
|
||||
}
|
Loading…
Add table
Reference in a new issue