| ... | ... | @@ -4,7 +4,9 @@ const os = std.os; |
| 4 | 4 | const warn = std.debug.warn; |
| 5 | 5 | const mem = std.mem; |
| 6 | 6 | |
| 7 | | pub const max_doc_file_size = 10 * 1024 * 1024; |
| 7 | const max_doc_file_size = 10 * 1024 * 1024; |
| 8 | |
| 9 | const exe_ext = std.build.Target(std.build.Target.Native).exeFileExt(); |
| 8 | 10 | |
| 9 | 11 | pub fn main() -> %void { |
| 10 | 12 | // TODO use a more general purpose allocator here |
| ... | ... | @@ -16,6 +18,9 @@ pub fn main() -> %void { |
| 16 | 18 | |
| 17 | 19 | if (!args_it.skip()) @panic("expected self arg"); |
| 18 | 20 | |
| 21 | const zig_exe = try (args_it.next(allocator) ?? @panic("expected zig exe arg")); |
| 22 | defer allocator.free(zig_exe); |
| 23 | |
| 19 | 24 | const in_file_name = try (args_it.next(allocator) ?? @panic("expected input arg")); |
| 20 | 25 | defer allocator.free(in_file_name); |
| 21 | 26 | |
| ... | ... | @@ -38,7 +43,7 @@ pub fn main() -> %void { |
| 38 | 43 | var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); |
| 39 | 44 | var toc = try genToc(allocator, &tokenizer); |
| 40 | 45 | |
| 41 | | try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream); |
| 46 | try genHtml(allocator, &tokenizer, &toc, &buffered_out_stream.stream, zig_exe); |
| 42 | 47 | try buffered_out_stream.flush(); |
| 43 | 48 | } |
| 44 | 49 | |
| ... | ... | @@ -246,11 +251,24 @@ const SeeAlsoItem = struct { |
| 246 | 251 | token: Token, |
| 247 | 252 | }; |
| 248 | 253 | |
| 254 | const Code = struct { |
| 255 | id: Id, |
| 256 | name: []const u8, |
| 257 | source_token: Token, |
| 258 | |
| 259 | const Id = enum { |
| 260 | Test, |
| 261 | Exe, |
| 262 | Error, |
| 263 | }; |
| 264 | }; |
| 265 | |
| 249 | 266 | const Node = union(enum) { |
| 250 | 267 | Content: []const u8, |
| 251 | 268 | Nav, |
| 252 | 269 | HeaderOpen: HeaderOpen, |
| 253 | 270 | SeeAlso: []const SeeAlsoItem, |
| 271 | Code: Code, |
| 254 | 272 | }; |
| 255 | 273 | |
| 256 | 274 | const Toc = struct { |
| ... | ... | @@ -300,14 +318,14 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { |
| 300 | 318 | const tag_name = tokenizer.buffer[tag_token.start..tag_token.end]; |
| 301 | 319 | |
| 302 | 320 | if (mem.eql(u8, tag_name, "nav")) { |
| 303 | | _ = eatToken(tokenizer, Token.Id.BracketClose); |
| 321 | _ = try eatToken(tokenizer, Token.Id.BracketClose); |
| 304 | 322 | |
| 305 | 323 | try nodes.append(Node.Nav); |
| 306 | 324 | } else if (mem.eql(u8, tag_name, "header_open")) { |
| 307 | | _ = eatToken(tokenizer, Token.Id.Separator); |
| 325 | _ = try eatToken(tokenizer, Token.Id.Separator); |
| 308 | 326 | const content_token = try eatToken(tokenizer, Token.Id.TagContent); |
| 309 | 327 | const content = tokenizer.buffer[content_token.start..content_token.end]; |
| 310 | | _ = eatToken(tokenizer, Token.Id.BracketClose); |
| 328 | _ = try eatToken(tokenizer, Token.Id.BracketClose); |
| 311 | 329 | |
| 312 | 330 | header_stack_size += 1; |
| 313 | 331 | |
| ... | ... | @@ -336,7 +354,7 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { |
| 336 | 354 | return parseError(tokenizer, tag_token, "unbalanced close header"); |
| 337 | 355 | } |
| 338 | 356 | header_stack_size -= 1; |
| 339 | | _ = eatToken(tokenizer, Token.Id.BracketClose); |
| 357 | _ = try eatToken(tokenizer, Token.Id.BracketClose); |
| 340 | 358 | |
| 341 | 359 | if (last_action == Action.Close) { |
| 342 | 360 | try toc.writeByteNTimes(' ', 8 + header_stack_size * 4); |
| ... | ... | @@ -367,6 +385,44 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { |
| 367 | 385 | else => return parseError(tokenizer, see_also_tok, "invalid see_also token"), |
| 368 | 386 | } |
| 369 | 387 | } |
| 388 | } else if (mem.eql(u8, tag_name, "code_begin")) { |
| 389 | _ = try eatToken(tokenizer, Token.Id.Separator); |
| 390 | const code_kind_tok = try eatToken(tokenizer, Token.Id.TagContent); |
| 391 | var name: []const u8 = "test"; |
| 392 | const maybe_sep = tokenizer.next(); |
| 393 | switch (maybe_sep.id) { |
| 394 | Token.Id.Separator => { |
| 395 | const name_tok = try eatToken(tokenizer, Token.Id.TagContent); |
| 396 | name = tokenizer.buffer[name_tok.start..name_tok.end]; |
| 397 | _ = try eatToken(tokenizer, Token.Id.BracketClose); |
| 398 | }, |
| 399 | Token.Id.BracketClose => {}, |
| 400 | else => return parseError(tokenizer, token, "invalid token"), |
| 401 | } |
| 402 | const code_kind_str = tokenizer.buffer[code_kind_tok.start..code_kind_tok.end]; |
| 403 | var code_kind_id: Code.Id = undefined; |
| 404 | if (mem.eql(u8, code_kind_str, "exe")) { |
| 405 | code_kind_id = Code.Id.Exe; |
| 406 | } else if (mem.eql(u8, code_kind_str, "test")) { |
| 407 | code_kind_id = Code.Id.Test; |
| 408 | } else if (mem.eql(u8, code_kind_str, "error")) { |
| 409 | code_kind_id = Code.Id.Error; |
| 410 | } else { |
| 411 | return parseError(tokenizer, code_kind_tok, "unrecognized code kind: {}", code_kind_str); |
| 412 | } |
| 413 | const source_token = try eatToken(tokenizer, Token.Id.Content); |
| 414 | _ = try eatToken(tokenizer, Token.Id.BracketOpen); |
| 415 | const end_code_tag = try eatToken(tokenizer, Token.Id.TagContent); |
| 416 | const end_tag_name = tokenizer.buffer[end_code_tag.start..end_code_tag.end]; |
| 417 | if (!mem.eql(u8, end_tag_name, "code_end")) { |
| 418 | return parseError(tokenizer, end_code_tag, "expected code_end token"); |
| 419 | } |
| 420 | _ = try eatToken(tokenizer, Token.Id.BracketClose); |
| 421 | try nodes.append(Node {.Code = Code{ |
| 422 | .id = code_kind_id, |
| 423 | .name = name, |
| 424 | .source_token = source_token, |
| 425 | }}); |
| 370 | 426 | } else { |
| 371 | 427 | return parseError(tokenizer, tag_token, "unrecognized tag name: {}", tag_name); |
| 372 | 428 | } |
| ... | ... | @@ -402,7 +458,27 @@ fn urlize(allocator: &mem.Allocator, input: []const u8) -> %[]u8 { |
| 402 | 458 | return buf.toOwnedSlice(); |
| 403 | 459 | } |
| 404 | 460 | |
| 405 | | fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io.OutStream) -> %void { |
| 461 | fn escapeHtml(allocator: &mem.Allocator, input: []const u8) -> %[]u8 { |
| 462 | var buf = try std.Buffer.initSize(allocator, 0); |
| 463 | defer buf.deinit(); |
| 464 | |
| 465 | var buf_adapter = io.BufferOutStream.init(&buf); |
| 466 | var out = &buf_adapter.stream; |
| 467 | for (input) |c| { |
| 468 | try switch (c) { |
| 469 | '&' => out.write("&"), |
| 470 | '<' => out.write("&lt;"), |
| 471 | '>' => out.write("&gt;"), |
| 472 | '"' => out.write("&quot;"), |
| 473 | else => out.writeByte(c), |
| 474 | }; |
| 475 | } |
| 476 | return buf.toOwnedSlice(); |
| 477 | } |
| 478 | |
| 479 | error ExampleFailedToCompile; |
| 480 | |
| 481 | fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io.OutStream, zig_exe: []const u8) -> %void { |
| 406 | 482 | for (toc.nodes) |node| { |
| 407 | 483 | switch (node) { |
| 408 | 484 | Node.Content => |data| { |
| ... | ... | @@ -425,6 +501,65 @@ fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io |
| 425 | 501 | } |
| 426 | 502 | try out.write("</ul>\n"); |
| 427 | 503 | }, |
| 504 | Node.Code => |code| { |
| 505 | const raw_source = tokenizer.buffer[code.source_token.start..code.source_token.end]; |
| 506 | const trimmed_raw_source = mem.trim(u8, raw_source, " \n"); |
| 507 | const escaped_source = try escapeHtml(allocator, trimmed_raw_source); |
| 508 | try out.print("<pre><code class=\"zig\">{}</code></pre>", escaped_source); |
| 509 | const tmp_dir_name = "docgen_tmp"; |
| 510 | try os.makePath(allocator, tmp_dir_name); |
| 511 | const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name); |
| 512 | const name_plus_bin_ext = try std.fmt.allocPrint(allocator, "{}{}", code.name, exe_ext); |
| 513 | const tmp_source_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_ext); |
| 514 | const tmp_bin_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_bin_ext); |
| 515 | try io.writeFile(tmp_source_file_name, trimmed_raw_source, null); |
| 516 | |
| 517 | switch (code.id) { |
| 518 | Code.Id.Exe => { |
| 519 | { |
| 520 | const args = [][]const u8 {zig_exe, "build-exe", tmp_source_file_name, "--output", tmp_bin_file_name}; |
| 521 | const result = try os.ChildProcess.exec(allocator, args, null, null, max_doc_file_size); |
| 522 | switch (result.term) { |
| 523 | os.ChildProcess.Term.Exited => |exit_code| { |
| 524 | if (exit_code != 0) { |
| 525 | warn("{}\nThe following command exited with code {}:\n", result.stderr, exit_code); |
| 526 | for (args) |arg| warn("{} ", arg) else warn("\n"); |
| 527 | return parseError(tokenizer, code.source_token, "example failed to compile"); |
| 528 | } |
| 529 | }, |
| 530 | else => { |
| 531 | warn("{}\nThe following command crashed:\n", result.stderr); |
| 532 | for (args) |arg| warn("{} ", arg) else warn("\n"); |
| 533 | return parseError(tokenizer, code.source_token, "example failed to compile"); |
| 534 | }, |
| 535 | } |
| 536 | } |
| 537 | const args = [][]const u8 {tmp_bin_file_name}; |
| 538 | const result = try os.ChildProcess.exec(allocator, args, null, null, max_doc_file_size); |
| 539 | switch (result.term) { |
| 540 | os.ChildProcess.Term.Exited => |exit_code| { |
| 541 | if (exit_code != 0) { |
| 542 | warn("The following command exited with code {}:\n", exit_code); |
| 543 | for (args) |arg| warn("{} ", arg) else warn("\n"); |
| 544 | return parseError(tokenizer, code.source_token, "example exited with code {}", exit_code); |
| 545 | } |
| 546 | }, |
| 547 | else => { |
| 548 | warn("The following command crashed:\n"); |
| 549 | for (args) |arg| warn("{} ", arg) else warn("\n"); |
| 550 | return parseError(tokenizer, code.source_token, "example crashed"); |
| 551 | }, |
| 552 | } |
| 553 | try out.print("<pre><code class=\"sh\">$ zig build-exe {}.zig\n$ ./{}\n{}{}</code></pre>\n", code.name, code.name, result.stderr, result.stdout); |
| 554 | }, |
| 555 | Code.Id.Test => { |
| 556 | @panic("TODO"); |
| 557 | }, |
| 558 | Code.Id.Error => { |
| 559 | @panic("TODO"); |
| 560 | }, |
| 561 | } |
| 562 | }, |
| 428 | 563 | } |
| 429 | 564 | } |
| 430 | 565 | |