| ... | @@ -4,7 +4,9 @@ const os = std.os; | ... | @@ -4,7 +4,9 @@ const os = std.os; |
| 4 | const warn = std.debug.warn; | 4 | const warn = std.debug.warn; |
| 5 | const mem = std.mem; | 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 | pub fn main() -> %void { | 11 | pub fn main() -> %void { |
| 10 | // TODO use a more general purpose allocator here | 12 | // TODO use a more general purpose allocator here |
| ... | @@ -16,6 +18,9 @@ pub fn main() -> %void { | ... | @@ -16,6 +18,9 @@ pub fn main() -> %void { |
| 16 | | 18 | |
| 17 | if (!args_it.skip()) @panic("expected self arg"); | 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 | const in_file_name = try (args_it.next(allocator) ?? @panic("expected input arg")); | 24 | const in_file_name = try (args_it.next(allocator) ?? @panic("expected input arg")); |
| 20 | defer allocator.free(in_file_name); | 25 | defer allocator.free(in_file_name); |
| 21 | | 26 | |
| ... | @@ -38,7 +43,7 @@ pub fn main() -> %void { | ... | @@ -38,7 +43,7 @@ pub fn main() -> %void { |
| 38 | var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); | 43 | var tokenizer = Tokenizer.init(in_file_name, input_file_bytes); |
| 39 | var toc = try genToc(allocator, &tokenizer); | 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 | try buffered_out_stream.flush(); | 47 | try buffered_out_stream.flush(); |
| 43 | } | 48 | } |
| 44 | | 49 | |
| ... | @@ -246,11 +251,24 @@ const SeeAlsoItem = struct { | ... | @@ -246,11 +251,24 @@ const SeeAlsoItem = struct { |
| 246 | token: Token, | 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 | const Node = union(enum) { | 266 | const Node = union(enum) { |
| 250 | Content: []const u8, | 267 | Content: []const u8, |
| 251 | Nav, | 268 | Nav, |
| 252 | HeaderOpen: HeaderOpen, | 269 | HeaderOpen: HeaderOpen, |
| 253 | SeeAlso: []const SeeAlsoItem, | 270 | SeeAlso: []const SeeAlsoItem, |
| | 271 | Code: Code, |
| 254 | }; | 272 | }; |
| 255 | | 273 | |
| 256 | const Toc = struct { | 274 | const Toc = struct { |
| ... | @@ -300,14 +318,14 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { | ... | @@ -300,14 +318,14 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { |
| 300 | const tag_name = tokenizer.buffer[tag_token.start..tag_token.end]; | 318 | const tag_name = tokenizer.buffer[tag_token.start..tag_token.end]; |
| 301 | | 319 | |
| 302 | if (mem.eql(u8, tag_name, "nav")) { | 320 | if (mem.eql(u8, tag_name, "nav")) { |
| 303 | _ = eatToken(tokenizer, Token.Id.BracketClose); | 321 | _ = try eatToken(tokenizer, Token.Id.BracketClose); |
| 304 | | 322 | |
| 305 | try nodes.append(Node.Nav); | 323 | try nodes.append(Node.Nav); |
| 306 | } else if (mem.eql(u8, tag_name, "header_open")) { | 324 | } else if (mem.eql(u8, tag_name, "header_open")) { |
| 307 | _ = eatToken(tokenizer, Token.Id.Separator); | 325 | _ = try eatToken(tokenizer, Token.Id.Separator); |
| 308 | const content_token = try eatToken(tokenizer, Token.Id.TagContent); | 326 | const content_token = try eatToken(tokenizer, Token.Id.TagContent); |
| 309 | const content = tokenizer.buffer[content_token.start..content_token.end]; | 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 | header_stack_size += 1; | 330 | header_stack_size += 1; |
| 313 | | 331 | |
| ... | @@ -336,7 +354,7 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { | ... | @@ -336,7 +354,7 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { |
| 336 | return parseError(tokenizer, tag_token, "unbalanced close header"); | 354 | return parseError(tokenizer, tag_token, "unbalanced close header"); |
| 337 | } | 355 | } |
| 338 | header_stack_size -= 1; | 356 | header_stack_size -= 1; |
| 339 | _ = eatToken(tokenizer, Token.Id.BracketClose); | 357 | _ = try eatToken(tokenizer, Token.Id.BracketClose); |
| 340 | | 358 | |
| 341 | if (last_action == Action.Close) { | 359 | if (last_action == Action.Close) { |
| 342 | try toc.writeByteNTimes(' ', 8 + header_stack_size * 4); | 360 | try toc.writeByteNTimes(' ', 8 + header_stack_size * 4); |
| ... | @@ -367,6 +385,44 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { | ... | @@ -367,6 +385,44 @@ fn genToc(allocator: &mem.Allocator, tokenizer: &Tokenizer) -> %Toc { |
| 367 | else => return parseError(tokenizer, see_also_tok, "invalid see_also token"), | 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 | } else { | 426 | } else { |
| 371 | return parseError(tokenizer, tag_token, "unrecognized tag name: {}", tag_name); | 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,7 +458,27 @@ fn urlize(allocator: &mem.Allocator, input: []const u8) -> %[]u8 { |
| 402 | return buf.toOwnedSlice(); | 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 | for (toc.nodes) |node| { | 482 | for (toc.nodes) |node| { |
| 407 | switch (node) { | 483 | switch (node) { |
| 408 | Node.Content => |data| { | 484 | Node.Content => |data| { |
| ... | @@ -425,6 +501,65 @@ fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io | ... | @@ -425,6 +501,65 @@ fn genHtml(allocator: &mem.Allocator, tokenizer: &Tokenizer, toc: &Toc, out: &io |
| 425 | } | 501 | } |
| 426 | try out.write("</ul>\n"); | 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 | |