Something went wrong. Try again.
A pretty printer for zig
zig
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742const std = @import("std");const Io = std.Io;const Type = std.builtin.Type;
pub fn pretty(value: anytype) Pretty( @TypeOf(value), if (isComptimeType(@TypeOf(value))) value else @TypeOf(value),) { return Pretty( @TypeOf(value), if (isComptimeType(@TypeOf(value))) value else @TypeOf(value), ).init(value);}
pub fn prettyO(value: anytype, comptime opts: Options) PrettyWithOptions( @TypeOf(value), opts, if (isComptimeType(@TypeOf(value))) value else @TypeOf(value),) { return PrettyWithOptions( @TypeOf(value), opts, if (isComptimeType(@TypeOf(value))) value else @TypeOf(value), ).init(value);}
const root = @import("root");const default_options: Options = if (@hasDecl(root, "pretty_options")) root.pretty_optionselse Options{};
pub const Options = struct { /// Disable pretty printing. /// /// If enabled, will format values using `{any}` disable: bool = false,
/// Show type names show_type_names: bool = true, /// Don't show type names for the root value skip_root_type_name: bool = false,
/// Disable color output no_color: bool = false, /// Customize the theme, change colors or separators theme: Theme = .{},
/// Inline print arrays array_inline: bool = false, /// Always show array indices, even in inline mode array_always_show_index: bool = false,
/// Treat pointers to `[]u8` / `[]const u8` as strings ptr_array_u8_as_string: bool = true, /// Treat pointers to a u8 slice as strings ptr_slice_u8_as_string: bool = true, /// Inline print slices ptr_slice_inline: bool = false, /// Always show slice indices, even in inline mode ptr_slice_always_show_index: bool = false,
/// Inline print structs struct_inline: bool = false, /// Highlight tag type of unions union_highlight_tag: bool = true,};
pub const Theme = struct { pub const Color = enum { dim, field, value, @"error", type, string, many_ptr, null, true, false, };
indent_width: comptime_int = 2,
field_name_type_sep: []const u8 = ": ", type_value_sep: []const u8 = " = ", index_value_sep: []const u8 = " = ",
index_open: []const u8 = "[", index_close: []const u8 = "]",
array_open: []const u8 = "{", array_close: []const u8 = "}",
vec_open: []const u8 = "(", vec_close: []const u8 = ")",
color_dim: Io.Terminal.Color = .dim, color_field: Io.Terminal.Color = .green, color_value: Io.Terminal.Color = .blue, color_error: Io.Terminal.Color = .red, color_type: Io.Terminal.Color = .bright_blue, color_string: Io.Terminal.Color = .magenta, color_many_ptr: Io.Terminal.Color = .magenta, color_null: Io.Terminal.Color = .cyan, color_true: Io.Terminal.Color = .bright_green, color_false: Io.Terminal.Color = .bright_red,
pub inline fn getColor(comptime this: Theme, comptime color: Color) Io.Terminal.Color { return @field(this, "color_" ++ @tagName(color)); }};
pub fn Pretty(comptime T: type, comptime value: anytype) type { return PrettyWithOptions(T, default_options, value);}
pub fn PrettyWithOptions(comptime T: type, comptime options: Options, comptime value: anytype) type { if (default_options.disable) return PrettyDisabled(T);
return if (isComptimeType(T)) PrettyComptimeValue(T, options, value) else PrettyRuntimeValue(T, options);}
pub fn PrettyDisabled(comptime T: type) type { return struct { value: T,
pub fn init(val: T) @This() { return .{ .value = val }; }
pub fn format(this: *const @This(), w: *std.Io.Writer) error{WriteFailed}!void { try w.print("{any}", .{this.value}); } };}
fn isComptimeType(comptime T: type) bool { switch (@typeInfo(T)) { .comptime_int, .comptime_float, .enum_literal, .@"fn", .type, .null, => return true, else => return false, }}
const global = struct { var tty: ?Io.Terminal = null;
pub inline fn setup(w: *std.Io.Writer) void { if (tty == null) { var buffer: [1]u8 = undefined; const stderr = std.debug.lockStderr(&buffer).terminal(); defer std.debug.unlockStderr();
tty = stderr; tty.?.writer = w; } }};
pub fn PrettyComptimeValue(comptime T: type, comptime options: Options, comptime value: T) type { if (default_options.disable) return PrettyDisabled(T); const ctx: Context = Context{ .options = options };
return struct { pub fn init(_: T) @This() { return .{}; }
pub fn format(_: *const @This(), w: *std.Io.Writer) error{WriteFailed}!void { global.setup(w);
var run = Runtime{ .out = w, .tty = global.tty.?, .no_color = options.no_color, };
try run.pretty(ctx, value, .{}); try w.flush(); } };}
pub fn PrettyRuntimeValue(comptime T: type, comptime options: Options) type { if (default_options.disable) return PrettyDisabled(T); const ctx: Context = Context{ .options = options };
return struct { value: T,
pub fn init(val: T) @This() { return .{ .value = val }; }
pub fn format(this: *const @This(), w: *std.Io.Writer) error{WriteFailed}!void { global.setup(w);
var run = Runtime{ .out = w, .tty = global.tty.?, .no_color = options.no_color, };
try run.pretty(ctx, this.value, .{}); try w.flush(); } };}
pub const Runtime = struct { out: *Io.Writer, tty: Io.Terminal,
no_color: bool = default_options.no_color,
pub inline fn print( this: *const Runtime, comptime fmt: []const u8, args: anytype, ) error{WriteFailed}!void { return this.out.print(fmt, args); }
pub inline fn write(this: *const Runtime, text: []const u8) error{WriteFailed}!void { _ = try this.out.write(text); }
pub fn pretty( this: *const Runtime, comptime ctx: Context, value: anytype, comptime opts: InnerFmtOptions, ) error{WriteFailed}!void { return innerFmt(@TypeOf(value), ctx, this, value, opts); }
pub inline fn setColor( this: *const Runtime, comptime ctx: Context, comptime color: Theme.Color, ) void { if (this.no_color or default_options.no_color) return; this.tty.setColor(comptime ctx.options.theme.getColor(color)) catch {}; }
pub inline fn setColorRaw(this: *const Runtime, color: Io.Terminal.Color) void { if (this.no_color or default_options.no_color) return; this.tty.setColor(color) catch {}; }
pub inline fn resetColor(this: *const Runtime) void { if (this.no_color or default_options.no_color) return; this.tty.setColor(.reset) catch {}; }};
pub const Context = struct { depth: comptime_int = 0,
options: Options, exited_comptime: bool = false,
pub fn inComptime(comptime this: Context) bool { if (!this.exited_comptime) return false; return @inComptime(); }};
pub const InnerFmtOptions = struct { skip_type_name: bool = !default_options.show_type_names, force_inline: bool = false,};
fn innerFmt( comptime T: type, comptime ctx: Context, run: *const Runtime, value: T, comptime opts: InnerFmtOptions,) error{WriteFailed}!void { const info = @typeInfo(T);
if (!opts.skip_type_name) try printType(T, ctx, run, value);
if (ctx.options.struct_inline or opts.force_inline) { if (std.meta.hasMethod(T, "prettyInline")) { return value.prettyInline(ctx, run); } } else { if (std.meta.hasMethod(T, "pretty")) { return value.pretty(ctx, run); } }
return switch (info) { .bool => formatBool(ctx, run, value), .null => formatNull(ctx, run), .type => formatType(ctx, run, value),
// comptime types .comptime_int, .comptime_float, // number types .int, .float, // enum types .@"enum", .enum_literal, => formatValue(ctx, run, value),
.optional => |opt| formatOptional(opt.child, ctx, run, value), .@"struct" => |st| formatStruct(T, ctx, st, run, value), .@"union" => formatUnion(T, ctx, run, value),
.error_set => formatErrorSet(ctx, run, value), .error_union => formatErrorUnion(ctx, run, value),
.array => |arr| formatArray(ctx, arr, run, value), .vector => |vec| formatVector(ctx, vec, run, value),
.@"fn" => formatFn(T, ctx, run),
.pointer => |ptr| switch (ptr.size) { .one => formatPtrOne(ctx, ptr, run, value), .slice => formatPtrSlice(ctx, ptr, run, value), .many => formatPtrMany(ctx, ptr, run, value), else => { run.setColor(ctx, .@"error"); try run.print("Unimplemented! ({} = {any})", .{ info, value }); run.resetColor(); }, },
else => { run.setColor(ctx, .@"error"); try run.print("Unimplemented! ({} = {any})", .{ info, value }); run.resetColor(); }, };}
inline fn printType( comptime T: type, comptime ctx: Context, run: *const Runtime, value: T,) error{WriteFailed}!void { const active_type = comptime std.meta.activeTag(@typeInfo(T)); const excluded = comptime switch (active_type) { .void, .noreturn, .undefined, .null, .@"fn" => true, else => false, };
if ((ctx.depth != 0 or !ctx.options.skip_root_type_name) and !excluded) { run.setColor(ctx, .dim);
if (ctx.options.show_type_names) { try run.write(@typeName(T));
if (active_type == .@"union") { if (ctx.options.union_highlight_tag) { run.resetColor(); run.setColor(ctx, .field); }
try run.print("{}", .{std.meta.activeTag(value)});
if (ctx.options.union_highlight_tag) { run.resetColor(); run.setColor(ctx, .dim); } } } try run.write(ctx.options.theme.type_value_sep);
run.resetColor(); }}
inline fn formatBool( comptime ctx: Context, run: *const Runtime, value: bool,) !void { if (value) run.setColor(ctx, .true) else run.setColor(ctx, .false);
try run.print("{}", .{value}); run.resetColor();}
inline fn formatNull(comptime ctx: Context, run: *const Runtime) !void { run.setColor(ctx, .null); try run.write("null"); run.resetColor();}
inline fn formatType( comptime ctx: Context, run: *const Runtime, value: type,) !void { run.setColor(ctx, .type); run.setColorRaw(.bold); try run.write(@typeName(value)); run.resetColor();}
inline fn formatValue( comptime ctx: Context, run: *const Runtime, value: anytype,) !void { run.setColor(ctx, .value); try run.print("{}", .{value}); run.resetColor();}
inline fn formatOptional( comptime T: type, comptime ctx: Context, run: *const Runtime, value: ?T,) !void { return if (value) |val| innerFmt(T, ctx, run, val, .{ .skip_type_name = true }) else formatNull(ctx, run);}
inline fn formatStruct( comptime T: type, comptime ctx: Context, comptime st: Type.Struct, run: *const Runtime, value: T,) !void { const next_ctx = Context{ .depth = ctx.depth + 1, .exited_comptime = ctx.exited_comptime, .options = ctx.options, };
if (ctx.options.struct_inline) { run.setColor(ctx, .dim); try run.write(".{ "); run.resetColor(); }
comptime var index = 0; inline for (st.field_names, 0..) |field, idx| { indent(next_ctx, next_ctx.options.struct_inline, run); if (index != 0 and ctx.options.struct_inline) { run.setColor(ctx, .dim); try run.write(", "); run.resetColor(); }
run.setColor(ctx, .field); try run.write("." ++ field);
if (st.field_attrs[idx].@"comptime") { run.setColor(ctx, .dim); try run.write(" [comptime]"); run.resetColor(); }
if (st.field_attrs[idx].@"align") |a| { run.setColor(ctx, .dim); run.pretty(ctx, " align({})", .{a}); run.resetColor(); }
run.setColor(ctx, .dim); try run.write(ctx.options.theme.field_name_type_sep); run.resetColor();
try innerFmt(st.field_types[idx], next_ctx, run, @field(value, field), .{});
index += 1; }
if (ctx.options.struct_inline) { run.setColor(ctx, .dim); try run.write(" }"); run.resetColor(); }}
inline fn formatUnion( comptime T: type, comptime ctx: Context, run: *const Runtime, value: T,) !void { switch (value) { inline else => |val| try innerFmt(@TypeOf(val), ctx, run, val, .{ .skip_type_name = true }), }}
inline fn formatArray( comptime ctx: Context, comptime arr: Type.Array, run: *const Runtime, value: anytype,) !void { if (arr.len == 0) { try run.write(ctx.options.theme.array_open); try run.write(ctx.options.theme.array_close); return; }
const next_ctx = Context{ .depth = ctx.depth + 1, .exited_comptime = ctx.exited_comptime, .options = ctx.options, };
if (ctx.options.array_inline) try arrayOpen(ctx, run);
comptime var index = 0; inline for (value) |val| { indent( next_ctx, next_ctx.options.array_inline, run, );
run.setColor(ctx, .dim); if (index != 0 and ctx.options.array_inline) try run.write(", ");
if (!ctx.options.array_inline or ctx.options.array_always_show_index) { try run.write(ctx.options.theme.index_open); try run.print("{}", .{index}); try run.write(ctx.options.theme.index_close ++ ctx.options.theme.index_value_sep); }
run.resetColor();
try innerFmt(arr.child, next_ctx, run, val, .{ .skip_type_name = true });
index += 1; }
if (ctx.options.array_inline) try arrayClose(ctx, run);}
inline fn formatVector( comptime ctx: Context, comptime vec: Type.Vector, run: *const Runtime, value: @Vector(vec.len, vec.child),) !void { run.setColor(ctx, .dim); try run.write(ctx.options.theme.vec_open ++ " "); run.resetColor();
inline for (0..vec.len) |idx| { if (idx != 0) { run.setColor(ctx, .dim); try run.write(", "); run.resetColor(); }
try innerFmt(vec.child, ctx, run, value[idx], .{ .skip_type_name = true }); }
run.setColor(ctx, .dim); try run.write(" " ++ ctx.options.theme.vec_close); run.resetColor();}
inline fn formatPtrOne( comptime ctx: Context, comptime ptr: Type.Pointer, run: *const Runtime, value: anytype,) !void { const is_string = switch (@typeInfo(ptr.child)) { .array => |arr| arr.child == u8 and ctx.options.ptr_array_u8_as_string, else => false, }; if (is_string) return formatString(ctx, run, value);
try innerFmt(ptr.child, ctx, run, value.*, .{ .skip_type_name = true });}
inline fn formatPtrSlice( comptime ctx: Context, comptime ptr: Type.Pointer, run: *const Runtime, value: anytype,) !void { if (ptr.child == u8 and ctx.options.ptr_slice_u8_as_string) return formatString(ctx, run, value);
const next_ctx = Context{ .depth = if (!ctx.options.ptr_slice_inline) ctx.depth + 1 else ctx.depth, .exited_comptime = ctx.exited_comptime, .options = ctx.options, };
if (ctx.options.ptr_slice_inline) try arrayOpen(ctx, run);
var count: usize = 0; for (value, 0..) |val, idx| { count += 1;
indent( next_ctx, next_ctx.options.ptr_slice_inline, run, );
run.setColor(ctx, .dim); if (idx != 0 and ctx.options.ptr_slice_inline) try run.write(", ");
if (!ctx.options.ptr_slice_inline or ctx.options.ptr_slice_always_show_index) { try run.write(ctx.options.theme.index_open); try run.print("{}", .{idx}); try run.write(ctx.options.theme.index_close ++ ctx.options.theme.index_value_sep); }
run.resetColor();
try innerFmt(ptr.child, next_ctx, run, val, .{ .skip_type_name = true }); }
if (count == 0) { run.setColor(ctx, .dim); try run.write(ctx.options.theme.array_open); try run.write(ctx.options.theme.array_close); run.resetColor(); }
if (ctx.options.ptr_slice_inline) try arrayClose(ctx, run);}
inline fn formatPtrMany( comptime ctx: Context, comptime ptr: Type.Pointer, run: *const Runtime, value: anytype,) !void { _ = ptr; run.setColor(ctx, .dim); run.setColor(ctx, .many_ptr); try run.print("{*}", .{value}); run.resetColor();}
inline fn formatString( comptime ctx: Context, run: *const Runtime, value: anytype,) !void { run.setColor(ctx, .string); try run.write("\""); try run.write(value); try run.write("\""); run.resetColor();}
inline fn formatErrorSet( comptime ctx: Context, run: *const Runtime, value: anyerror,) !void { run.setColor(ctx, .@"error"); try run.write("error."); try run.write(@errorName(value)); run.resetColor();}
inline fn formatErrorUnion( comptime ctx: Context, run: *const Runtime, value: anytype,) !void { const val = value catch |err| return formatErrorSet(ctx, run, err); return innerFmt( @TypeOf(val), ctx, run, val, .{ .skip_type_name = true }, );}
inline fn formatFn( comptime T: type, comptime ctx: Context, run: *const Runtime,) !void { run.setColor(ctx, .dim); run.setColor(ctx, .type); run.setColorRaw(.bold);
try run.write(@typeName(T));
run.resetColor();}
inline fn indent( comptime ctx: Context, comptime inline_option: bool, run: *const Runtime,) void { if (inline_option) return;
const text: [ctx.depth * ctx.options.theme.indent_width]u8 = @splat(' '); run.write("\n" ++ text) catch {};}
fn arrayOpen(comptime ctx: Context, run: *const Runtime) !void { run.setColor(ctx, .dim); try run.write(ctx.options.theme.array_open ++ " "); run.resetColor();}
fn arrayClose(comptime ctx: Context, run: *const Runtime) !void { run.setColor(ctx, .dim); try run.write(" " ++ ctx.options.theme.array_close); run.resetColor();}