mirror of
https://github.com/ziglang/zig.git
synced 2025-12-08 15:23:14 +00:00
210 lines
5.7 KiB
Zig
210 lines
5.7 KiB
Zig
const std = @import("../std.zig");
|
|
const assert = std.debug.assert;
|
|
const Writer = @This();
|
|
|
|
context: ?*anyopaque,
|
|
vtable: *const VTable,
|
|
|
|
pub const VTable = struct {
|
|
/// Each slice in `data` is written in order.
|
|
///
|
|
/// `data.len` must be greater than zero, and the last element of `data` is
|
|
/// special. It is repeated as necessary so that it is written `splat`
|
|
/// number of times.
|
|
///
|
|
/// Number of bytes actually written is returned.
|
|
///
|
|
/// Number of bytes returned may be zero, which does not mean
|
|
/// end-of-stream. A subsequent call may return nonzero, or may signal end
|
|
/// of stream via an error.
|
|
writeSplat: *const fn (ctx: ?*anyopaque, data: []const []const u8, splat: usize) Result,
|
|
|
|
/// Writes contents from an open file. `headers` are written first, then `len`
|
|
/// bytes of `file` starting from `offset`, then `trailers`.
|
|
///
|
|
/// Number of bytes actually written is returned, which may lie within
|
|
/// headers, the file, trailers, or anywhere in between.
|
|
///
|
|
/// Number of bytes returned may be zero, which does not mean
|
|
/// end-of-stream. A subsequent call may return nonzero, or may signal end
|
|
/// of stream via an error.
|
|
writeFile: *const fn (
|
|
ctx: ?*anyopaque,
|
|
file: std.fs.File,
|
|
offset: Offset,
|
|
/// When zero, it means copy until the end of the file is reached.
|
|
len: FileLen,
|
|
/// Headers and trailers must be passed together so that in case `len` is
|
|
/// zero, they can be forwarded directly to `VTable.writev`.
|
|
headers_and_trailers: []const []const u8,
|
|
headers_len: usize,
|
|
) Result,
|
|
|
|
pub const eof: VTable = .{
|
|
.writeSplat = eof_writeSplat,
|
|
.writeFile = eof_writeFile,
|
|
};
|
|
};
|
|
|
|
pub const Result = struct {
|
|
/// Even when a failure occurs, `len` may be nonzero, and `end` may be
|
|
/// true.
|
|
err: anyerror!void = {},
|
|
/// Number of bytes that were transferred. When an error occurs, ideally
|
|
/// this will be zero, but may not always be the case.
|
|
len: usize = 0,
|
|
/// Indicates end of stream.
|
|
end: bool = false,
|
|
};
|
|
|
|
pub const Offset = enum(u64) {
|
|
/// Indicates to read the file as a stream.
|
|
none = std.math.maxInt(u64),
|
|
_,
|
|
|
|
pub fn init(integer: u64) Offset {
|
|
const result: Offset = @enumFromInt(integer);
|
|
assert(result != .none);
|
|
return result;
|
|
}
|
|
|
|
pub fn toInt(o: Offset) ?u64 {
|
|
if (o == .none) return null;
|
|
return @intFromEnum(o);
|
|
}
|
|
};
|
|
|
|
pub const FileLen = enum(u64) {
|
|
zero = 0,
|
|
entire_file = std.math.maxInt(u64),
|
|
_,
|
|
|
|
pub fn init(integer: u64) FileLen {
|
|
const result: FileLen = @enumFromInt(integer);
|
|
assert(result != .entire_file);
|
|
return result;
|
|
}
|
|
|
|
pub fn int(len: FileLen) u64 {
|
|
return @intFromEnum(len);
|
|
}
|
|
};
|
|
|
|
pub fn writev(w: Writer, data: []const []const u8) Result {
|
|
return w.vtable.writeSplat(w.context, data, 1);
|
|
}
|
|
|
|
pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) Result {
|
|
return w.vtable.writeSplat(w.context, data, splat);
|
|
}
|
|
|
|
pub fn writeFile(
|
|
w: Writer,
|
|
file: std.fs.File,
|
|
offset: u64,
|
|
len: FileLen,
|
|
headers_and_trailers: []const []const u8,
|
|
headers_len: usize,
|
|
) Result {
|
|
return w.vtable.writeFile(w.context, file, offset, len, headers_and_trailers, headers_len);
|
|
}
|
|
|
|
pub fn unimplemented_writeFile(
|
|
context: ?*anyopaque,
|
|
file: std.fs.File,
|
|
offset: u64,
|
|
len: FileLen,
|
|
headers_and_trailers: []const []const u8,
|
|
headers_len: usize,
|
|
) Result {
|
|
_ = context;
|
|
_ = file;
|
|
_ = offset;
|
|
_ = len;
|
|
_ = headers_and_trailers;
|
|
_ = headers_len;
|
|
return .{ .err = error.Unimplemented };
|
|
}
|
|
|
|
pub fn buffered(w: Writer, buffer: []u8) std.io.BufferedWriter {
|
|
return .{
|
|
.buffer = .initBuffer(buffer),
|
|
.unbuffered_writer = w,
|
|
};
|
|
}
|
|
|
|
pub fn unbuffered(w: Writer) std.io.BufferedWriter {
|
|
return buffered(w, &.{});
|
|
}
|
|
|
|
/// A `Writer` that discards all data.
|
|
pub const @"null": Writer = .{
|
|
.context = undefined,
|
|
.vtable = &.{
|
|
.writeSplat = null_writeSplat,
|
|
.writeFile = null_writeFile,
|
|
},
|
|
};
|
|
|
|
fn null_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Result {
|
|
_ = context;
|
|
const headers = data[0 .. data.len - 1];
|
|
const pattern = data[headers.len..];
|
|
var written: usize = pattern.len * splat;
|
|
for (headers) |bytes| written += bytes.len;
|
|
return .{ .len = written };
|
|
}
|
|
|
|
fn null_writeFile(
|
|
context: ?*anyopaque,
|
|
file: std.fs.File,
|
|
offset: Offset,
|
|
len: FileLen,
|
|
headers_and_trailers: []const []const u8,
|
|
headers_len: usize,
|
|
) Result {
|
|
_ = context;
|
|
var n: usize = 0;
|
|
if (len == .entire_file) {
|
|
const headers = headers_and_trailers[0..headers_len];
|
|
for (headers) |bytes| n += bytes.len;
|
|
if (offset.toInt()) |off| {
|
|
const stat = file.stat() catch |err| return .{ .err = err, .len = n };
|
|
n += stat.size - off;
|
|
for (headers_and_trailers[headers_len..]) |bytes| n += bytes.len;
|
|
return .{ .len = n };
|
|
}
|
|
@panic("TODO stream from file until eof, counting");
|
|
}
|
|
for (headers_and_trailers) |bytes| n += bytes.len;
|
|
return .{ .len = len.int() + n };
|
|
}
|
|
|
|
test @"null" {
|
|
try @"null".writeAll("yay");
|
|
}
|
|
|
|
fn eof_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) Result {
|
|
_ = context;
|
|
_ = data;
|
|
_ = splat;
|
|
return .{ .end = true };
|
|
}
|
|
|
|
fn eof_writeFile(
|
|
context: ?*anyopaque,
|
|
file: std.fs.File,
|
|
offset: u64,
|
|
len: FileLen,
|
|
headers_and_trailers: []const []const u8,
|
|
headers_len: usize,
|
|
) Result {
|
|
_ = context;
|
|
_ = file;
|
|
_ = offset;
|
|
_ = len;
|
|
_ = headers_and_trailers;
|
|
_ = headers_len;
|
|
return .{ .end = true };
|
|
}
|