Last active
July 18, 2024 15:14
-
-
Save ant1fact/38faaa371ff3fd225c4bc320eda17d63 to your computer and use it in GitHub Desktop.
Get the last line of a text file in Zig
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
/// Try to get the last line of a text file. The returned line will be trimmed of any new line characters. | |
/// If the file is empty, or if the file only contains new lines, the returned slice will be empty. | |
pub fn getTail(allocator: std.mem.Allocator, file: *std.fs.File) ![]const u8 { | |
var result = std.ArrayList(u8).init(allocator); | |
const step_size: i64 = 4096; // Same as default BufferedReader | |
var read_buffer: [step_size]u8 = undefined; | |
var reader = file.reader(); | |
try file.seekFromEnd(0); | |
var iter_count: usize = 0; | |
outer: while (true) { | |
// Each iteration, expand the capacity of the ArrayList storing the result | |
iter_count += 1; | |
try result.ensureTotalCapacity(iter_count * step_size); | |
// Get the current cursor position | |
const pos = try file.getPos(); | |
// If the position is at 0, there is no more data to read, stop iterating | |
if (pos == 0) break; | |
// While there is data to read, move the cursor back -step_size or -pos, whichever is smaller | |
const seek_amount: i64 = @min(@as(i64, @intCast(pos)), step_size); | |
try file.seekBy(-seek_amount); | |
// Save the current position so we can move the cursor back to it once we have read the current chunk of bytes | |
const pos_backup = try file.getPos(); | |
// Prevent reading more data than the amount the cursor was moved backward by limiting the buffer to seek_amount | |
const buffer_len: usize = @intCast(seek_amount); | |
const bytes_read = try reader.read(read_buffer[0..buffer_len]); | |
// Reset the cursor to the previously stored position | |
try file.seekTo(pos_backup); | |
// Read characters from the read_buffer backwards until a new line is found | |
var i: usize = bytes_read; | |
inner: while (i > 0) { | |
i -= 1; | |
const byte = read_buffer[i]; | |
// Check for new line | |
if (byte == '\n' or byte == '\r') { | |
// Continue looping if the result buffer is empty, i.e. the new line found was at the end of the file | |
if (result.items.len == 0) continue :inner; | |
// If a new line was found and the result buffer was not empty, stop looking for a new line | |
break :outer; | |
} | |
// Keep adding bytes to the result buffer | |
result.appendAssumeCapacity(byte); | |
} | |
} | |
// Since we've written the bytes into the ArrayList in reverse order, we need to reverse it back | |
std.mem.reverse(u8, result.items); | |
return try result.toOwnedSlice(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment