| 1 | const builtin = @import("builtin"); |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const Io = std.Io; |
| 5 | const Dir = std.Io.Dir; |
| 6 | const Writer = std.Io.Writer; |
| 7 | const fatal = std.process.fatal; |
| 8 | const mem = std.mem; |
| 9 | const process = std.process; |
| 10 | const Allocator = std.mem.Allocator; |
| 11 | const testing = std.testing; |
| 12 | const getExternalExecutor = std.zig.system.getExternalExecutor; |
| 13 | |
| 14 | const max_doc_file_size = 10 * 1024 * 1024; |
| 15 | |
| 16 | const usage = |
| 17 | \\Usage: doctest [options] -i input -o output |
| 18 | \\ |
| 19 | \\ Compiles and possibly runs a code example, capturing output and rendering |
| 20 | \\ it to HTML documentation. |
| 21 | \\ |
| 22 | \\Options: |
| 23 | \\ -h, --help Print this help and exit |
| 24 | \\ -i input Source code file path |
| 25 | \\ -o output Where to write output HTML docs to |
| 26 | \\ --zig zig Path to the zig compiler |
| 27 | \\ --zig-lib-dir dir Override the zig compiler library path |
| 28 | \\ --cache-root dir Path to local .zig-cache/ |
| 29 | \\ |
| 30 | ; |
| 31 | |
| 32 | pub fn main(init: std.process.Init) !void { |
| 33 | const arena = init.arena.allocator(); |
| 34 | const io = init.io; |
| 35 | const environ_map = init.environ_map; |
| 36 | const cwd_path = try std.process.currentPathAlloc(io, arena); |
| 37 | |
| 38 | try environ_map.put("CLICOLOR_FORCE", "1"); |
| 39 | |
| 40 | var args_it = try init.minimal.args.iterateAllocator(arena); |
| 41 | if (!args_it.skip()) fatal("missing argv[0]", .{}); |
| 42 | |
| 43 | var opt_input: ?[]const u8 = null; |
| 44 | var opt_output: ?[]const u8 = null; |
| 45 | var opt_zig: ?[]const u8 = null; |
| 46 | var opt_zig_lib_dir: ?[]const u8 = null; |
| 47 | var opt_cache_root: ?[]const u8 = null; |
| 48 | |
| 49 | while (args_it.next()) |arg| { |
| 50 | if (mem.startsWith(u8, arg, "-")) { |
| 51 | if (mem.eql(u8, arg, "-h") or mem.eql(u8, arg, "--help")) { |
| 52 | try Io.File.stdout().writeStreamingAll(io, usage); |
| 53 | process.exit(0); |
| 54 | } else if (mem.eql(u8, arg, "-i")) { |
| 55 | opt_input = args_it.next() orelse fatal("expected parameter after -i", .{}); |
| 56 | } else if (mem.eql(u8, arg, "-o")) { |
| 57 | opt_output = args_it.next() orelse fatal("expected parameter after -o", .{}); |
| 58 | } else if (mem.eql(u8, arg, "--zig")) { |
| 59 | opt_zig = args_it.next() orelse fatal("expected parameter after --zig", .{}); |
| 60 | } else if (mem.eql(u8, arg, "--zig-lib-dir")) { |
| 61 | opt_zig_lib_dir = args_it.next() orelse fatal("expected parameter after --zig-lib-dir", .{}); |
| 62 | } else if (mem.eql(u8, arg, "--cache-root")) { |
| 63 | opt_cache_root = args_it.next() orelse fatal("expected parameter after --cache-root", .{}); |
| 64 | } else { |
| 65 | fatal("unrecognized option: '{s}'", .{arg}); |
| 66 | } |
| 67 | } else { |
| 68 | fatal("unexpected positional argument: '{s}'", .{arg}); |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | const input_path = opt_input orelse fatal("missing input file (-i)", .{}); |
| 73 | const output_path = opt_output orelse fatal("missing output file (-o)", .{}); |
| 74 | const zig_path = opt_zig orelse fatal("missing zig compiler path (--zig)", .{}); |
| 75 | const cache_root = opt_cache_root orelse fatal("missing cache root path (--cache-root)", .{}); |
| 76 | |
| 77 | const source_bytes = try Dir.cwd().readFileAlloc(io, input_path, arena, .limited(std.math.maxInt(u32))); |
| 78 | const code = try parseManifest(arena, source_bytes); |
| 79 | const source = stripManifest(source_bytes); |
| 80 | |
| 81 | var random_integer: u64 = undefined; |
| 82 | io.random(@ptrCast(&random_integer)); |
| 83 | |
| 84 | const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ cache_root, random_integer }); |
| 85 | Dir.cwd().createDirPath(io, tmp_dir_path) catch |err| |
| 86 | fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err }); |
| 87 | defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{ |
| 88 | tmp_dir_path, err, |
| 89 | }); |
| 90 | |
| 91 | var out_file = try Dir.cwd().createFile(io, output_path, .{}); |
| 92 | defer out_file.close(io); |
| 93 | var out_file_buffer: [4096]u8 = undefined; |
| 94 | var out_file_writer = out_file.writer(io, &out_file_buffer); |
| 95 | |
| 96 | const out = &out_file_writer.interface; |
| 97 | |
| 98 | try printSourceBlock(arena, out, source, Dir.path.basename(input_path)); |
| 99 | try printOutput( |
| 100 | arena, |
| 101 | io, |
| 102 | out, |
| 103 | code, |
| 104 | tmp_dir_path, |
| 105 | try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_path), |
| 106 | try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, input_path), |
| 107 | if (opt_zig_lib_dir) |zig_lib_dir| |
| 108 | try Dir.path.relative(arena, cwd_path, environ_map, tmp_dir_path, zig_lib_dir) |
| 109 | else |
| 110 | null, |
| 111 | environ_map, |
| 112 | ); |
| 113 | |
| 114 | try out_file_writer.end(); |
| 115 | } |
| 116 | |
| 117 | fn printOutput( |
| 118 | arena: Allocator, |
| 119 | io: Io, |
| 120 | out: *Writer, |
| 121 | code: Code, |
| 122 | /// Relative to this process' cwd. |
| 123 | tmp_dir_path: []const u8, |
| 124 | /// Relative to `tmp_dir_path`. |
| 125 | zig_exe: []const u8, |
| 126 | /// Relative to `tmp_dir_path`. |
| 127 | input_path: []const u8, |
| 128 | /// Relative to `tmp_dir_path`. |
| 129 | opt_zig_lib_dir: ?[]const u8, |
| 130 | environ_map: *const process.Environ.Map, |
| 131 | ) !void { |
| 132 | const host = try std.zig.system.resolveTargetQuery(io, .{}); |
| 133 | const obj_ext = builtin.object_format.fileExt(builtin.cpu.arch); |
| 134 | const print = std.debug.print; |
| 135 | |
| 136 | var shell_buffer: Writer.Allocating = .init(arena); |
| 137 | defer shell_buffer.deinit(); |
| 138 | const shell_out = &shell_buffer.writer; |
| 139 | |
| 140 | const code_name = Dir.path.stem(input_path); |
| 141 | |
| 142 | switch (code.id) { |
| 143 | .exe => |expected_outcome| code_block: { |
| 144 | var build_args = std.array_list.Managed([]const u8).init(arena); |
| 145 | defer build_args.deinit(); |
| 146 | try build_args.appendSlice(&[_][]const u8{ |
| 147 | zig_exe, "build-exe", |
| 148 | "--name", code_name, |
| 149 | "--color", "on", |
| 150 | input_path, |
| 151 | }); |
| 152 | if (opt_zig_lib_dir) |zig_lib_dir| { |
| 153 | try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); |
| 154 | } |
| 155 | |
| 156 | try shell_out.print("$ zig build-exe {s}.zig ", .{code_name}); |
| 157 | |
| 158 | switch (code.mode) { |
| 159 | .debug => {}, |
| 160 | else => { |
| 161 | try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); |
| 162 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); |
| 163 | }, |
| 164 | } |
| 165 | for (code.link_objects) |link_object| { |
| 166 | const name_with_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ link_object, obj_ext }); |
| 167 | try build_args.append(name_with_ext); |
| 168 | try shell_out.print("{s} ", .{name_with_ext}); |
| 169 | } |
| 170 | if (code.link_libc) { |
| 171 | try build_args.append("-lc"); |
| 172 | try shell_out.print("-lc ", .{}); |
| 173 | } |
| 174 | |
| 175 | if (code.target_str) |triple| { |
| 176 | try build_args.appendSlice(&[_][]const u8{ "-target", triple }); |
| 177 | try shell_out.print("-target {s} ", .{triple}); |
| 178 | } |
| 179 | if (code.use_llvm) |use_llvm| { |
| 180 | if (use_llvm) { |
| 181 | try build_args.append("-fllvm"); |
| 182 | try shell_out.print("-fllvm", .{}); |
| 183 | } else { |
| 184 | try build_args.append("-fno-llvm"); |
| 185 | try shell_out.print("-fno-llvm", .{}); |
| 186 | } |
| 187 | } |
| 188 | for (code.additional_options) |option| { |
| 189 | try build_args.append(option); |
| 190 | try shell_out.print("{s} ", .{option}); |
| 191 | } |
| 192 | |
| 193 | try shell_out.print("\n", .{}); |
| 194 | |
| 195 | if (expected_outcome == .build_fail) { |
| 196 | const result = try process.run(arena, io, .{ |
| 197 | .argv = build_args.items, |
| 198 | .cwd = .{ .path = tmp_dir_path }, |
| 199 | .environ_map = environ_map, |
| 200 | }); |
| 201 | switch (result.term) { |
| 202 | .exited => |exit_code| { |
| 203 | if (exit_code == 0) { |
| 204 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 205 | dumpArgs(build_args.items); |
| 206 | fatal("example incorrectly compiled", .{}); |
| 207 | } |
| 208 | }, |
| 209 | else => { |
| 210 | print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 211 | dumpArgs(build_args.items); |
| 212 | fatal("example compile crashed", .{}); |
| 213 | }, |
| 214 | } |
| 215 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 216 | const colored_stderr = try termColor(arena, escaped_stderr); |
| 217 | try shell_out.writeAll(colored_stderr); |
| 218 | break :code_block; |
| 219 | } |
| 220 | const exec_result = run(arena, io, environ_map, tmp_dir_path, build_args.items) catch |
| 221 | fatal("example failed to compile", .{}); |
| 222 | _ = exec_result; |
| 223 | |
| 224 | if (code.target_str) |triple| { |
| 225 | if (mem.startsWith(u8, triple, "wasm32") or |
| 226 | mem.startsWith(u8, triple, "riscv64-linux") or |
| 227 | (mem.startsWith(u8, triple, "x86_64-linux") and |
| 228 | builtin.os.tag != .linux or builtin.cpu.arch != .x86_64)) |
| 229 | { |
| 230 | // skip execution |
| 231 | break :code_block; |
| 232 | } |
| 233 | } |
| 234 | const target_query = try std.Target.Query.parse(.{ |
| 235 | .arch_os_abi = code.target_str orelse "native", |
| 236 | }); |
| 237 | const target = try std.zig.system.resolveTargetQuery(io, target_query); |
| 238 | |
| 239 | const path_to_exe = try std.fmt.allocPrint(arena, "./{s}{s}", .{ |
| 240 | code_name, target.exeFileExt(), |
| 241 | }); |
| 242 | const run_args = &[_][]const u8{path_to_exe}; |
| 243 | |
| 244 | var exited_with_signal = false; |
| 245 | |
| 246 | const result = if (expected_outcome == .fail) blk: { |
| 247 | const result = try process.run(arena, io, .{ |
| 248 | .argv = run_args, |
| 249 | .environ_map = environ_map, |
| 250 | .cwd = .{ .path = tmp_dir_path }, |
| 251 | }); |
| 252 | switch (result.term) { |
| 253 | .exited => |exit_code| { |
| 254 | if (exit_code == 0) { |
| 255 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 256 | dumpArgs(run_args); |
| 257 | fatal("example incorrectly compiled", .{}); |
| 258 | } |
| 259 | }, |
| 260 | .signal => exited_with_signal = true, |
| 261 | else => {}, |
| 262 | } |
| 263 | break :blk result; |
| 264 | } else blk: { |
| 265 | break :blk run(arena, io, environ_map, tmp_dir_path, run_args) catch |
| 266 | fatal("example crashed", .{}); |
| 267 | }; |
| 268 | |
| 269 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 270 | const escaped_stdout = try escapeHtml(arena, result.stdout); |
| 271 | |
| 272 | const colored_stderr = try termColor(arena, escaped_stderr); |
| 273 | const colored_stdout = try termColor(arena, escaped_stdout); |
| 274 | |
| 275 | try shell_out.print("$ ./{s}\n{s}{s}", .{ code_name, colored_stdout, colored_stderr }); |
| 276 | if (exited_with_signal) { |
| 277 | try shell_out.print("(process terminated by signal)", .{}); |
| 278 | } |
| 279 | try shell_out.writeAll("\n"); |
| 280 | }, |
| 281 | .@"test" => { |
| 282 | var test_args = std.array_list.Managed([]const u8).init(arena); |
| 283 | defer test_args.deinit(); |
| 284 | |
| 285 | try test_args.appendSlice(&[_][]const u8{ |
| 286 | zig_exe, "test", input_path, |
| 287 | }); |
| 288 | if (opt_zig_lib_dir) |zig_lib_dir| { |
| 289 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); |
| 290 | } |
| 291 | try shell_out.print("$ zig test {s}.zig ", .{code_name}); |
| 292 | |
| 293 | switch (code.mode) { |
| 294 | .debug => {}, |
| 295 | else => { |
| 296 | try test_args.appendSlice(&[_][]const u8{ |
| 297 | "-O", @tagName(code.mode), |
| 298 | }); |
| 299 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); |
| 300 | }, |
| 301 | } |
| 302 | if (code.link_libc) { |
| 303 | try test_args.append("-lc"); |
| 304 | try shell_out.print("-lc ", .{}); |
| 305 | } |
| 306 | if (code.target_str) |triple| { |
| 307 | try test_args.appendSlice(&[_][]const u8{ "-target", triple }); |
| 308 | try shell_out.print("-target {s} ", .{triple}); |
| 309 | |
| 310 | const target_query = try std.Target.Query.parse(.{ |
| 311 | .arch_os_abi = triple, |
| 312 | }); |
| 313 | const target = try std.zig.system.resolveTargetQuery(io, target_query); |
| 314 | switch (getExternalExecutor(io, &target, .{ |
| 315 | .host_cpu_arch = host.cpu.arch, |
| 316 | .host_os_tag = host.os.tag, |
| 317 | .link_libc = code.link_libc, |
| 318 | })) { |
| 319 | .native => {}, |
| 320 | else => { |
| 321 | try test_args.appendSlice(&[_][]const u8{"--test-no-exec"}); |
| 322 | try shell_out.writeAll("--test-no-exec"); |
| 323 | }, |
| 324 | } |
| 325 | } |
| 326 | if (code.use_llvm) |use_llvm| { |
| 327 | if (use_llvm) { |
| 328 | try test_args.append("-fllvm"); |
| 329 | try shell_out.print("-fllvm", .{}); |
| 330 | } else { |
| 331 | try test_args.append("-fno-llvm"); |
| 332 | try shell_out.print("-fno-llvm", .{}); |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | const result = run(arena, io, environ_map, tmp_dir_path, test_args.items) catch |
| 337 | fatal("test failed", .{}); |
| 338 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 339 | const escaped_stdout = try escapeHtml(arena, result.stdout); |
| 340 | try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); |
| 341 | }, |
| 342 | .test_error => |error_match| { |
| 343 | var test_args = std.array_list.Managed([]const u8).init(arena); |
| 344 | defer test_args.deinit(); |
| 345 | |
| 346 | try test_args.appendSlice(&[_][]const u8{ |
| 347 | zig_exe, "test", |
| 348 | "--color", "on", |
| 349 | input_path, |
| 350 | }); |
| 351 | if (opt_zig_lib_dir) |zig_lib_dir| { |
| 352 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); |
| 353 | } |
| 354 | try shell_out.print("$ zig test {s}.zig ", .{code_name}); |
| 355 | |
| 356 | switch (code.mode) { |
| 357 | .debug => {}, |
| 358 | else => { |
| 359 | try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); |
| 360 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); |
| 361 | }, |
| 362 | } |
| 363 | if (code.link_libc) { |
| 364 | try test_args.append("-lc"); |
| 365 | try shell_out.print("-lc ", .{}); |
| 366 | } |
| 367 | const result = try process.run(arena, io, .{ |
| 368 | .argv = test_args.items, |
| 369 | .environ_map = environ_map, |
| 370 | .cwd = .{ .path = tmp_dir_path }, |
| 371 | }); |
| 372 | switch (result.term) { |
| 373 | .exited => |exit_code| { |
| 374 | if (exit_code == 0) { |
| 375 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 376 | dumpArgs(test_args.items); |
| 377 | fatal("example incorrectly compiled", .{}); |
| 378 | } |
| 379 | }, |
| 380 | else => { |
| 381 | print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 382 | dumpArgs(test_args.items); |
| 383 | fatal("example compile crashed", .{}); |
| 384 | }, |
| 385 | } |
| 386 | if (mem.find(u8, result.stderr, error_match) == null) { |
| 387 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); |
| 388 | fatal("example did not have expected compile error", .{}); |
| 389 | } |
| 390 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 391 | const colored_stderr = try termColor(arena, escaped_stderr); |
| 392 | try shell_out.print("\n{s}\n", .{colored_stderr}); |
| 393 | }, |
| 394 | .test_safety => |error_match| { |
| 395 | var test_args = std.array_list.Managed([]const u8).init(arena); |
| 396 | defer test_args.deinit(); |
| 397 | |
| 398 | try test_args.appendSlice(&[_][]const u8{ |
| 399 | zig_exe, "test", |
| 400 | input_path, |
| 401 | }); |
| 402 | if (opt_zig_lib_dir) |zig_lib_dir| { |
| 403 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); |
| 404 | } |
| 405 | var mode_arg: []const u8 = ""; |
| 406 | switch (code.mode) { |
| 407 | .debug => {}, |
| 408 | .safe => { |
| 409 | try test_args.append("-Osafe"); |
| 410 | mode_arg = "-Osafe"; |
| 411 | }, |
| 412 | .fast => { |
| 413 | try test_args.append("-Ofast"); |
| 414 | mode_arg = "-Ofast"; |
| 415 | }, |
| 416 | .small => { |
| 417 | try test_args.append("-Osmall"); |
| 418 | mode_arg = "-Osmall"; |
| 419 | }, |
| 420 | } |
| 421 | |
| 422 | const result = try process.run(arena, io, .{ |
| 423 | .argv = test_args.items, |
| 424 | .environ_map = environ_map, |
| 425 | .cwd = .{ .path = tmp_dir_path }, |
| 426 | }); |
| 427 | switch (result.term) { |
| 428 | .exited => |exit_code| { |
| 429 | if (exit_code == 0) { |
| 430 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 431 | dumpArgs(test_args.items); |
| 432 | fatal("example test incorrectly succeeded", .{}); |
| 433 | } |
| 434 | }, |
| 435 | else => { |
| 436 | print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 437 | dumpArgs(test_args.items); |
| 438 | fatal("example compile crashed", .{}); |
| 439 | }, |
| 440 | } |
| 441 | if (mem.find(u8, result.stderr, error_match) == null) { |
| 442 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); |
| 443 | fatal("example did not have expected runtime safety error message", .{}); |
| 444 | } |
| 445 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 446 | const colored_stderr = try termColor(arena, escaped_stderr); |
| 447 | try shell_out.print("$ zig test {s}.zig {s}\n{s}\n", .{ |
| 448 | code_name, |
| 449 | mode_arg, |
| 450 | colored_stderr, |
| 451 | }); |
| 452 | }, |
| 453 | .obj => |maybe_error_match| { |
| 454 | const name_plus_obj_ext = try std.fmt.allocPrint(arena, "{s}{s}", .{ code_name, obj_ext }); |
| 455 | var build_args = std.array_list.Managed([]const u8).init(arena); |
| 456 | defer build_args.deinit(); |
| 457 | |
| 458 | try build_args.appendSlice(&[_][]const u8{ |
| 459 | zig_exe, "build-obj", |
| 460 | "--color", "on", |
| 461 | "--name", code_name, |
| 462 | input_path, try std.fmt.allocPrint(arena, "-femit-bin={s}", .{name_plus_obj_ext}), |
| 463 | }); |
| 464 | if (opt_zig_lib_dir) |zig_lib_dir| { |
| 465 | try build_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); |
| 466 | } |
| 467 | |
| 468 | try shell_out.print("$ zig build-obj {s}.zig ", .{code_name}); |
| 469 | |
| 470 | switch (code.mode) { |
| 471 | .debug => {}, |
| 472 | else => { |
| 473 | try build_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); |
| 474 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); |
| 475 | }, |
| 476 | } |
| 477 | |
| 478 | if (code.target_str) |triple| { |
| 479 | try build_args.appendSlice(&[_][]const u8{ "-target", triple }); |
| 480 | try shell_out.print("-target {s} ", .{triple}); |
| 481 | } |
| 482 | if (code.use_llvm) |use_llvm| { |
| 483 | if (use_llvm) { |
| 484 | try build_args.append("-fllvm"); |
| 485 | try shell_out.print("-fllvm", .{}); |
| 486 | } else { |
| 487 | try build_args.append("-fno-llvm"); |
| 488 | try shell_out.print("-fno-llvm", .{}); |
| 489 | } |
| 490 | } |
| 491 | for (code.additional_options) |option| { |
| 492 | try build_args.append(option); |
| 493 | try shell_out.print("{s} ", .{option}); |
| 494 | } |
| 495 | |
| 496 | if (maybe_error_match) |error_match| { |
| 497 | const result = try process.run(arena, io, .{ |
| 498 | .argv = build_args.items, |
| 499 | .environ_map = environ_map, |
| 500 | .cwd = .{ .path = tmp_dir_path }, |
| 501 | }); |
| 502 | switch (result.term) { |
| 503 | .exited => |exit_code| { |
| 504 | if (exit_code == 0) { |
| 505 | print("{s}\nThe following command incorrectly succeeded:\n", .{result.stderr}); |
| 506 | dumpArgs(build_args.items); |
| 507 | fatal("example build incorrectly succeeded", .{}); |
| 508 | } |
| 509 | }, |
| 510 | else => { |
| 511 | print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 512 | dumpArgs(build_args.items); |
| 513 | fatal("example compile crashed", .{}); |
| 514 | }, |
| 515 | } |
| 516 | if (mem.find(u8, result.stderr, error_match) == null) { |
| 517 | print("{s}\nExpected to find '{s}' in stderr\n", .{ result.stderr, error_match }); |
| 518 | fatal("example did not have expected compile error message", .{}); |
| 519 | } |
| 520 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 521 | const colored_stderr = try termColor(arena, escaped_stderr); |
| 522 | try shell_out.print("\n{s} ", .{colored_stderr}); |
| 523 | } else { |
| 524 | _ = run(arena, io, environ_map, tmp_dir_path, build_args.items) catch fatal("example failed to compile", .{}); |
| 525 | } |
| 526 | try shell_out.writeAll("\n"); |
| 527 | }, |
| 528 | .lib => { |
| 529 | const bin_basename = try std.zig.binNameAlloc(arena, .{ |
| 530 | .root_name = code_name, |
| 531 | .cpu_arch = builtin.target.cpu.arch, |
| 532 | .os_tag = builtin.target.os.tag, |
| 533 | .ofmt = builtin.target.ofmt, |
| 534 | .abi = builtin.target.abi, |
| 535 | .output_mode = .Lib, |
| 536 | }); |
| 537 | |
| 538 | var test_args = std.array_list.Managed([]const u8).init(arena); |
| 539 | defer test_args.deinit(); |
| 540 | |
| 541 | try test_args.appendSlice(&[_][]const u8{ |
| 542 | zig_exe, "build-lib", |
| 543 | input_path, try std.fmt.allocPrint(arena, "-femit-bin={s}", .{bin_basename}), |
| 544 | }); |
| 545 | if (opt_zig_lib_dir) |zig_lib_dir| { |
| 546 | try test_args.appendSlice(&.{ "--zig-lib-dir", zig_lib_dir }); |
| 547 | } |
| 548 | try shell_out.print("$ zig build-lib {s}.zig ", .{code_name}); |
| 549 | |
| 550 | switch (code.mode) { |
| 551 | .debug => {}, |
| 552 | else => { |
| 553 | try test_args.appendSlice(&[_][]const u8{ "-O", @tagName(code.mode) }); |
| 554 | try shell_out.print("-O {s} ", .{@tagName(code.mode)}); |
| 555 | }, |
| 556 | } |
| 557 | if (code.target_str) |triple| { |
| 558 | try test_args.appendSlice(&[_][]const u8{ "-target", triple }); |
| 559 | try shell_out.print("-target {s} ", .{triple}); |
| 560 | } |
| 561 | if (code.use_llvm) |use_llvm| { |
| 562 | if (use_llvm) { |
| 563 | try test_args.append("-fllvm"); |
| 564 | try shell_out.print("-fllvm", .{}); |
| 565 | } else { |
| 566 | try test_args.append("-fno-llvm"); |
| 567 | try shell_out.print("-fno-llvm", .{}); |
| 568 | } |
| 569 | } |
| 570 | if (code.link_mode) |link_mode| { |
| 571 | switch (link_mode) { |
| 572 | .static => { |
| 573 | try test_args.append("-static"); |
| 574 | try shell_out.print("-static ", .{}); |
| 575 | }, |
| 576 | .dynamic => { |
| 577 | try test_args.append("-dynamic"); |
| 578 | try shell_out.print("-dynamic ", .{}); |
| 579 | }, |
| 580 | } |
| 581 | } |
| 582 | for (code.additional_options) |option| { |
| 583 | try test_args.append(option); |
| 584 | try shell_out.print("{s} ", .{option}); |
| 585 | } |
| 586 | const result = run(arena, io, environ_map, tmp_dir_path, test_args.items) catch fatal("test failed", .{}); |
| 587 | const escaped_stderr = try escapeHtml(arena, result.stderr); |
| 588 | const escaped_stdout = try escapeHtml(arena, result.stdout); |
| 589 | try shell_out.print("\n{s}{s}\n", .{ escaped_stderr, escaped_stdout }); |
| 590 | }, |
| 591 | } |
| 592 | |
| 593 | if (!code.just_check_syntax) { |
| 594 | try printShell(out, shell_buffer.written(), false); |
| 595 | } |
| 596 | } |
| 597 | |
| 598 | fn dumpArgs(args: []const []const u8) void { |
| 599 | for (args) |arg| |
| 600 | std.debug.print("{s} ", .{arg}) |
| 601 | else |
| 602 | std.debug.print("\n", .{}); |
| 603 | } |
| 604 | |
| 605 | fn printSourceBlock(arena: Allocator, out: *Writer, source_bytes: []const u8, name: []const u8) !void { |
| 606 | try out.print("<figure><figcaption class=\"{s}-cap\"><cite class=\"file\">{s}</cite></figcaption><pre>", .{ |
| 607 | "zig", name, |
| 608 | }); |
| 609 | try tokenizeAndPrint(arena, out, source_bytes); |
| 610 | try out.writeAll("</pre></figure>"); |
| 611 | } |
| 612 | |
| 613 | fn tokenizeAndPrint(arena: Allocator, out: *Writer, raw_src: []const u8) !void { |
| 614 | const src_non_terminated = mem.trim(u8, raw_src, " \r\n"); |
| 615 | const src = try arena.dupeSentinel(u8, src_non_terminated, 0); |
| 616 | |
| 617 | try out.writeAll("<code>"); |
| 618 | var tokenizer = std.zig.Tokenizer.init(src); |
| 619 | var index: usize = 0; |
| 620 | var next_tok_is_fn = false; |
| 621 | while (true) { |
| 622 | const prev_tok_was_fn = next_tok_is_fn; |
| 623 | next_tok_is_fn = false; |
| 624 | |
| 625 | const token = tokenizer.next(); |
| 626 | if (mem.find(u8, src[index..token.loc.start], "//")) |comment_start_off| { |
| 627 | // render one comment |
| 628 | const comment_start = index + comment_start_off; |
| 629 | const comment_end_off = mem.find(u8, src[comment_start..token.loc.start], "\n"); |
| 630 | const comment_end = if (comment_end_off) |o| comment_start + o else token.loc.start; |
| 631 | |
| 632 | try writeEscapedLines(out, src[index..comment_start]); |
| 633 | try out.writeAll("<span class=\"tok-comment\">"); |
| 634 | try writeEscaped(out, src[comment_start..comment_end]); |
| 635 | try out.writeAll("</span>"); |
| 636 | index = comment_end; |
| 637 | tokenizer.index = index; |
| 638 | continue; |
| 639 | } |
| 640 | |
| 641 | try writeEscapedLines(out, src[index..token.loc.start]); |
| 642 | switch (token.tag) { |
| 643 | .eof => break, |
| 644 | |
| 645 | .keyword_addrspace, |
| 646 | .keyword_align, |
| 647 | .keyword_and, |
| 648 | .keyword_asm, |
| 649 | .keyword_break, |
| 650 | .keyword_catch, |
| 651 | .keyword_comptime, |
| 652 | .keyword_const, |
| 653 | .keyword_continue, |
| 654 | .keyword_defer, |
| 655 | .keyword_else, |
| 656 | .keyword_enum, |
| 657 | .keyword_errdefer, |
| 658 | .keyword_error, |
| 659 | .keyword_export, |
| 660 | .keyword_extern, |
| 661 | .keyword_for, |
| 662 | .keyword_if, |
| 663 | .keyword_inline, |
| 664 | .keyword_noalias, |
| 665 | .keyword_noinline, |
| 666 | .keyword_nosuspend, |
| 667 | .keyword_opaque, |
| 668 | .keyword_or, |
| 669 | .keyword_orelse, |
| 670 | .keyword_packed, |
| 671 | .keyword_anyframe, |
| 672 | .keyword_pub, |
| 673 | .keyword_resume, |
| 674 | .keyword_return, |
| 675 | .keyword_linksection, |
| 676 | .keyword_callconv, |
| 677 | .keyword_struct, |
| 678 | .keyword_suspend, |
| 679 | .keyword_switch, |
| 680 | .keyword_test, |
| 681 | .keyword_threadlocal, |
| 682 | .keyword_try, |
| 683 | .keyword_union, |
| 684 | .keyword_unreachable, |
| 685 | .keyword_var, |
| 686 | .keyword_volatile, |
| 687 | .keyword_allowzero, |
| 688 | .keyword_while, |
| 689 | .keyword_anytype, |
| 690 | => { |
| 691 | try out.writeAll("<span class=\"tok-kw\">"); |
| 692 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 693 | try out.writeAll("</span>"); |
| 694 | }, |
| 695 | |
| 696 | .keyword_fn => { |
| 697 | try out.writeAll("<span class=\"tok-kw\">"); |
| 698 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 699 | try out.writeAll("</span>"); |
| 700 | next_tok_is_fn = true; |
| 701 | }, |
| 702 | |
| 703 | .string_literal, |
| 704 | .multiline_string_literal_line, |
| 705 | .char_literal, |
| 706 | => { |
| 707 | try out.writeAll("<span class=\"tok-str\">"); |
| 708 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 709 | try out.writeAll("</span>"); |
| 710 | }, |
| 711 | |
| 712 | .builtin => { |
| 713 | try out.writeAll("<span class=\"tok-builtin\">"); |
| 714 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 715 | try out.writeAll("</span>"); |
| 716 | }, |
| 717 | |
| 718 | .doc_comment, |
| 719 | .container_doc_comment, |
| 720 | => { |
| 721 | try out.writeAll("<span class=\"tok-comment\">"); |
| 722 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 723 | try out.writeAll("</span>"); |
| 724 | }, |
| 725 | |
| 726 | .identifier => { |
| 727 | const tok_bytes = src[token.loc.start..token.loc.end]; |
| 728 | if (mem.eql(u8, tok_bytes, "undefined") or |
| 729 | mem.eql(u8, tok_bytes, "null") or |
| 730 | mem.eql(u8, tok_bytes, "true") or |
| 731 | mem.eql(u8, tok_bytes, "false")) |
| 732 | { |
| 733 | try out.writeAll("<span class=\"tok-null\">"); |
| 734 | try writeEscaped(out, tok_bytes); |
| 735 | try out.writeAll("</span>"); |
| 736 | } else if (prev_tok_was_fn) { |
| 737 | try out.writeAll("<span class=\"tok-fn\">"); |
| 738 | try writeEscaped(out, tok_bytes); |
| 739 | try out.writeAll("</span>"); |
| 740 | } else { |
| 741 | const is_int = blk: { |
| 742 | if (src[token.loc.start] != 'i' and src[token.loc.start] != 'u') |
| 743 | break :blk false; |
| 744 | var i = token.loc.start + 1; |
| 745 | if (i == token.loc.end) |
| 746 | break :blk false; |
| 747 | while (i != token.loc.end) : (i += 1) { |
| 748 | if (src[i] < '0' or src[i] > '9') |
| 749 | break :blk false; |
| 750 | } |
| 751 | break :blk true; |
| 752 | }; |
| 753 | const isType = std.zig.isPrimitive; |
| 754 | if (is_int or isType(tok_bytes)) { |
| 755 | try out.writeAll("<span class=\"tok-type\">"); |
| 756 | try writeEscaped(out, tok_bytes); |
| 757 | try out.writeAll("</span>"); |
| 758 | } else { |
| 759 | try writeEscaped(out, tok_bytes); |
| 760 | } |
| 761 | } |
| 762 | }, |
| 763 | |
| 764 | .number_literal => { |
| 765 | try out.writeAll("<span class=\"tok-number\">"); |
| 766 | try writeEscaped(out, src[token.loc.start..token.loc.end]); |
| 767 | try out.writeAll("</span>"); |
| 768 | }, |
| 769 | |
| 770 | .bang, |
| 771 | .pipe, |
| 772 | .pipe_pipe, |
| 773 | .pipe_equal, |
| 774 | .equal, |
| 775 | .equal_equal, |
| 776 | .equal_angle_bracket_right, |
| 777 | .bang_equal, |
| 778 | .l_paren, |
| 779 | .r_paren, |
| 780 | .semicolon, |
| 781 | .percent, |
| 782 | .percent_equal, |
| 783 | .l_brace, |
| 784 | .r_brace, |
| 785 | .l_bracket, |
| 786 | .r_bracket, |
| 787 | .period, |
| 788 | .period_asterisk, |
| 789 | .ellipsis2, |
| 790 | .ellipsis3, |
| 791 | .caret, |
| 792 | .caret_equal, |
| 793 | .plus, |
| 794 | .plus_plus, |
| 795 | .plus_equal, |
| 796 | .plus_percent, |
| 797 | .plus_percent_equal, |
| 798 | .plus_pipe, |
| 799 | .plus_pipe_equal, |
| 800 | .minus, |
| 801 | .minus_equal, |
| 802 | .minus_percent, |
| 803 | .minus_percent_equal, |
| 804 | .minus_pipe, |
| 805 | .minus_pipe_equal, |
| 806 | .asterisk, |
| 807 | .asterisk_equal, |
| 808 | .asterisk_percent, |
| 809 | .asterisk_percent_equal, |
| 810 | .asterisk_pipe, |
| 811 | .asterisk_pipe_equal, |
| 812 | .arrow, |
| 813 | .colon, |
| 814 | .slash, |
| 815 | .slash_equal, |
| 816 | .comma, |
| 817 | .ampersand, |
| 818 | .ampersand_equal, |
| 819 | .question_mark, |
| 820 | .angle_bracket_left, |
| 821 | .angle_bracket_left_equal, |
| 822 | .angle_bracket_angle_bracket_left, |
| 823 | .angle_bracket_angle_bracket_left_equal, |
| 824 | .angle_bracket_angle_bracket_left_pipe, |
| 825 | .angle_bracket_angle_bracket_left_pipe_equal, |
| 826 | .angle_bracket_right, |
| 827 | .angle_bracket_right_equal, |
| 828 | .angle_bracket_angle_bracket_right, |
| 829 | .angle_bracket_angle_bracket_right_equal, |
| 830 | .tilde, |
| 831 | => try writeEscaped(out, src[token.loc.start..token.loc.end]), |
| 832 | |
| 833 | .invalid => fatal("syntax error", .{}), |
| 834 | } |
| 835 | index = token.loc.end; |
| 836 | } |
| 837 | try out.writeAll("</code>"); |
| 838 | } |
| 839 | |
| 840 | fn writeEscapedLines(out: *Writer, text: []const u8) !void { |
| 841 | return writeEscaped(out, text); |
| 842 | } |
| 843 | |
| 844 | const Code = struct { |
| 845 | id: Id, |
| 846 | mode: std.builtin.Optimize, |
| 847 | link_objects: []const []const u8, |
| 848 | target_str: ?[]const u8, |
| 849 | link_libc: bool, |
| 850 | link_mode: ?std.builtin.LinkMode, |
| 851 | disable_cache: bool, |
| 852 | just_check_syntax: bool, |
| 853 | additional_options: []const []const u8, |
| 854 | use_llvm: ?bool, |
| 855 | |
| 856 | const Id = union(enum) { |
| 857 | @"test", |
| 858 | test_error: []const u8, |
| 859 | test_safety: []const u8, |
| 860 | exe: ExpectedOutcome, |
| 861 | obj: ?[]const u8, |
| 862 | lib, |
| 863 | }; |
| 864 | |
| 865 | const ExpectedOutcome = enum { |
| 866 | succeed, |
| 867 | fail, |
| 868 | build_fail, |
| 869 | }; |
| 870 | }; |
| 871 | |
| 872 | fn stripManifest(source_bytes: []const u8) []const u8 { |
| 873 | const manifest_start = mem.findLast(u8, source_bytes, "\n\n// ") orelse |
| 874 | fatal("missing manifest comment", .{}); |
| 875 | return source_bytes[0 .. manifest_start + 1]; |
| 876 | } |
| 877 | |
| 878 | fn parseManifest(arena: Allocator, source_bytes: []const u8) !Code { |
| 879 | const manifest_start = mem.findLast(u8, source_bytes, "\n\n// ") orelse |
| 880 | fatal("missing manifest comment", .{}); |
| 881 | var it = mem.tokenizeScalar(u8, source_bytes[manifest_start..], '\n'); |
| 882 | const first_line = skipPrefix(it.next().?); |
| 883 | |
| 884 | var just_check_syntax = false; |
| 885 | const id: Code.Id = if (mem.eql(u8, first_line, "syntax")) blk: { |
| 886 | just_check_syntax = true; |
| 887 | break :blk .{ .obj = null }; |
| 888 | } else if (mem.eql(u8, first_line, "test")) |
| 889 | .@"test" |
| 890 | else if (mem.eql(u8, first_line, "lib")) |
| 891 | .lib |
| 892 | else if (mem.eql(u8, first_line, "obj")) |
| 893 | .{ .obj = null } |
| 894 | else if (mem.startsWith(u8, first_line, "test_error=")) |
| 895 | .{ .test_error = first_line["test_error=".len..] } |
| 896 | else if (mem.startsWith(u8, first_line, "test_safety=")) |
| 897 | .{ .test_safety = first_line["test_safety=".len..] } |
| 898 | else if (mem.startsWith(u8, first_line, "exe=")) |
| 899 | .{ .exe = std.meta.stringToEnum(Code.ExpectedOutcome, first_line["exe=".len..]) orelse |
| 900 | fatal("bad exe expected outcome in line '{s}'", .{first_line}) } |
| 901 | else if (mem.startsWith(u8, first_line, "obj=")) |
| 902 | .{ .obj = first_line["obj=".len..] } |
| 903 | else |
| 904 | fatal("unrecognized manifest id: '{s}'", .{first_line}); |
| 905 | |
| 906 | var mode: std.builtin.Optimize = .debug; |
| 907 | var link_mode: ?std.builtin.LinkMode = null; |
| 908 | var link_objects: std.ArrayList([]const u8) = .empty; |
| 909 | var additional_options: std.ArrayList([]const u8) = .empty; |
| 910 | var target_str: ?[]const u8 = null; |
| 911 | var link_libc = false; |
| 912 | var disable_cache = false; |
| 913 | var use_llvm: ?bool = null; |
| 914 | |
| 915 | while (it.next()) |prefixed_line| { |
| 916 | const line = skipPrefix(prefixed_line); |
| 917 | if (mem.startsWith(u8, line, "optimize=")) { |
| 918 | mode = std.builtin.Optimize.fromString(line["optimize=".len..]) orelse |
| 919 | fatal("bad optimization mode line: {q}", .{line}); |
| 920 | } else if (mem.startsWith(u8, line, "link_mode=")) { |
| 921 | link_mode = std.meta.stringToEnum(std.builtin.LinkMode, line["link_mode=".len..]) orelse |
| 922 | fatal("bad link mode line: {q}", .{line}); |
| 923 | } else if (mem.startsWith(u8, line, "link_object=")) { |
| 924 | try link_objects.append(arena, line["link_object=".len..]); |
| 925 | } else if (mem.startsWith(u8, line, "additional_option=")) { |
| 926 | try additional_options.append(arena, line["additional_option=".len..]); |
| 927 | } else if (mem.startsWith(u8, line, "target=")) { |
| 928 | target_str = line["target=".len..]; |
| 929 | } else if (mem.eql(u8, line, "llvm=true")) { |
| 930 | use_llvm = true; |
| 931 | } else if (mem.eql(u8, line, "llvm=false")) { |
| 932 | use_llvm = false; |
| 933 | } else if (mem.eql(u8, line, "link_libc")) { |
| 934 | link_libc = true; |
| 935 | } else if (mem.eql(u8, line, "disable_cache")) { |
| 936 | disable_cache = true; |
| 937 | } else { |
| 938 | fatal("unrecognized manifest line: {s}", .{line}); |
| 939 | } |
| 940 | } |
| 941 | |
| 942 | return .{ |
| 943 | .id = id, |
| 944 | .mode = mode, |
| 945 | .additional_options = try additional_options.toOwnedSlice(arena), |
| 946 | .link_objects = try link_objects.toOwnedSlice(arena), |
| 947 | .target_str = target_str, |
| 948 | .link_libc = link_libc, |
| 949 | .link_mode = link_mode, |
| 950 | .disable_cache = disable_cache, |
| 951 | .just_check_syntax = just_check_syntax, |
| 952 | .use_llvm = use_llvm, |
| 953 | }; |
| 954 | } |
| 955 | |
| 956 | fn skipPrefix(line: []const u8) []const u8 { |
| 957 | if (!mem.startsWith(u8, line, "// ")) { |
| 958 | fatal("line does not start with '// ': '{s}", .{line}); |
| 959 | } |
| 960 | return line[3..]; |
| 961 | } |
| 962 | |
| 963 | fn escapeHtml(gpa: Allocator, input: []const u8) ![]u8 { |
| 964 | var allocating: Writer.Allocating = .init(gpa); |
| 965 | defer allocating.deinit(); |
| 966 | try writeEscaped(&allocating.writer, input); |
| 967 | return allocating.toOwnedSlice(); |
| 968 | } |
| 969 | |
| 970 | fn writeEscaped(w: *Writer, input: []const u8) !void { |
| 971 | for (input) |c| try switch (c) { |
| 972 | '&' => w.writeAll("&amp;"), |
| 973 | '<' => w.writeAll("&lt;"), |
| 974 | '>' => w.writeAll("&gt;"), |
| 975 | '"' => w.writeAll("&quot;"), |
| 976 | else => w.writeByte(c), |
| 977 | }; |
| 978 | } |
| 979 | |
| 980 | fn termColor(allocator: Allocator, input: []const u8) ![]u8 { |
| 981 | // The SRG sequences generates by the Zig compiler are in the format: |
| 982 | // ESC [ <foreground-color> ; <n> m |
| 983 | // or |
| 984 | // ESC [ <n> m |
| 985 | // |
| 986 | // where |
| 987 | // foreground-color is 31 (red), 32 (green), 36 (cyan) |
| 988 | // n is 0 (reset), 1 (bold), 2 (dim) |
| 989 | // |
| 990 | // Note that 37 (white) is currently not used by the compiler. |
| 991 | // |
| 992 | // See std.debug.TTY.Color. |
| 993 | const supported_sgr_colors = [_]u8{ 31, 32, 36 }; |
| 994 | const supported_sgr_numbers = [_]u8{ 0, 1, 2 }; |
| 995 | |
| 996 | var buf = std.array_list.Managed(u8).init(allocator); |
| 997 | defer buf.deinit(); |
| 998 | |
| 999 | var sgr_param_start_index: usize = undefined; |
| 1000 | var sgr_num: u8 = undefined; |
| 1001 | var sgr_color: u8 = undefined; |
| 1002 | var i: usize = 0; |
| 1003 | var state: enum { |
| 1004 | start, |
| 1005 | escape, |
| 1006 | lbracket, |
| 1007 | number, |
| 1008 | after_number, |
| 1009 | arg, |
| 1010 | arg_number, |
| 1011 | expect_end, |
| 1012 | } = .start; |
| 1013 | var last_new_line: usize = 0; |
| 1014 | var open_span_count: usize = 0; |
| 1015 | while (i < input.len) : (i += 1) { |
| 1016 | const c = input[i]; |
| 1017 | switch (state) { |
| 1018 | .start => switch (c) { |
| 1019 | '\x1b' => state = .escape, |
| 1020 | '\n' => { |
| 1021 | try buf.append(c); |
| 1022 | last_new_line = buf.items.len; |
| 1023 | }, |
| 1024 | else => try buf.append(c), |
| 1025 | }, |
| 1026 | .escape => switch (c) { |
| 1027 | '[' => state = .lbracket, |
| 1028 | else => return error.UnsupportedEscape, |
| 1029 | }, |
| 1030 | .lbracket => switch (c) { |
| 1031 | '0'...'9' => { |
| 1032 | sgr_param_start_index = i; |
| 1033 | state = .number; |
| 1034 | }, |
| 1035 | else => return error.UnsupportedEscape, |
| 1036 | }, |
| 1037 | .number => switch (c) { |
| 1038 | '0'...'9' => {}, |
| 1039 | else => { |
| 1040 | sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10); |
| 1041 | sgr_color = 0; |
| 1042 | state = .after_number; |
| 1043 | i -= 1; |
| 1044 | }, |
| 1045 | }, |
| 1046 | .after_number => switch (c) { |
| 1047 | ';' => state = .arg, |
| 1048 | 'D' => state = .start, |
| 1049 | 'K' => { |
| 1050 | buf.items.len = last_new_line; |
| 1051 | state = .start; |
| 1052 | }, |
| 1053 | else => { |
| 1054 | state = .expect_end; |
| 1055 | i -= 1; |
| 1056 | }, |
| 1057 | }, |
| 1058 | .arg => switch (c) { |
| 1059 | '0'...'9' => { |
| 1060 | sgr_param_start_index = i; |
| 1061 | state = .arg_number; |
| 1062 | }, |
| 1063 | else => return error.UnsupportedEscape, |
| 1064 | }, |
| 1065 | .arg_number => switch (c) { |
| 1066 | '0'...'9' => {}, |
| 1067 | else => { |
| 1068 | // Keep the sequence consistent, foreground color first. |
| 1069 | // 32;1m is equivalent to 1;32m, but the latter will |
| 1070 | // generate an incorrect HTML class without notice. |
| 1071 | sgr_color = sgr_num; |
| 1072 | if (!in(&supported_sgr_colors, sgr_color)) return error.UnsupportedForegroundColor; |
| 1073 | |
| 1074 | sgr_num = try std.fmt.parseInt(u8, input[sgr_param_start_index..i], 10); |
| 1075 | if (!in(&supported_sgr_numbers, sgr_num)) return error.UnsupportedNumber; |
| 1076 | |
| 1077 | state = .expect_end; |
| 1078 | i -= 1; |
| 1079 | }, |
| 1080 | }, |
| 1081 | .expect_end => switch (c) { |
| 1082 | 'm' => { |
| 1083 | state = .start; |
| 1084 | while (open_span_count != 0) : (open_span_count -= 1) { |
| 1085 | try buf.appendSlice("</span>"); |
| 1086 | } |
| 1087 | if (sgr_num == 0) { |
| 1088 | if (sgr_color != 0) return error.UnsupportedColor; |
| 1089 | continue; |
| 1090 | } |
| 1091 | if (sgr_color != 0) { |
| 1092 | try buf.print("<span class=\"sgr-{d}_{d}m\">", .{ sgr_color, sgr_num }); |
| 1093 | } else { |
| 1094 | try buf.print("<span class=\"sgr-{d}m\">", .{sgr_num}); |
| 1095 | } |
| 1096 | open_span_count += 1; |
| 1097 | }, |
| 1098 | else => return error.UnsupportedEscape, |
| 1099 | }, |
| 1100 | } |
| 1101 | } |
| 1102 | return try buf.toOwnedSlice(); |
| 1103 | } |
| 1104 | |
| 1105 | // Returns true if number is in slice. |
| 1106 | fn in(slice: []const u8, number: u8) bool { |
| 1107 | return mem.findScalar(u8, slice, number) != null; |
| 1108 | } |
| 1109 | |
| 1110 | fn run( |
| 1111 | allocator: Allocator, |
| 1112 | io: Io, |
| 1113 | environ_map: *const process.Environ.Map, |
| 1114 | cwd: []const u8, |
| 1115 | args: []const []const u8, |
| 1116 | ) !process.RunResult { |
| 1117 | const result = try process.run(allocator, io, .{ |
| 1118 | .argv = args, |
| 1119 | .environ_map = environ_map, |
| 1120 | .cwd = .{ .path = cwd }, |
| 1121 | }); |
| 1122 | switch (result.term) { |
| 1123 | .exited => |exit_code| { |
| 1124 | if (exit_code != 0) { |
| 1125 | std.debug.print("{s}\nThe following command exited with code {}:\n", .{ result.stderr, exit_code }); |
| 1126 | dumpArgs(args); |
| 1127 | return error.ChildExitError; |
| 1128 | } |
| 1129 | }, |
| 1130 | .signal => |sig| { |
| 1131 | std.debug.print("{s}\nThe following command terminated with signal {t}:\n", .{ result.stderr, sig }); |
| 1132 | dumpArgs(args); |
| 1133 | return error.ChildCrashed; |
| 1134 | }, |
| 1135 | .stopped => |sig| { |
| 1136 | std.debug.print("{s}\nThe following command stopped with signal {t}:\n", .{ result.stderr, sig }); |
| 1137 | dumpArgs(args); |
| 1138 | return error.ChildCrashed; |
| 1139 | }, |
| 1140 | .unknown => { |
| 1141 | std.debug.print("{s}\nThe following command crashed:\n", .{result.stderr}); |
| 1142 | dumpArgs(args); |
| 1143 | return error.ChildCrashed; |
| 1144 | }, |
| 1145 | } |
| 1146 | return result; |
| 1147 | } |
| 1148 | |
| 1149 | fn printShell(out: *Writer, shell_content: []const u8, escape: bool) !void { |
| 1150 | const trimmed_shell_content = mem.trim(u8, shell_content, " \r\n"); |
| 1151 | try out.writeAll("<figure><figcaption class=\"shell-cap\">Shell</figcaption><pre><samp>"); |
| 1152 | var cmd_cont: bool = false; |
| 1153 | var iter = std.mem.splitScalar(u8, trimmed_shell_content, '\n'); |
| 1154 | while (iter.next()) |orig_line| { |
| 1155 | const line = mem.trimEnd(u8, orig_line, " \r"); |
| 1156 | if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] != '\\') { |
| 1157 | try out.writeAll("$ <kbd>"); |
| 1158 | const s = std.mem.trimStart(u8, line[1..], " "); |
| 1159 | if (escape) { |
| 1160 | try writeEscaped(out, s); |
| 1161 | } else { |
| 1162 | try out.writeAll(s); |
| 1163 | } |
| 1164 | try out.writeAll("</kbd>" ++ "\n"); |
| 1165 | } else if (!cmd_cont and line.len > 1 and mem.eql(u8, line[0..2], "$ ") and line[line.len - 1] == '\\') { |
| 1166 | try out.writeAll("$ <kbd>"); |
| 1167 | const s = std.mem.trimStart(u8, line[1..], " "); |
| 1168 | if (escape) { |
| 1169 | try writeEscaped(out, s); |
| 1170 | } else { |
| 1171 | try out.writeAll(s); |
| 1172 | } |
| 1173 | try out.writeAll("\n"); |
| 1174 | cmd_cont = true; |
| 1175 | } else if (line.len > 0 and line[line.len - 1] != '\\' and cmd_cont) { |
| 1176 | if (escape) { |
| 1177 | try writeEscaped(out, line); |
| 1178 | } else { |
| 1179 | try out.writeAll(line); |
| 1180 | } |
| 1181 | try out.writeAll("</kbd>" ++ "\n"); |
| 1182 | cmd_cont = false; |
| 1183 | } else { |
| 1184 | if (escape) { |
| 1185 | try writeEscaped(out, line); |
| 1186 | } else { |
| 1187 | try out.writeAll(line); |
| 1188 | } |
| 1189 | try out.writeAll("\n"); |
| 1190 | } |
| 1191 | } |
| 1192 | |
| 1193 | try out.writeAll("</samp></pre></figure>"); |
| 1194 | } |
| 1195 | |
| 1196 | test "term supported colors" { |
| 1197 | const test_allocator = testing.allocator; |
| 1198 | |
| 1199 | { |
| 1200 | const input = "A\x1b[31;1mred\x1b[0mB"; |
| 1201 | const expect = "A<span class=\"sgr-31_1m\">red</span>B"; |
| 1202 | |
| 1203 | const result = try termColor(test_allocator, input); |
| 1204 | defer test_allocator.free(result); |
| 1205 | try testing.expectEqualSlices(u8, expect, result); |
| 1206 | } |
| 1207 | |
| 1208 | { |
| 1209 | const input = "A\x1b[32;1mgreen\x1b[0mB"; |
| 1210 | const expect = "A<span class=\"sgr-32_1m\">green</span>B"; |
| 1211 | |
| 1212 | const result = try termColor(test_allocator, input); |
| 1213 | defer test_allocator.free(result); |
| 1214 | try testing.expectEqualSlices(u8, expect, result); |
| 1215 | } |
| 1216 | |
| 1217 | { |
| 1218 | const input = "A\x1b[36;1mcyan\x1b[0mB"; |
| 1219 | const expect = "A<span class=\"sgr-36_1m\">cyan</span>B"; |
| 1220 | |
| 1221 | const result = try termColor(test_allocator, input); |
| 1222 | defer test_allocator.free(result); |
| 1223 | try testing.expectEqualSlices(u8, expect, result); |
| 1224 | } |
| 1225 | |
| 1226 | { |
| 1227 | const input = "A\x1b[1mbold\x1b[0mB"; |
| 1228 | const expect = "A<span class=\"sgr-1m\">bold</span>B"; |
| 1229 | |
| 1230 | const result = try termColor(test_allocator, input); |
| 1231 | defer test_allocator.free(result); |
| 1232 | try testing.expectEqualSlices(u8, expect, result); |
| 1233 | } |
| 1234 | |
| 1235 | { |
| 1236 | const input = "A\x1b[2mdim\x1b[0mB"; |
| 1237 | const expect = "A<span class=\"sgr-2m\">dim</span>B"; |
| 1238 | |
| 1239 | const result = try termColor(test_allocator, input); |
| 1240 | defer test_allocator.free(result); |
| 1241 | try testing.expectEqualSlices(u8, expect, result); |
| 1242 | } |
| 1243 | } |
| 1244 | |
| 1245 | test "term output from zig" { |
| 1246 | // Use data generated by https://github.com/perillo/zig-tty-test-data, |
| 1247 | // with zig version 0.11.0-dev.1898+36d47dd19. |
| 1248 | const test_allocator = testing.allocator; |
| 1249 | |
| 1250 | { |
| 1251 | // 1.1-with-build-progress.out |
| 1252 | const input = "Semantic Analysis [1324] \x1b[25D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLVM Emit Object... \x1b[20D\x1b[0KLLD Link... \x1b[12D\x1b[0K"; |
| 1253 | const expect = ""; |
| 1254 | |
| 1255 | const result = try termColor(test_allocator, input); |
| 1256 | defer test_allocator.free(result); |
| 1257 | try testing.expectEqualSlices(u8, expect, result); |
| 1258 | } |
| 1259 | |
| 1260 | { |
| 1261 | // 2.1-with-reference-traces.out |
| 1262 | const input = "\x1b[1msrc/2.1-with-reference-traces.zig:3:7: \x1b[31;1merror: \x1b[0m\x1b[1mcannot assign to constant\n\x1b[0m x += 1;\n \x1b[32;1m~~^~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.1-with-reference-traces.zig:7:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m"; |
| 1263 | const expect = |
| 1264 | \\<span class="sgr-1m">src/2.1-with-reference-traces.zig:3:7: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">cannot assign to constant |
| 1265 | \\</span> x += 1; |
| 1266 | \\ <span class="sgr-32_1m">~~^~~~ |
| 1267 | \\</span><span class="sgr-2m">referenced by: |
| 1268 | \\ main: src/2.1-with-reference-traces.zig:7:5 |
| 1269 | \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17 |
| 1270 | \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces |
| 1271 | \\ |
| 1272 | \\</span> |
| 1273 | ; |
| 1274 | |
| 1275 | const result = try termColor(test_allocator, input); |
| 1276 | defer test_allocator.free(result); |
| 1277 | try testing.expectEqualSlices(u8, expect, result); |
| 1278 | } |
| 1279 | |
| 1280 | { |
| 1281 | // 2.2-without-reference-traces.out |
| 1282 | const input = "\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: \x1b[31;1merror: \x1b[0m\x1b[1minvalid type given to fixedBufferStream\n\x1b[0m else => @compileError(\"invalid type given to fixedBufferStream\"),\n \x1b[32;1m^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\x1b[0m\x1b[1m/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: \x1b[36;1mnote: \x1b[0m\x1b[1mcalled from here\n\x1b[0mpub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {\n; \x1b[32;1m~~~~~^~~~~~~~~~~~~~~~~\n\x1b[0m"; |
| 1283 | const expect = |
| 1284 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:128:29: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">invalid type given to fixedBufferStream |
| 1285 | \\</span> else => @compileError("invalid type given to fixedBufferStream"), |
| 1286 | \\ <span class="sgr-32_1m">^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 1287 | \\</span><span class="sgr-1m">/usr/local/lib/zig/lib/std/io/fixed_buffer_stream.zig:116:66: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">called from here |
| 1288 | \\</span>pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) { |
| 1289 | \\; <span class="sgr-32_1m">~~~~~^~~~~~~~~~~~~~~~~ |
| 1290 | \\</span> |
| 1291 | ; |
| 1292 | |
| 1293 | const result = try termColor(test_allocator, input); |
| 1294 | defer test_allocator.free(result); |
| 1295 | try testing.expectEqualSlices(u8, expect, result); |
| 1296 | } |
| 1297 | |
| 1298 | { |
| 1299 | // 2.3-with-notes.out |
| 1300 | const input = "\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[31;1merror: \x1b[0m\x1b[1mexpected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat'\n\x1b[0m bar(w);\n \x1b[32;1m^\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:6:9: \x1b[36;1mnote: \x1b[0m\x1b[1mpointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp'\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:2:13: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Wat = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:1:14: \x1b[36;1mnote: \x1b[0m\x1b[1mopaque declared here\n\x1b[0mconst Derp = opaque {};\n \x1b[32;1m^~~~~~~~~\n\x1b[0m\x1b[1msrc/2.3-with-notes.zig:4:18: \x1b[36;1mnote: \x1b[0m\x1b[1mparameter type declared here\n\x1b[0mextern fn bar(d: *Derp) void;\n \x1b[32;1m^~~~~\n\x1b[0m\x1b[0m\x1b[2mreferenced by:\n main: src/2.3-with-notes.zig:10:5\n callMain: /usr/local/lib/zig/lib/std/start.zig:607:17\n remaining reference traces hidden; use '-freference-trace' to see all reference traces\n\n\x1b[0m"; |
| 1301 | const expect = |
| 1302 | \\<span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-31_1m">error: </span><span class="sgr-1m">expected type '*2.3-with-notes.Derp', found '*2.3-with-notes.Wat' |
| 1303 | \\</span> bar(w); |
| 1304 | \\ <span class="sgr-32_1m">^ |
| 1305 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:6:9: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">pointer type child '2.3-with-notes.Wat' cannot cast into pointer type child '2.3-with-notes.Derp' |
| 1306 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:2:13: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here |
| 1307 | \\</span>const Wat = opaque {}; |
| 1308 | \\ <span class="sgr-32_1m">^~~~~~~~~ |
| 1309 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:1:14: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">opaque declared here |
| 1310 | \\</span>const Derp = opaque {}; |
| 1311 | \\ <span class="sgr-32_1m">^~~~~~~~~ |
| 1312 | \\</span><span class="sgr-1m">src/2.3-with-notes.zig:4:18: </span><span class="sgr-36_1m">note: </span><span class="sgr-1m">parameter type declared here |
| 1313 | \\</span>extern fn bar(d: *Derp) void; |
| 1314 | \\ <span class="sgr-32_1m">^~~~~ |
| 1315 | \\</span><span class="sgr-2m">referenced by: |
| 1316 | \\ main: src/2.3-with-notes.zig:10:5 |
| 1317 | \\ callMain: /usr/local/lib/zig/lib/std/start.zig:607:17 |
| 1318 | \\ remaining reference traces hidden; use '-freference-trace' to see all reference traces |
| 1319 | \\ |
| 1320 | \\</span> |
| 1321 | ; |
| 1322 | |
| 1323 | const result = try termColor(test_allocator, input); |
| 1324 | defer test_allocator.free(result); |
| 1325 | try testing.expectEqualSlices(u8, expect, result); |
| 1326 | } |
| 1327 | |
| 1328 | { |
| 1329 | // 3.1-with-error-return-traces.out |
| 1330 | |
| 1331 | const input = "error: Error\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:5:5\x1b[0m: \x1b[2m0x20b008 in callee (3.1-with-error-return-traces)\x1b[0m\n return error.Error;\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:9:5\x1b[0m: \x1b[2m0x20b113 in caller (3.1-with-error-return-traces)\x1b[0m\n try callee();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.1-with-error-return-traces.zig:13:5\x1b[0m: \x1b[2m0x20b153 in main (3.1-with-error-return-traces)\x1b[0m\n try caller();\n \x1b[32;1m^\x1b[0m\n"; |
| 1332 | const expect = |
| 1333 | \\error: Error |
| 1334 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:5:5</span>: <span class="sgr-2m">0x20b008 in callee (3.1-with-error-return-traces)</span> |
| 1335 | \\ return error.Error; |
| 1336 | \\ <span class="sgr-32_1m">^</span> |
| 1337 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:9:5</span>: <span class="sgr-2m">0x20b113 in caller (3.1-with-error-return-traces)</span> |
| 1338 | \\ try callee(); |
| 1339 | \\ <span class="sgr-32_1m">^</span> |
| 1340 | \\<span class="sgr-1m">/home/zig/src/3.1-with-error-return-traces.zig:13:5</span>: <span class="sgr-2m">0x20b153 in main (3.1-with-error-return-traces)</span> |
| 1341 | \\ try caller(); |
| 1342 | \\ <span class="sgr-32_1m">^</span> |
| 1343 | \\ |
| 1344 | ; |
| 1345 | |
| 1346 | const result = try termColor(test_allocator, input); |
| 1347 | defer test_allocator.free(result); |
| 1348 | try testing.expectEqualSlices(u8, expect, result); |
| 1349 | } |
| 1350 | |
| 1351 | { |
| 1352 | // 3.2-with-stack-trace.out |
| 1353 | const input = "\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:561:19\x1b[0m: \x1b[2m0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)\x1b[0m\n while (it.next()) |return_address| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/debug.zig:157:80\x1b[0m: \x1b[2m0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)\x1b[0m\n writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| {\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:5:36\x1b[0m: \x1b[2m0x20d3b2 in foo (3.2-with-stack-trace)\x1b[0m\n std.debug.dumpCurrentStackTrace(null);\n \x1b[32;1m^\x1b[0m\n\x1b[1m/home/zig/src/3.2-with-stack-trace.zig:9:8\x1b[0m: \x1b[2m0x20b458 in main (3.2-with-stack-trace)\x1b[0m\n foo();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:607:22\x1b[0m: \x1b[2m0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)\x1b[0m\n root.main();\n \x1b[32;1m^\x1b[0m\n\x1b[1m/usr/local/lib/zig/lib/std/start.zig:376:5\x1b[0m: \x1b[2m0x20a411 in _start (3.2-with-stack-trace)\x1b[0m\n @call(.never_inline, posixCallMainAndExit, .{});\n \x1b[32;1m^\x1b[0m\n"; |
| 1354 | const expect = |
| 1355 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:561:19</span>: <span class="sgr-2m">0x22a107 in writeCurrentStackTrace__anon_5898 (3.2-with-stack-trace)</span> |
| 1356 | \\ while (it.next()) |return_address| { |
| 1357 | \\ <span class="sgr-32_1m">^</span> |
| 1358 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/debug.zig:157:80</span>: <span class="sgr-2m">0x20bb23 in dumpCurrentStackTrace (3.2-with-stack-trace)</span> |
| 1359 | \\ writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(io.getStdErr()), start_addr) catch |err| { |
| 1360 | \\ <span class="sgr-32_1m">^</span> |
| 1361 | \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:5:36</span>: <span class="sgr-2m">0x20d3b2 in foo (3.2-with-stack-trace)</span> |
| 1362 | \\ std.debug.dumpCurrentStackTrace(null); |
| 1363 | \\ <span class="sgr-32_1m">^</span> |
| 1364 | \\<span class="sgr-1m">/home/zig/src/3.2-with-stack-trace.zig:9:8</span>: <span class="sgr-2m">0x20b458 in main (3.2-with-stack-trace)</span> |
| 1365 | \\ foo(); |
| 1366 | \\ <span class="sgr-32_1m">^</span> |
| 1367 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:607:22</span>: <span class="sgr-2m">0x20a965 in posixCallMainAndExit (3.2-with-stack-trace)</span> |
| 1368 | \\ root.main(); |
| 1369 | \\ <span class="sgr-32_1m">^</span> |
| 1370 | \\<span class="sgr-1m">/usr/local/lib/zig/lib/std/start.zig:376:5</span>: <span class="sgr-2m">0x20a411 in _start (3.2-with-stack-trace)</span> |
| 1371 | \\ @call(.never_inline, posixCallMainAndExit, .{}); |
| 1372 | \\ <span class="sgr-32_1m">^</span> |
| 1373 | \\ |
| 1374 | ; |
| 1375 | |
| 1376 | const result = try termColor(test_allocator, input); |
| 1377 | defer test_allocator.free(result); |
| 1378 | try testing.expectEqualSlices(u8, expect, result); |
| 1379 | } |
| 1380 | } |
| 1381 | |
| 1382 | test "printShell" { |
| 1383 | const test_allocator = std.testing.allocator; |
| 1384 | |
| 1385 | { |
| 1386 | const shell_out = |
| 1387 | \\$ zig build test.zig |
| 1388 | ; |
| 1389 | const expected = |
| 1390 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> |
| 1391 | \\</samp></pre></figure> |
| 1392 | ; |
| 1393 | |
| 1394 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1395 | defer buffer.deinit(); |
| 1396 | |
| 1397 | try printShell(&buffer.writer, shell_out, false); |
| 1398 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1399 | } |
| 1400 | { |
| 1401 | const shell_out = |
| 1402 | \\$ zig build test.zig |
| 1403 | \\build output |
| 1404 | ; |
| 1405 | const expected = |
| 1406 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> |
| 1407 | \\build output |
| 1408 | \\</samp></pre></figure> |
| 1409 | ; |
| 1410 | |
| 1411 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1412 | defer buffer.deinit(); |
| 1413 | |
| 1414 | try printShell(&buffer.writer, shell_out, false); |
| 1415 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1416 | } |
| 1417 | { |
| 1418 | const shell_out = "$ zig build test.zig\r\nbuild output\r\n"; |
| 1419 | const expected = |
| 1420 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> |
| 1421 | \\build output |
| 1422 | \\</samp></pre></figure> |
| 1423 | ; |
| 1424 | |
| 1425 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1426 | defer buffer.deinit(); |
| 1427 | |
| 1428 | try printShell(&buffer.writer, shell_out, false); |
| 1429 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1430 | } |
| 1431 | { |
| 1432 | const shell_out = |
| 1433 | \\$ zig build test.zig |
| 1434 | \\build output |
| 1435 | \\$ ./test |
| 1436 | ; |
| 1437 | const expected = |
| 1438 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> |
| 1439 | \\build output |
| 1440 | \\$ <kbd>./test</kbd> |
| 1441 | \\</samp></pre></figure> |
| 1442 | ; |
| 1443 | |
| 1444 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1445 | defer buffer.deinit(); |
| 1446 | |
| 1447 | try printShell(&buffer.writer, shell_out, false); |
| 1448 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1449 | } |
| 1450 | { |
| 1451 | const shell_out = |
| 1452 | \\$ zig build test.zig |
| 1453 | \\ |
| 1454 | \\$ ./test |
| 1455 | \\output |
| 1456 | ; |
| 1457 | const expected = |
| 1458 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> |
| 1459 | \\ |
| 1460 | \\$ <kbd>./test</kbd> |
| 1461 | \\output |
| 1462 | \\</samp></pre></figure> |
| 1463 | ; |
| 1464 | |
| 1465 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1466 | defer buffer.deinit(); |
| 1467 | |
| 1468 | try printShell(&buffer.writer, shell_out, false); |
| 1469 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1470 | } |
| 1471 | { |
| 1472 | const shell_out = |
| 1473 | \\$ zig build test.zig |
| 1474 | \\$ ./test |
| 1475 | \\output |
| 1476 | ; |
| 1477 | const expected = |
| 1478 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> |
| 1479 | \\$ <kbd>./test</kbd> |
| 1480 | \\output |
| 1481 | \\</samp></pre></figure> |
| 1482 | ; |
| 1483 | |
| 1484 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1485 | defer buffer.deinit(); |
| 1486 | |
| 1487 | try printShell(&buffer.writer, shell_out, false); |
| 1488 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1489 | } |
| 1490 | { |
| 1491 | const shell_out = |
| 1492 | \\$ zig build test.zig \ |
| 1493 | \\ --build-option |
| 1494 | \\build output |
| 1495 | \\$ ./test |
| 1496 | \\output |
| 1497 | ; |
| 1498 | const expected = |
| 1499 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ |
| 1500 | \\ --build-option</kbd> |
| 1501 | \\build output |
| 1502 | \\$ <kbd>./test</kbd> |
| 1503 | \\output |
| 1504 | \\</samp></pre></figure> |
| 1505 | ; |
| 1506 | |
| 1507 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1508 | defer buffer.deinit(); |
| 1509 | |
| 1510 | try printShell(&buffer.writer, shell_out, false); |
| 1511 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1512 | } |
| 1513 | { |
| 1514 | // intentional space after "--build-option1 \" |
| 1515 | const shell_out = |
| 1516 | \\$ zig build test.zig \ |
| 1517 | \\ --build-option1 \ |
| 1518 | \\ --build-option2 |
| 1519 | \\$ ./test |
| 1520 | ; |
| 1521 | const expected = |
| 1522 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ |
| 1523 | \\ --build-option1 \ |
| 1524 | \\ --build-option2</kbd> |
| 1525 | \\$ <kbd>./test</kbd> |
| 1526 | \\</samp></pre></figure> |
| 1527 | ; |
| 1528 | |
| 1529 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1530 | defer buffer.deinit(); |
| 1531 | |
| 1532 | try printShell(&buffer.writer, shell_out, false); |
| 1533 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1534 | } |
| 1535 | { |
| 1536 | const shell_out = |
| 1537 | \\$ zig build test.zig \ |
| 1538 | \\$ ./test |
| 1539 | ; |
| 1540 | const expected = |
| 1541 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig \ |
| 1542 | \\$ ./test</kbd> |
| 1543 | \\</samp></pre></figure> |
| 1544 | ; |
| 1545 | |
| 1546 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1547 | defer buffer.deinit(); |
| 1548 | |
| 1549 | try printShell(&buffer.writer, shell_out, false); |
| 1550 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1551 | } |
| 1552 | { |
| 1553 | const shell_out = |
| 1554 | \\$ zig build test.zig |
| 1555 | \\$ ./test |
| 1556 | \\$1 |
| 1557 | ; |
| 1558 | const expected = |
| 1559 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$ <kbd>zig build test.zig</kbd> |
| 1560 | \\$ <kbd>./test</kbd> |
| 1561 | \\$1 |
| 1562 | \\</samp></pre></figure> |
| 1563 | ; |
| 1564 | |
| 1565 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1566 | defer buffer.deinit(); |
| 1567 | |
| 1568 | try printShell(&buffer.writer, shell_out, false); |
| 1569 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1570 | } |
| 1571 | { |
| 1572 | const shell_out = |
| 1573 | \\$zig build test.zig |
| 1574 | ; |
| 1575 | const expected = |
| 1576 | \\<figure><figcaption class="shell-cap">Shell</figcaption><pre><samp>$zig build test.zig |
| 1577 | \\</samp></pre></figure> |
| 1578 | ; |
| 1579 | |
| 1580 | var buffer: Writer.Allocating = .init(test_allocator); |
| 1581 | defer buffer.deinit(); |
| 1582 | |
| 1583 | try printShell(&buffer.writer, shell_out, false); |
| 1584 | try testing.expectEqualSlices(u8, expected, buffer.written()); |
| 1585 | } |
| 1586 | } |