Compare commits
3 commits
512eab0db0
...
12b755660e
| Author | SHA1 | Date | |
|---|---|---|---|
| 12b755660e | |||
| a99adb0b29 | |||
| 19246e5f83 |
1 changed files with 71 additions and 12 deletions
83
src/srf.zig
83
src/srf.zig
|
|
@ -412,6 +412,7 @@ pub const Record = struct {
|
|||
fields_allocated: [fields_len]bool = .{false} ** fields_len,
|
||||
allocator: std.mem.Allocator,
|
||||
source_value: T,
|
||||
format_options: FormatOptions,
|
||||
cached_record: ?Record = null,
|
||||
|
||||
const Self = @This();
|
||||
|
|
@ -419,12 +420,13 @@ pub const Record = struct {
|
|||
|
||||
pub const SourceType = T;
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, source: T) Self {
|
||||
pub fn init(allocator: std.mem.Allocator, source: T, options: FormatOptions) Self {
|
||||
return .{
|
||||
// SAFETY: fields_buf is set by record() and is guarded by fields_set
|
||||
.fields_buf = undefined,
|
||||
.allocator = allocator,
|
||||
.source_value = source,
|
||||
.format_options = options,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -442,7 +444,7 @@ pub const Record = struct {
|
|||
) !usize {
|
||||
if (default_value_ptr) |d| {
|
||||
const default_val: *const field_type = @ptrCast(@alignCast(d));
|
||||
if (std.meta.eql(val, default_val.*)) return inx;
|
||||
if (!self.format_options.emit_default_values and std.meta.eql(val, default_val.*)) return inx;
|
||||
}
|
||||
const value = try self.formatField(field_type, field_name, val);
|
||||
self.fields_buf[inx] = .{
|
||||
|
|
@ -512,7 +514,7 @@ pub const Record = struct {
|
|||
const key = if (@hasDecl(U, "srf_tag_field"))
|
||||
U.srf_tag_field
|
||||
else
|
||||
"active_tag";
|
||||
"type";
|
||||
self.fields_buf[inx] = .{
|
||||
.key = key,
|
||||
.value = .{ .string = active_tag_name },
|
||||
|
|
@ -562,7 +564,13 @@ pub const Record = struct {
|
|||
///
|
||||
/// Call `deinit()` to free any allocations made for custom-formatted fields.
|
||||
pub fn from(comptime T: type, allocator: std.mem.Allocator, val: T) !OwnedRecord(T) {
|
||||
return OwnedRecord(T).init(allocator, val);
|
||||
return OwnedRecord(T).init(allocator, val, .{});
|
||||
}
|
||||
|
||||
/// Internal function to allow an OwnedRecord to see format options necessary
|
||||
/// to emit default values
|
||||
fn fromWithOptions(comptime T: type, allocator: std.mem.Allocator, val: T, options: FormatOptions) !OwnedRecord(T) {
|
||||
return OwnedRecord(T).init(allocator, val, options);
|
||||
}
|
||||
|
||||
/// Coerce a `Record` to a Zig struct or tagged union. For each field in `T`,
|
||||
|
|
@ -573,7 +581,7 @@ pub const Record = struct {
|
|||
/// first value silently ignored.
|
||||
///
|
||||
/// For tagged unions, the active variant is determined by a field named
|
||||
/// `"active_tag"` (or the value of `T.srf_tag_field` if declared). The
|
||||
/// `"type"` (or the value of `T.srf_tag_field` if declared). The
|
||||
/// remaining fields are coerced into the payload struct of that variant.
|
||||
///
|
||||
/// For streaming data without collecting fields first, prefer
|
||||
|
|
@ -607,7 +615,7 @@ pub const Record = struct {
|
|||
const active_tag_name = if (@hasDecl(T, "srf_tag_field"))
|
||||
T.srf_tag_field
|
||||
else
|
||||
"active_tag";
|
||||
"type";
|
||||
if (self.firstFieldByName(active_tag_name)) |srf_field| {
|
||||
if (srf_field.value == null or srf_field.value.? != .string)
|
||||
return error.ActiveTagValueMustBeAString;
|
||||
|
|
@ -900,7 +908,7 @@ pub const RecordIterator = struct {
|
|||
///
|
||||
/// For tagged unions, the active tag field must appear first in the
|
||||
/// stream (unlike `Record.to` which can do random access). The tag
|
||||
/// field name defaults to `"active_tag"` or `T.srf_tag_field` if
|
||||
/// field name defaults to `"type"` or `T.srf_tag_field` if
|
||||
/// declared.
|
||||
pub fn to(self: FieldIterator, comptime T: type) !T {
|
||||
const ti = @typeInfo(T);
|
||||
|
|
@ -957,7 +965,7 @@ pub const RecordIterator = struct {
|
|||
const active_tag_name = if (@hasDecl(T, "srf_tag_field"))
|
||||
T.srf_tag_field
|
||||
else
|
||||
"active_tag";
|
||||
"type";
|
||||
const first_try = try self.next();
|
||||
if (first_try == null) return error.ActiveTagFieldNotFound;
|
||||
const f = first_try.?;
|
||||
|
|
@ -1118,6 +1126,9 @@ pub const FormatOptions = struct {
|
|||
/// and just format the record. This is useful for appending to an existing
|
||||
/// srf file rather than overwriting all the data
|
||||
emit_directives: bool = true,
|
||||
|
||||
/// When set to true, this will output all values, even if they are the default values
|
||||
emit_default_values: bool = false,
|
||||
};
|
||||
|
||||
/// Returns a `Formatter` for writing pre-built `Record` values to a writer.
|
||||
|
|
@ -1150,7 +1161,12 @@ pub fn FromFormatter(comptime T: type) type {
|
|||
for (self.value) |item| {
|
||||
if (!first and self.options.long_format) try writer.writeByte('\n');
|
||||
first = false;
|
||||
var owned_record = Record.from(T, self.allocator, item) catch
|
||||
var owned_record = Record.fromWithOptions(
|
||||
T,
|
||||
self.allocator,
|
||||
item,
|
||||
self.options,
|
||||
) catch
|
||||
return std.Io.Writer.Error.WriteFailed;
|
||||
defer owned_record.deinit();
|
||||
const record = owned_record.record() catch return std.Io.Writer.Error.WriteFailed;
|
||||
|
|
@ -1226,8 +1242,9 @@ pub const RecordFormatter = struct {
|
|||
switch (f.value.?) {
|
||||
.string => |s| {
|
||||
const newlines = std.mem.containsAtLeastScalar(u8, s, 1, '\n');
|
||||
const commas = !self.options.long_format and std.mem.containsAtLeastScalar(u8, s, 1, ',');
|
||||
// Output the count if newlines exist
|
||||
const count = if (newlines) s.len else null;
|
||||
const count = if (newlines or commas) s.len else null;
|
||||
if (count) |c| try writer.print("{d}", .{c});
|
||||
try writer.writeByte(':');
|
||||
try writer.writeAll(s);
|
||||
|
|
@ -1842,8 +1859,8 @@ test "unions" {
|
|||
);
|
||||
const expect =
|
||||
\\#!srfv1
|
||||
\\active_tag::foo,number:num:42,true_or_false:bool:true
|
||||
\\active_tag::bar,sentence::foobar,decimal:num:6.9
|
||||
\\type::foo,number:num:42,true_or_false:bool:true
|
||||
\\type::bar,sentence::foobar,decimal:num:6.9
|
||||
\\
|
||||
;
|
||||
try std.testing.expectEqualStrings(expect, compact_from);
|
||||
|
|
@ -2016,6 +2033,48 @@ test fmtFrom {
|
|||
\\
|
||||
, result);
|
||||
}
|
||||
test "fmtFrom commas" {
|
||||
// Example: serialize typed Zig values directly to SRF format.
|
||||
const Data = struct {
|
||||
name: []const u8 = "bob",
|
||||
age: u8,
|
||||
};
|
||||
const values: []const Data = &.{
|
||||
.{ .name = "alice, yo", .age = 30 },
|
||||
};
|
||||
var buf: [4096]u8 = undefined;
|
||||
const result = try std.fmt.bufPrint(
|
||||
&buf,
|
||||
"{f}",
|
||||
.{fmtFrom(Data, std.testing.allocator, values, .{})},
|
||||
);
|
||||
try std.testing.expectEqualStrings(
|
||||
\\#!srfv1
|
||||
\\name:9:alice, yo,age:num:30
|
||||
\\
|
||||
, result);
|
||||
}
|
||||
test "fmtFrom outputs defaults with option" {
|
||||
// Example: serialize typed Zig values directly to SRF format.
|
||||
const Data = struct {
|
||||
name: []const u8 = "bob",
|
||||
age: u8,
|
||||
};
|
||||
const values: []const Data = &.{
|
||||
.{ .age = 30 },
|
||||
};
|
||||
var buf: [4096]u8 = undefined;
|
||||
const result = try std.fmt.bufPrint(
|
||||
&buf,
|
||||
"{f}",
|
||||
.{fmtFrom(Data, std.testing.allocator, values, .{ .emit_default_values = true })},
|
||||
);
|
||||
try std.testing.expectEqualStrings(
|
||||
\\#!srfv1
|
||||
\\name::bob,age:num:30
|
||||
\\
|
||||
, result);
|
||||
}
|
||||
test "parse with diagnostics" {
|
||||
// Example: batch parsing collects all records and fields into slices.
|
||||
// Prefer `iterator` for streaming; use `parse` when random access to
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue