const std = @import("std"); const Io = std.Io; const Type = std.builtin.Type; pub fn pretty(value: anytype) Pretty(@TypeOf(value)) { return Pretty(@TypeOf(value)).init(value); } fn Context(comptime runtime: bool) type { if (runtime) { return struct { indent_width: u8 = 2, indent_level: usize = 0, }; } else { return struct { indent_width: comptime_int = 2, indent_level: comptime_int = 0, }; } } const Options = struct { type_name: bool = true, last_layer_type_name: bool = true, follow_pointers: bool = true, }; pub fn Pretty(comptime T: type) type { const global = struct { var conf: ?std.Io.tty.Config = null; }; 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 { if (global.conf == null) { global.conf = .detect(.stderr()); } return innerFmt(w, global.conf.?, T, this.value, false, .{}, .{}); } }; } const Color = enum { dim, reset, text, field, value, }; fn setColor( w: *Io.Writer, tty: Io.tty.Config, color: Color, ) void { switch (color) { .dim => { tty.setColor(w, .dim) catch {}; }, .reset => { tty.setColor(w, .reset) catch {}; }, .text => { // tty.setColor(w, .italic) catch {}; tty.setColor(w, .dim) catch {}; tty.setColor(w, .blue) catch {}; }, .field => { tty.setColor(w, .green) catch {}; }, .value => { // tty.setColor(w, .italic) catch {}; tty.setColor(w, .blue) catch {}; }, } } fn innerFmt( w: *std.Io.Writer, tty: std.Io.tty.Config, comptime T: type, value: T, comptime runtime: bool, ctx: Context(runtime), opts: Options, ) error{WriteFailed}!void { const info = @typeInfo(T); switch (info) { .bool => try printBool(w, tty, value, opts), .int => |int| try printInt(w, tty, int, value, opts), .float => |float| try printFloat(w, tty, float, value, opts), .@"enum", .enum_literal => try printEnum(w, tty, value, opts), .optional => |optional| try printOptional(w, tty, optional, value, runtime, ctx, opts), .pointer => |ptr| { if (opts.follow_pointers) { try printPointer(w, tty, ptr, value, runtime, ctx, opts); } else {} }, .array => |array| try printArray(w, tty, array, value, runtime, ctx, opts), .@"struct" => |str| try printStruct(T, w, tty, str, value, runtime, ctx, opts), else => { tty.setColor(w, .red) catch {}; try w.print("Unimplemented! ({})", .{info}); setColor(w, tty, .reset); }, } } fn printBool( w: *Io.Writer, tty: Io.tty.Config, value: bool, opts: Options, ) !void { if (opts.type_name) { setColor(w, tty, .dim); try w.print("bool => ", .{}); setColor(w, tty, .reset); } else { tty.setColor(w, .dim) catch {}; try w.print("=> ", .{}); setColor(w, tty, .reset); } setColor(w, tty, .value); if (value) { tty.setColor(w, .bright_green) catch {}; } else { tty.setColor(w, .bright_red) catch {}; } try w.print("{}", .{value}); setColor(w, tty, .reset); } fn printInt( w: *Io.Writer, tty: Io.tty.Config, int: Type.Int, value: anytype, opts: Options, ) !void { if (opts.type_name) { tty.setColor(w, .dim) catch {}; try w.print("{s}{} => ", .{ if (int.signedness == .signed) "i" else "u", int.bits }); setColor(w, tty, .reset); } else { tty.setColor(w, .dim) catch {}; try w.print("=> ", .{}); setColor(w, tty, .reset); } setColor(w, tty, .value); try w.print("{}", .{value}); setColor(w, tty, .reset); } fn printFloat( w: *Io.Writer, tty: Io.tty.Config, float: Type.Float, value: anytype, opts: Options, ) !void { if (opts.type_name) { tty.setColor(w, .dim) catch {}; try w.print("f{} => ", .{float.bits}); setColor(w, tty, .reset); } else { tty.setColor(w, .dim) catch {}; try w.print("=> ", .{}); setColor(w, tty, .reset); } setColor(w, tty, .value); try w.print("{}", .{value}); setColor(w, tty, .reset); } fn printEnum( w: *Io.Writer, tty: Io.tty.Config, value: anytype, opts: Options, ) !void { if (opts.type_name) { tty.setColor(w, .dim) catch {}; try w.print("{s} => ", .{@typeName(@TypeOf(value))}); setColor(w, tty, .reset); } else { tty.setColor(w, .dim) catch {}; try w.print("=> ", .{}); setColor(w, tty, .reset); } setColor(w, tty, .value); try w.print("{}", .{value}); setColor(w, tty, .reset); } fn printOptional( w: *Io.Writer, tty: Io.tty.Config, optional: Type.Optional, value: anytype, comptime runtime: bool, ctx: Context(runtime), opts: Options, ) !void { var next_opts = opts; next_opts.type_name = if (!opts.type_name and !opts.last_layer_type_name) false else true; next_opts.last_layer_type_name = opts.type_name; if (opts.type_name) { tty.setColor(w, .dim) catch {}; try w.print("?", .{}); setColor(w, tty, .reset); } if (value) |val| { try innerFmt(w, tty, optional.child, val, runtime, ctx, next_opts); } else { if (opts.type_name) { tty.setColor(w, .dim) catch {}; try w.print("{s} => ", .{@typeName(optional.child)}); setColor(w, tty, .reset); } else { tty.setColor(w, .dim) catch {}; try w.print("=> ", .{}); setColor(w, tty, .reset); } tty.setColor(w, .blue) catch {}; try w.writeAll("null"); setColor(w, tty, .reset); } } fn printArray( w: *Io.Writer, tty: Io.tty.Config, array: Type.Array, value: anytype, comptime runtime: bool, ctx: Context(runtime), opts: Options, ) !void { comptime var next_ctx = ctx; var next_opts = opts; next_opts.type_name = if (!opts.type_name and !opts.last_layer_type_name) false else true; next_opts.last_layer_type_name = opts.type_name; if (opts.type_name) { tty.setColor(w, .dim) catch {}; try w.print("[{}]{s} =>", .{ array.len, @typeName(array.child) }); setColor(w, tty, .reset); } else { tty.setColor(w, .dim) catch {}; try w.print("=> ", .{}); setColor(w, tty, .reset); } inline for (value, 0..) |item, idx| { try indent(w, runtime, ctx); setColor(w, tty, .field); try w.print("[{}] ", .{idx}); setColor(w, tty, .reset); next_ctx.indent_level = ctx.indent_level + 1; next_opts.type_name = false; try innerFmt(w, tty, @TypeOf(item), item, runtime, next_ctx, next_opts); } } fn printPointer( w: *Io.Writer, tty: Io.tty.Config, ptr: Type.Pointer, value: anytype, comptime runtime: bool, ctx: Context(runtime), opts: Options, ) !void { var next_opts = opts; next_opts.type_name = if (!opts.type_name and !opts.last_layer_type_name) false else true; next_opts.last_layer_type_name = opts.type_name; switch (ptr.size) { .one => { if (opts.type_name) { tty.setColor(w, .dim) catch {}; try w.print("*{s}", .{if (ptr.is_const) "const " else ""}); setColor(w, tty, .reset); } if (ptr.child == anyopaque or @typeInfo(ptr.child) == .@"fn") return; try innerFmt(w, tty, ptr.child, value, runtime, ctx, next_opts); }, .slice => { if (opts.type_name) { setColor(w, tty, .dim); try w.print("[]{s}", .{if (ptr.is_const) "const " else ""}); setColor(w, tty, .reset); } if (ptr.child == u8) { tty.setColor(w, .dim) catch {}; try w.print("{s}=>", .{if (opts.type_name) "u8 " else ""}); setColor(w, tty, .reset); try indent(w, runtime, ctx); setColor(w, tty, .text); try w.print("\"{s}\"", .{value}); setColor(w, tty, .reset); return; } setColor(w, tty, .dim); try w.print("{s}{s}", .{ if (opts.type_name) @typeName(ptr.child) else "", if (opts.type_name) " =>" else "=>", }); setColor(w, tty, .reset); const run_ctx: Context(true) = .{ .indent_width = ctx.indent_width, .indent_level = ctx.indent_level }; var next_ctx: Context(true) = .{ .indent_width = ctx.indent_width, .indent_level = ctx.indent_level }; var count: usize = 0; for (value, 0..) |item, idx| { try indent(w, true, run_ctx); setColor(w, tty, .field); try w.print("[{}] ", .{idx}); setColor(w, tty, .reset); next_ctx.indent_level = ctx.indent_level + 1; next_opts.type_name = false; try innerFmt(w, tty, @TypeOf(item), item, true, next_ctx, next_opts); count += 1; } if (count == 0) { setColor(w, tty, .dim); try w.writeAll(" empty"); setColor(w, tty, .reset); } }, else => { try w.print("unimplemented {}", .{ptr}); }, } } fn printStruct( comptime T: type, w: *Io.Writer, tty: Io.tty.Config, str: Type.Struct, value: anytype, comptime runtime: bool, ctx: Context(runtime), opts: Options, ) !void { var next_opts = opts; next_opts.type_name = if (!opts.type_name and !opts.last_layer_type_name) false else true; next_opts.last_layer_type_name = opts.type_name; setColor(w, tty, .dim); if (opts.type_name) { try w.print("{s} =>", .{@typeName(T)}); } else { try w.print("=>", .{}); } setColor(w, tty, .reset); if (runtime) { var next_ctx: Context(true) = ctx; inline for (str.fields) |field| { try indent(w, runtime, ctx); setColor(w, tty, .field); try w.print(".{s}", .{field.name}); setColor(w, tty, .reset); setColor(w, tty, .dim); if (opts.type_name) { try w.writeAll(": "); } else try w.writeByte(' '); setColor(w, tty, .reset); next_ctx.indent_level = ctx.indent_level + 1; try innerFmt( w, tty, field.type, @field(value, field.name), runtime, next_ctx, next_opts, ); } } else { comptime var next_ctx: Context(false) = ctx; inline for (str.fields) |field| { try indent(w, runtime, ctx); setColor(w, tty, .field); try w.print(".{s}", .{field.name}); setColor(w, tty, .reset); setColor(w, tty, .dim); if (opts.type_name) { try w.writeAll(": "); } else try w.writeByte(' '); setColor(w, tty, .reset); next_ctx.indent_level = ctx.indent_level + 1; try innerFmt( w, tty, field.type, @field(value, field.name), runtime, next_ctx, next_opts, ); } } } fn indent(w: *Io.Writer, comptime runtime: bool, ctx: Context(runtime)) !void { try w.writeByte('\n'); if (runtime) { for (0..((ctx.indent_level + 1) * ctx.indent_width)) |_| { try w.writeByte(' '); } } else { const text: [(ctx.indent_level + 1) * ctx.indent_width]u8 = @splat(' '); try w.writeAll(&text); } }