A pretty printer for zig
zig
at aa04716ea6ccca41ebbaa5579f92d4bca6a89f11 651 lines 17 kB view raw
1const std = @import("std"); 2const Io = std.Io; 3const Type = std.builtin.Type; 4 5pub fn pretty(value: anytype) Pretty(@TypeOf(value)) { 6 return Pretty(@TypeOf(value)).init(value); 7} 8 9pub fn prettyO(value: anytype, comptime opts: Options) PrettyWithOptions(@TypeOf(value), opts) { 10 return PrettyWithOptions(@TypeOf(value), opts).init(value); 11} 12 13const root = @import("root"); 14const default_options: Options = if (@hasDecl(root, "pretty_options")) 15 root.pretty_options 16else 17 Options{}; 18 19pub const Options = struct { 20 /// Disable pretty printing. 21 /// 22 /// If enabled, will format values using `{any}` 23 disable: bool = false, 24 25 /// Show type names 26 show_type_names: bool = true, 27 /// Don't show type names for the root value 28 skip_root_type_name: bool = false, 29 30 /// Disable color output 31 no_color: bool = false, 32 /// Customize the theme, change colors or separators 33 theme: Theme = .{}, 34 35 /// Inline print arrays 36 array_inline: bool = false, 37 /// Always show array indices, even in inline mode 38 array_always_show_index: bool = false, 39 40 /// Treat pointers to `[]u8` / `[]const u8` as strings 41 ptr_array_u8_as_string: bool = true, 42 /// Treat pointers to a u8 slice as strings 43 ptr_slice_u8_as_string: bool = true, 44 /// Inline print slices 45 ptr_slice_inline: bool = false, 46 /// Always show slice indices, even in inline mode 47 ptr_slice_always_show_index: bool = false, 48 49 /// Inline print structs 50 struct_inline: bool = false, 51 /// Highlight tag type of unions 52 union_highlight_tag: bool = true, 53}; 54 55pub const Theme = struct { 56 pub const Color = enum { 57 dim, 58 field, 59 value, 60 @"error", 61 type, 62 string, 63 many_ptr, 64 null, 65 true, 66 false, 67 }; 68 69 indent_width: comptime_int = 2, 70 71 field_name_type_sep: []const u8 = ": ", 72 type_value_sep: []const u8 = " = ", 73 index_value_sep: []const u8 = " = ", 74 75 index_open: []const u8 = "[", 76 index_close: []const u8 = "]", 77 78 array_open: []const u8 = "{", 79 array_close: []const u8 = "}", 80 81 vec_open: []const u8 = "(", 82 vec_close: []const u8 = ")", 83 84 color_dim: Io.Terminal.Color = .dim, 85 color_field: Io.Terminal.Color = .green, 86 color_value: Io.Terminal.Color = .blue, 87 color_error: Io.Terminal.Color = .red, 88 color_type: Io.Terminal.Color = .bright_blue, 89 color_string: Io.Terminal.Color = .magenta, 90 color_many_ptr: Io.Terminal.Color = .magenta, 91 color_null: Io.Terminal.Color = .cyan, 92 color_true: Io.Terminal.Color = .bright_green, 93 color_false: Io.Terminal.Color = .bright_red, 94 95 pub inline fn getColor(comptime this: Theme, comptime color: Color) Io.Terminal.Color { 96 return @field(this, "color_" ++ @tagName(color)); 97 } 98}; 99 100pub fn Pretty(comptime T: type) type { 101 return PrettyWithOptions(T, default_options); 102} 103 104pub fn PrettyWithOptions(comptime T: type, comptime options: Options) type { 105 if (default_options.disable) return struct { 106 value: T, 107 108 pub fn init(val: T) @This() { 109 return .{ .value = val }; 110 } 111 112 pub fn format(this: *const @This(), w: *std.Io.Writer) error{WriteFailed}!void { 113 try w.print("{any}", .{this.value}); 114 } 115 }; 116 117 const global = struct { 118 var tty: ?Io.Terminal = null; 119 }; 120 const ctx: Context = Context{ .options = options }; 121 122 return struct { 123 value: T, 124 125 pub fn init(val: T) @This() { 126 return .{ .value = val }; 127 } 128 129 pub fn format(this: *const @This(), w: *std.Io.Writer) error{WriteFailed}!void { 130 if (global.tty == null) { 131 var buffer: [1]u8 = undefined; 132 const stderr = std.debug.lockStderr(&buffer).terminal(); 133 defer std.debug.unlockStderr(); 134 135 global.tty = stderr; 136 global.tty.?.writer = w; 137 } 138 139 var run = Runtime{ 140 .out = w, 141 .tty = global.tty.?, 142 .no_color = options.no_color, 143 }; 144 145 return run.pretty(ctx, this.value, .{}); 146 } 147 }; 148} 149 150pub const Runtime = struct { 151 out: *Io.Writer, 152 tty: Io.Terminal, 153 154 no_color: bool = default_options.no_color, 155 156 pub inline fn print( 157 this: *const Runtime, 158 comptime fmt: []const u8, 159 args: anytype, 160 ) error{WriteFailed}!void { 161 return this.out.print(fmt, args); 162 } 163 164 pub inline fn write(this: *const Runtime, text: []const u8) error{WriteFailed}!void { 165 _ = try this.out.write(text); 166 } 167 168 pub fn pretty( 169 this: *const Runtime, 170 comptime ctx: Context, 171 value: anytype, 172 comptime opts: InnerFmtOptions, 173 ) error{WriteFailed}!void { 174 return innerFmt(@TypeOf(value), ctx, this, value, opts); 175 } 176 177 pub inline fn setColor( 178 this: *const Runtime, 179 comptime ctx: Context, 180 comptime color: Theme.Color, 181 ) void { 182 if (this.no_color or default_options.no_color) return; 183 this.tty.setColor(comptime ctx.options.theme.getColor(color)) catch {}; 184 } 185 186 pub inline fn setColorRaw(this: *const Runtime, color: Io.Terminal.Color) void { 187 if (this.no_color or default_options.no_color) return; 188 this.tty.setColor(color) catch {}; 189 } 190 191 pub inline fn resetColor(this: *const Runtime) void { 192 if (this.no_color or default_options.no_color) return; 193 this.tty.setColor(.reset) catch {}; 194 } 195}; 196 197pub const Context = struct { 198 depth: comptime_int = 0, 199 200 options: Options, 201 exited_comptime: bool = false, 202 203 pub fn inComptime(comptime this: Context) bool { 204 if (!this.exited_comptime) return false; 205 return @inComptime(); 206 } 207}; 208 209pub const InnerFmtOptions = struct { 210 skip_type_name: bool = !default_options.show_type_names, 211}; 212 213fn innerFmt( 214 comptime T: type, 215 comptime ctx: Context, 216 run: *const Runtime, 217 value: T, 218 comptime opts: InnerFmtOptions, 219) error{WriteFailed}!void { 220 const info = @typeInfo(T); 221 222 if (!opts.skip_type_name) 223 try printType(T, ctx, run, value); 224 225 if (std.meta.hasMethod(T, "pretty")) { 226 return value.pretty(ctx, run); 227 } 228 229 return switch (info) { 230 .bool => formatBool(ctx, run, value), 231 .null => formatNull(ctx, run), 232 .type => formatType(ctx, run, value), 233 234 // comptime types 235 .comptime_int, 236 .comptime_float, 237 // number types 238 .int, 239 .float, 240 // enum types 241 .@"enum", 242 .enum_literal, 243 => formatValue(ctx, run, value), 244 245 .optional => |opt| formatOptional(opt.child, ctx, run, value), 246 .@"struct" => |st| formatStruct(T, ctx, st, run, value), 247 .@"union" => formatUnion(T, ctx, run, value), 248 249 .error_set => formatErrorSet(ctx, run, value), 250 .error_union => formatErrorUnion(ctx, run, value), 251 252 .array => |arr| formatArray(ctx, arr, run, value), 253 .vector => |vec| formatVector(ctx, vec, run, value), 254 255 .@"fn" => formatFn(T, ctx, run), 256 257 .pointer => |ptr| switch (ptr.size) { 258 .one => formatPtrOne(ctx, ptr, run, value), 259 .slice => formatPtrSlice(ctx, ptr, run, value), 260 .many => formatPtrMany(ctx, ptr, run, value), 261 else => { 262 run.setColor(ctx, .@"error"); 263 try run.print("Unimplemented! ({} = {any})", .{ info, value }); 264 run.resetColor(); 265 }, 266 }, 267 268 else => { 269 run.setColor(ctx, .@"error"); 270 try run.print("Unimplemented! ({} = {any})", .{ info, value }); 271 run.resetColor(); 272 }, 273 }; 274} 275 276inline fn printType( 277 comptime T: type, 278 comptime ctx: Context, 279 run: *const Runtime, 280 value: T, 281) error{WriteFailed}!void { 282 const active_type = comptime std.meta.activeTag(@typeInfo(T)); 283 const excluded = comptime switch (active_type) { 284 .void, .noreturn, .undefined, .null, .@"fn" => true, 285 else => false, 286 }; 287 288 if ((ctx.depth != 0 or !ctx.options.skip_root_type_name) and !excluded) { 289 run.setColor(ctx, .dim); 290 291 if (ctx.options.show_type_names) { 292 try run.write(@typeName(T)); 293 294 if (active_type == .@"union") { 295 if (ctx.options.union_highlight_tag) { 296 run.resetColor(); 297 run.setColor(ctx, .field); 298 } 299 300 try run.print("{}", .{std.meta.activeTag(value)}); 301 302 if (ctx.options.union_highlight_tag) { 303 run.resetColor(); 304 run.setColor(ctx, .dim); 305 } 306 } 307 } 308 try run.write(ctx.options.theme.type_value_sep); 309 310 run.resetColor(); 311 } 312} 313 314inline fn formatBool( 315 comptime ctx: Context, 316 run: *const Runtime, 317 value: bool, 318) !void { 319 if (value) 320 run.setColor(ctx, .true) 321 else 322 run.setColor(ctx, .false); 323 324 try run.print("{}", .{value}); 325 run.resetColor(); 326} 327 328inline fn formatNull(comptime ctx: Context, run: *const Runtime) !void { 329 run.setColor(ctx, .null); 330 try run.write("null"); 331 run.resetColor(); 332} 333 334inline fn formatType( 335 comptime ctx: Context, 336 run: *const Runtime, 337 value: type, 338) !void { 339 run.setColor(ctx, .type); 340 run.setColorRaw(.bold); 341 try run.write(@typeName(value)); 342 run.resetColor(); 343} 344 345inline fn formatValue( 346 comptime ctx: Context, 347 run: *const Runtime, 348 value: anytype, 349) !void { 350 run.setColor(ctx, .value); 351 try run.print("{}", .{value}); 352 run.resetColor(); 353} 354 355inline fn formatOptional( 356 comptime T: type, 357 comptime ctx: Context, 358 run: *const Runtime, 359 value: ?T, 360) !void { 361 return if (value) |val| 362 innerFmt(T, ctx, run, val, .{ .skip_type_name = true }) 363 else 364 formatNull(ctx, run); 365} 366 367inline fn formatStruct( 368 comptime T: type, 369 comptime ctx: Context, 370 comptime st: Type.Struct, 371 run: *const Runtime, 372 value: T, 373) !void { 374 const next_ctx = Context{ 375 .depth = ctx.depth + 1, 376 .exited_comptime = ctx.exited_comptime, 377 .options = ctx.options, 378 }; 379 380 if (ctx.options.struct_inline) { 381 run.setColor(ctx, .dim); 382 try run.write(".{ "); 383 run.resetColor(); 384 } 385 386 comptime var index = 0; 387 inline for (st.fields) |field| { 388 indent(next_ctx, next_ctx.options.struct_inline, run); 389 if (index != 0 and ctx.options.struct_inline) { 390 run.setColor(ctx, .dim); 391 try run.write(", "); 392 run.resetColor(); 393 } 394 395 run.setColor(ctx, .field); 396 try run.write("." ++ field.name ++ 397 ctx.options.theme.field_name_type_sep); 398 run.resetColor(); 399 400 try innerFmt(field.type, next_ctx, run, @field(value, field.name), .{}); 401 402 index += 1; 403 } 404 405 if (ctx.options.struct_inline) { 406 run.setColor(ctx, .dim); 407 try run.write(" }"); 408 run.resetColor(); 409 } 410} 411 412inline fn formatUnion( 413 comptime T: type, 414 comptime ctx: Context, 415 run: *const Runtime, 416 value: T, 417) !void { 418 switch (value) { 419 inline else => |val| try innerFmt(@TypeOf(val), ctx, run, val, .{ .skip_type_name = true }), 420 } 421} 422 423inline fn formatArray( 424 comptime ctx: Context, 425 comptime arr: Type.Array, 426 run: *const Runtime, 427 value: anytype, 428) !void { 429 if (arr.len == 0) { 430 try run.write(ctx.options.theme.array_open); 431 try run.write(ctx.options.theme.array_close); 432 return; 433 } 434 435 const next_ctx = Context{ 436 .depth = ctx.depth + 1, 437 .exited_comptime = ctx.exited_comptime, 438 .options = ctx.options, 439 }; 440 441 if (ctx.options.array_inline) try arrayOpen(ctx, run); 442 443 comptime var index = 0; 444 inline for (value) |val| { 445 indent( 446 next_ctx, 447 next_ctx.options.array_inline, 448 run, 449 ); 450 451 run.setColor(ctx, .dim); 452 if (index != 0 and ctx.options.array_inline) 453 try run.write(", "); 454 455 if (!ctx.options.array_inline or ctx.options.array_always_show_index) { 456 try run.write(ctx.options.theme.index_open); 457 try run.print("{}", .{index}); 458 try run.write(ctx.options.theme.index_close ++ 459 ctx.options.theme.index_value_sep); 460 } 461 462 run.resetColor(); 463 464 try innerFmt(arr.child, next_ctx, run, val, .{ .skip_type_name = true }); 465 466 index += 1; 467 } 468 469 if (ctx.options.array_inline) try arrayClose(ctx, run); 470} 471 472inline fn formatVector( 473 comptime ctx: Context, 474 comptime vec: Type.Vector, 475 run: *const Runtime, 476 value: @Vector(vec.len, vec.child), 477) !void { 478 run.setColor(ctx, .dim); 479 try run.write(ctx.options.theme.vec_open ++ " "); 480 run.resetColor(); 481 482 inline for (0..vec.len) |idx| { 483 if (idx != 0) { 484 run.setColor(ctx, .dim); 485 try run.write(", "); 486 run.resetColor(); 487 } 488 489 try innerFmt(vec.child, ctx, run, value[idx], .{ .skip_type_name = true }); 490 } 491 492 run.setColor(ctx, .dim); 493 try run.write(" " ++ ctx.options.theme.vec_close); 494 run.resetColor(); 495} 496 497inline fn formatPtrOne( 498 comptime ctx: Context, 499 comptime ptr: Type.Pointer, 500 run: *const Runtime, 501 value: anytype, 502) !void { 503 const is_string = switch (@typeInfo(ptr.child)) { 504 .array => |arr| arr.child == u8 and ctx.options.ptr_array_u8_as_string, 505 else => false, 506 }; 507 if (is_string) return formatString(ctx, run, value); 508 509 try innerFmt(ptr.child, ctx, run, value.*, .{ .skip_type_name = true }); 510} 511 512inline fn formatPtrSlice( 513 comptime ctx: Context, 514 comptime ptr: Type.Pointer, 515 run: *const Runtime, 516 value: anytype, 517) !void { 518 if (ptr.child == u8 and ctx.options.ptr_slice_u8_as_string) 519 return formatString(ctx, run, value); 520 521 const next_ctx = Context{ 522 .depth = if (!ctx.options.ptr_slice_inline) ctx.depth + 1 else ctx.depth, 523 .exited_comptime = ctx.exited_comptime, 524 .options = ctx.options, 525 }; 526 527 if (ctx.options.ptr_slice_inline) try arrayOpen(ctx, run); 528 529 var count: usize = 0; 530 for (value, 0..) |val, idx| { 531 count += 1; 532 533 indent( 534 next_ctx, 535 next_ctx.options.ptr_slice_inline, 536 run, 537 ); 538 539 run.setColor(ctx, .dim); 540 if (idx != 0 and ctx.options.ptr_slice_inline) 541 try run.write(", "); 542 543 if (!ctx.options.ptr_slice_inline or ctx.options.ptr_slice_always_show_index) { 544 try run.write(ctx.options.theme.index_open); 545 try run.print("{}", .{idx}); 546 try run.write(ctx.options.theme.index_close ++ 547 ctx.options.theme.index_value_sep); 548 } 549 550 run.resetColor(); 551 552 try innerFmt(ptr.child, next_ctx, run, val, .{ .skip_type_name = true }); 553 } 554 555 if (count == 0) { 556 run.setColor(ctx, .dim); 557 try run.write(ctx.options.theme.array_open); 558 try run.write(ctx.options.theme.array_close); 559 run.resetColor(); 560 } 561 562 if (ctx.options.ptr_slice_inline) try arrayClose(ctx, run); 563} 564 565inline fn formatPtrMany( 566 comptime ctx: Context, 567 comptime ptr: Type.Pointer, 568 run: *const Runtime, 569 value: anytype, 570) !void { 571 _ = ptr; 572 run.setColor(ctx, .dim); 573 run.setColor(ctx, .many_ptr); 574 try run.print("{*}", .{value}); 575 run.resetColor(); 576} 577 578inline fn formatString( 579 comptime ctx: Context, 580 run: *const Runtime, 581 value: anytype, 582) !void { 583 run.setColor(ctx, .string); 584 try run.write("\""); 585 try run.write(value); 586 try run.write("\""); 587 run.resetColor(); 588} 589 590inline fn formatErrorSet( 591 comptime ctx: Context, 592 run: *const Runtime, 593 value: anyerror, 594) !void { 595 run.setColor(ctx, .@"error"); 596 try run.write("error."); 597 try run.write(@errorName(value)); 598 run.resetColor(); 599} 600 601inline fn formatErrorUnion( 602 comptime ctx: Context, 603 run: *const Runtime, 604 value: anytype, 605) !void { 606 const val = value catch |err| return formatErrorSet(ctx, run, err); 607 return innerFmt( 608 @TypeOf(val), 609 ctx, 610 run, 611 val, 612 .{ .skip_type_name = true }, 613 ); 614} 615 616inline fn formatFn( 617 comptime T: type, 618 comptime ctx: Context, 619 run: *const Runtime, 620) !void { 621 run.setColor(ctx, .dim); 622 run.setColor(ctx, .type); 623 run.setColorRaw(.bold); 624 625 try run.write(@typeName(T)); 626 627 run.resetColor(); 628} 629 630inline fn indent( 631 comptime ctx: Context, 632 comptime inline_option: bool, 633 run: *const Runtime, 634) void { 635 if (inline_option) return; 636 637 const text: [ctx.depth * ctx.options.theme.indent_width]u8 = @splat(' '); 638 run.write("\n" ++ text) catch {}; 639} 640 641fn arrayOpen(comptime ctx: Context, run: *const Runtime) !void { 642 run.setColor(ctx, .dim); 643 try run.write(ctx.options.theme.array_open ++ " "); 644 run.resetColor(); 645} 646 647fn arrayClose(comptime ctx: Context, run: *const Runtime) !void { 648 run.setColor(ctx, .dim); 649 try run.write(" " ++ ctx.options.theme.array_close); 650 run.resetColor(); 651}