diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml index 9bdaa43..1d665ff 100644 --- a/.github/workflows/spec.yml +++ b/.github/workflows/spec.yml @@ -7,10 +7,10 @@ jobs: name: Run CommonMark specs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: submodules: recursive - - uses: goto-bus-stop/setup-zig@v2 + - uses: mlugg/setup-zig@v2 with: - version: 0.14.0 + version: 0.15.1 - run: make spec diff --git a/.github/workflows/zig.yml b/.github/workflows/zig.yml index 2ae434a..7b76a99 100644 --- a/.github/workflows/zig.yml +++ b/.github/workflows/zig.yml @@ -5,38 +5,26 @@ on: pull_request: jobs: - test-linux: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: goto-bus-stop/setup-zig@v2 - with: - version: 0.14.0 - - run: zig build - - run: zig build test - test-macos: - runs-on: macos-latest + tests: + strategy: + matrix: + zig-version: ["0.15.1"] + runs-on: ["ubuntu-latest", "macos-latest", "windows-latest"] + runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v4 - - uses: goto-bus-stop/setup-zig@2a9625d550eefc3a9b1a43d342ad655f563f8241 + - uses: actions/checkout@v6 + - uses: mlugg/setup-zig@v2 with: - version: 0.14.0 - - run: zig build - - run: zig build test - test-windows: - runs-on: windows-latest - steps: - - uses: actions/checkout@v4 - - uses: goto-bus-stop/setup-zig@v2 - with: - version: 0.14.0 + version: ${{ matrix.zig-version }} - run: zig build - run: zig build test lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: goto-bus-stop/setup-zig@v2 + - uses: actions/checkout@v6 + with: + submodules: recursive + - uses: mlugg/setup-zig@v2 with: - version: 0.14.0 - - run: zig fmt --check src/*.zig + version: 0.15.1 + - run: zig fmt --check build.zig src/*.zig diff --git a/.gitignore b/.gitignore index ef07331..9197bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ /result # generated by htmlentities.zig src/entities.zig + +# example outputs +examples/output-* diff --git a/Makefile b/Makefile index 9d92764..29a3417 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,26 @@ all: test: zig build test +exe: + echo hello | zig build run + zig build run --help + zig build run -- --help + spec: zig build cd vendor/cmark-gfm/test && python3 spec_tests.py --program=../../../zig-out/bin/koino + +fetch-clap: + zig fetch --save https://github.com/Hejsil/zig-clap/archive/refs/tags/0.11.0.tar.gz + +fetch-htmlentities: + zig fetch --save git+https://nossa.ee/~talya/htmlentities.zig + +fetch-libpcre: + zig fetch --save git+https://github.com/kivikakk/libpcre.zig + +fetch-zunicode: + zig fetch --save git+https://github.com/mishieck/zunicode + +example: + zig build example diff --git a/README.md b/README.md index 81f4c5e..c130aec 100644 --- a/README.md +++ b/README.md @@ -83,5 +83,8 @@ Options: Library: -Documentation is TODO — see [LoLa](https://github.com/MasterQ32/LoLa/blob/d02b0e6774fedbe07276d8af51e1a305cc58fb34/src/tools/render-md-page.zig#L157) for an example of use. Note also the [`build.zig`](https://github.com/MasterQ32/LoLa/blob/d02b0e6774fedbe07276d8af51e1a305cc58fb34/build.zig#L41-L50) declaration. +Documentation is TODO — see: + +- [LoLa](https://github.com/MasterQ32/LoLa/blob/d02b0e6774fedbe07276d8af51e1a305cc58fb34/src/tools/render-md-page.zig#L157): for an example of use. Note also the [`build.zig`](https://github.com/MasterQ32/LoLa/blob/d02b0e6774fedbe07276d8af51e1a305cc58fb34/build.zig#L41-L50) declaration. +- [Markdown to HTML example](./examples/to-html.zig). diff --git a/build.zig b/build.zig index 0dac694..35ef408 100644 --- a/build.zig +++ b/build.zig @@ -25,9 +25,16 @@ pub fn build(b: *std.Build) !void { const exe = b.addExecutable(.{ .name = "koino", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + + .target = target, + .optimize = optimize, + + .imports = &.{ + .{ .name = "test_koino", .module = mod }, + }, + }), }); try addCommonRequirements(exe.root_module, &deps); b.installArtifact(exe); @@ -41,10 +48,39 @@ pub fn build(b: *std.Build) !void { run_cmd.addArgs(args); } + const example = b.addExecutable(.{ + .name = "koino_example", + .root_module = b.createModule(.{ + .root_source_file = b.path("examples/to-html.zig"), + + .target = target, + .optimize = optimize, + + .imports = &.{ + .{ .name = "test_koino", .module = mod }, + }, + }), + }); + + try addCommonRequirements(example.root_module, &deps); + b.installArtifact(example); + + const example_run_cmd = b.addRunArtifact(example); + example_run_cmd.step.dependOn(b.getInstallStep()); + const example_run_step = b.step("example", "Run example"); + example_run_step.dependOn(&example_run_cmd.step); + const test_exe = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + + .target = target, + .optimize = optimize, + + .imports = &.{ + .{ .name = "test_koino", .module = mod }, + }, + }), }); try addCommonRequirements(test_exe.root_module, &deps); diff --git a/build.zig.zon b/build.zig.zon index 2b12fdc..8d7dce4 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,24 +1,24 @@ .{ .name = .koino, .version = "0.1.0", - .minimum_zig_version = "0.14.0", + .minimum_zig_version = "0.15.1", .fingerprint = 0x2af09c5b5aeed24b, .dependencies = .{ .zunicode = .{ - .url = "git+https://nossa.ee/~talya/zunicode#d5527c35892a1e448f65afd127f055251cd26b51", - .hash = "zunicode-0.1.0-AAAAAAn0BQAjplm0OqOuLlE2kcI8p65nBybLoZwsjBrH", + .url = "git+https://github.com/mishieck/zunicode#e765542e914737d313b4a56e946e5adb3d0544b9", + .hash = "zunicode-0.1.0-Ftska3z0BQAecb-Fpt9JXeZJpTOX4ZPOLkTWS5DoUVsP", }, .clap = .{ - .url = "git+https://github.com/Hejsil/zig-clap#a4e784da8399c51d5eeb5783e6a485b960d5c1f9", - .hash = "clap-0.10.0-oBajB8fkAQB0JvsrWLar4YZrseSZ9irFxHB7Hvy_bvxb", + .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.11.0.tar.gz", + .hash = "clap-0.11.0-oBajB-HnAQDPCKYzwF7rO3qDFwRcD39Q0DALlTSz5H7e", }, .libpcre_zig = .{ - .url = "git+https://nossa.ee/~talya/libpcre.zig#04224f0bed09888d043c6fe4352458dbd907c815", - .hash = "libpcre_zig-0.1.0-Dtf6CQg4AACWtDDhtI-rjGGLKdBowMQiQrK1O5sx5icv", + .url = "git+https://github.com/kivikakk/libpcre.zig#3f61efd01ec45281c4bc6f8f6d0ae7b718e7056e", + .hash = "libpcre_zig-0.1.0-Dtf6CQ04AACiOaA107r6fJ9BpThKZCzDeqp-v5Xv0Ixr", }, .htmlentities_zig = .{ - .url = "git+https://nossa.ee/~talya/htmlentities.zig#1a49942505db7bd06770006f66b907bb3a088b9f", - .hash = "htmlentities_zig-0.1.0-zV-DJCAfAwDQMPxoEXaBrDxijlyvCK7HXhz9MIgGYj5l", + .url = "git+https://nossa.ee/~talya/htmlentities.zig#50cb7d848e81389c71fc383534ece76a3fc36284", + .hash = "htmlentities_zig-0.1.0-zV-DJDYiAwAv5XJgsNlBPl79pWgiOkJ4-8OUgV95Ksb2", }, }, @@ -27,6 +27,7 @@ "Makefile", "build.zig.zon", "src", + "examples", "LICENSE", "README.md", }, diff --git a/examples/cc-&-gfm.md b/examples/cc-&-gfm.md new file mode 100644 index 0000000..a3a25b6 --- /dev/null +++ b/examples/cc-&-gfm.md @@ -0,0 +1,328 @@ + +# CommonMark + GitHub Flavored Markdown (GFM) — Feature demo + +This file demonstrates CommonMark core features and common GFM extensions +(tables, task lists, strikethrough, autolinks, mentions, emoji, collapsible +sections, etc.). + +--- + +## Headings + +ATX-style headings: + +```md +# H1 +## H2 +### H3 +#### H4 +##### H5 +###### H6 +``` + +Setext-style (underline) headings: + +```md +Heading level 1 +================ + +Heading level 2 +---------------- +``` +--- + +## Paragraphs and line breaks + +A paragraph is one or more lines of text separated by one or more blank lines. + +Soft line break (CommonMark): +This is a soft line break +that becomes a space in HTML. + +Hard line break (add two spaces at end of line) +This is a hard line break (two spaces then newline). + +--- + +## Thematic break + +--- + +*** + +___ + +--- + +## Emphasis and strong + +- Emphasis: *italic* or _italic_ +- Strong: **bold** or __bold__ +- Combined: **This is _bold and italic_ together** + +Escaped characters: \*not italic\* → `*not italic*` + +--- + +## Strikethrough (GFM) + +This is ~~deleted~~ text. + +--- + +## Inline code and code blocks + +Inline code: `std.debug.pring("hello\n", .{});` + +Fenced code block (backticks) with language hint (syntax highlighting on GitHub): + +```zig +const std = @import("std"); + +pub fn main() !void { + std.debug.print("Hello, World!\n", .{}); +} +``` + +Fenced code block (tildes): + +~~~text +This is a plain fenced block using tildes. +~~~ + +Code span with backticks in content: use `` `code with `backticks` inside` `` + +Indented code blocks (CommonMark): four-space indent + indented code line 1 + indented code line 2 + +--- + +## Blockquotes + +### Plain + +> This is a blockquote. +> +> > Nested blockquote. +> > +> > - Nested list item inside blockquote +> > - Another item +> +> Inline code in quote: `example()` + +### Alerts + +> [!NOTE] +> Highlights information that users should take into account, even when skimming. + +> [!TIP] +> Optional information to help a user be more successful. + +> [!IMPORTANT] +> Crucial information necessary for users to succeed. + +> [!WARNING] +> Critical content demanding immediate user attention due to potential risks. + +> [!CAUTION] +> Negative potential consequences of an action. + +--- + +## Lists + +Unordered list: +- Item A +- Item B + - Subitem B.1 + - Subitem B.2 + +Ordered list (starts at 1): +1. First +2. Second + 1. Sub-first + 2. Sub-second + +Ordered list with custom start (CommonMark allows any start number): +5. Start at five +6. Next + +GFM task list (checkboxes): +- [x] Completed task +- [ ] Open task +- [ ] Another task + +--- + +## Links and references + +Inline link: [Zig website](https://ziglang.org) + +Reference-style link: +This uses a [reference link][example]. + +[example]: https://example.com "Example site" + +Autolinks (GFM/CommonMark autolink): + + + +Image (inline): +![Alt text: small kitten](https://placekitten.com/120/80 "Kitten") + +Reference image: +![logo][logo-ref] + +[logo-ref]: https://upload.wikimedia.org/wikipedia/commons/4/4f/Iconic_image_example.svg "Logo (reference)" + +--- + +## Tables (GFM) + +Simple table: + +| Name | Age | Notes | +|:---------|:---:|--------------------:| +| Alice | 30 | Left-aligned name | +| Bob | 25 | Center age, right notes | +| Charlie | 23 | — | + +Alignment examples: + +| Left (default) | Center | Right | +|:---------------|:-----:|-------:| +| left text | center | right | +| foo | bar | baz | + +Note: Tables are a GFM extension (not core CommonMark). + +--- + +## HTML passthrough (allowed; rendered as HTML) + +You can include raw HTML. Example: keyboard key element and details/summary +(GFM supports this on GitHub): + +Ctrl + C + +
+Click to expand + +### Collapsible content + +This content is hidden until the summary is clicked. You can put lists, code, images, etc. + +- Item 1 +- Item 2 + +
+ +--- + +## Mentions, issue/PR refs, and emoji (GFM) + +- Mention a user: @octocat (only works on GitHub to link a username) +- Reference an issue: #123 (links on GitHub) +- Emoji shortcodes: :smile: :rocket: :bug: + +--- + +## Autolinks and bare URLs + +URLs in angle brackets are autolinked: + +Bare URLs in GFM are automatically turned into links in many renderers: +https://ziglang.org + +--- + +## Reference-style resources and footnote-like links + +Reference links allow you to define URLs separately: + +See the [Zig site][zig]. + +[zig]: https://ziglang.org + +Note: GFM does not provide native "footnotes" in the CommonMark spec; some +renderers support footnote extensions, but they are not standard on GitHub. + +--- + +## Inline HTML comments and entity examples + +This is visible text. + +Use entities when needed: & < > + +--- + +## Accessibility & images + +Use alt text for accessibility: +![A kitten looking up](https://placekitten.com/200/140 "Kitten looking up") + +--- + +## Notes — What is CommonMark vs. GFM + +- CommonMark defines the baseline, unambiguous Markdown syntax (headings, lists, + code fences, blockquotes, emphasis, links, reference links, etc.). +- GFM (GitHub Flavored Markdown) adds commonly-used extensions: + - Tables + - Task lists (checkboxes) + - Strikethrough (`~~`) + - Autolinking bare URLs and email addresses + - @mentions, #issue references, and emoji shortcodes + - Collapsible `
`/`` blocks (HTML) + - Syntax highlighting for fenced code blocks (via language hints) + +Features not universally supported: +- Footnotes are not part of CommonMark; some implementations/extensions add them. +- Certain HTML tags may be sanitized on some platforms for security. + +## LaTeX + +### Inline + +This sentence uses `$` delimiters to show math inline: $\sqrt{3x-1}+(1+x)^2$. + +This sentence uses $\` and \`$ delimiters to show math inline: $`\sqrt{3x-1}+(1+x)^2`$. + +### Block + +**The Cauchy-Schwarz Inequality**\ +$$\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right)$$ + +**The Cauchy-Schwarz Inequality** + +```math +\left( \sum_{k=1}^n a_k b_k \right)^2 \leq \left( \sum_{k=1}^n a_k^2 \right) \left( \sum_{k=1}^n b_k^2 \right) +``` + +### Using a Dollar Sign + +#### Within a Math Expression + +Within a math expression, add a \ symbol before the explicit $. + +This expression uses `\$` to display a dollar sign: $`\sqrt{\$4}`$ +Screenshot of rendered Markdown showing how a backslash before a dollar sign +displays the sign as part of a mathematical expression. + +#### Outside a Math Expression + +Outside a math expression, but on the same line, use span tags around the explicit $. + +To split $100 in half, we calculate $100/2$ + +## References + +[GitHub Flavored Markdown][GFM] +[GitHub Alerts][GA] +[Writing Mathematical Expressions][WME] + +[GFM]: https://github.github.com/gfm "GitHub Flavored Markdown" +[GA]: https://github.com/orgs/community/discussions/16925 "GitHub Alerts" +[WME]: https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions "Writing Mathematical Expressions" diff --git a/examples/to-html.zig b/examples/to-html.zig new file mode 100644 index 0000000..fb7fc0e --- /dev/null +++ b/examples/to-html.zig @@ -0,0 +1,44 @@ +const std = @import("std"); +const debug = std.debug; +const assert = debug.assert; + +const koino = @import("test_koino"); + +const input_file_name = "cc-&-gfm.md"; +const markdown = @embedFile("./" ++ input_file_name); + +pub fn main() !void { + debug.print("Converting './examples/{s}' to HTML.\n", .{input_file_name}); + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer std.debug.assert(gpa.deinit() == .ok); + const allocator = gpa.allocator(); + + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + const options = koino.Options{ + .extensions = .{ + .table = true, + .autolink = true, + .strikethrough = true, + }, + }; + + var parser = try koino.parser.Parser.init(arena.allocator(), options); + defer parser.deinit(); + try parser.feed(markdown); + + var doc = try parser.finish(); + defer doc.deinit(); + + var buffer = std.array_list.Managed(u8).init(allocator); + defer buffer.clearAndFree(); + try koino.html.print(buffer.writer(), allocator, parser.options, doc); + + const output_file_path = "./examples/output-to-html.html"; + const cwd = std.fs.cwd(); + try cwd.writeFile(.{ .data = buffer.items, .sub_path = output_file_path }); + + debug.print("Done! Output has been saved in '{s}'.\n", .{output_file_path}); +} diff --git a/src/autolink.zig b/src/autolink.zig index d20d907..e855c8c 100644 --- a/src/autolink.zig +++ b/src/autolink.zig @@ -1,5 +1,6 @@ const std = @import("std"); const ascii = std.ascii; +const ArrayList = std.array_list.Managed; const assert = std.debug.assert; const nodes = @import("nodes.zig"); const strings = @import("strings.zig"); @@ -77,7 +78,7 @@ pub const AutolinkProcessor = struct { link_end = autolinkDelim(self.text.*[i..], link_end); - var url = try std.ArrayList(u8).initCapacity(self.allocator, 7 + link_end); + var url = try ArrayList(u8).initCapacity(self.allocator, 7 + link_end); try url.appendSlice("http://"); try url.appendSlice(self.text.*[i .. link_end + i]); @@ -193,7 +194,7 @@ pub const AutolinkProcessor = struct { link_end = autolinkDelim(self.text.*[i..], link_end); - var url = try std.ArrayList(u8).initCapacity(self.allocator, 7 + link_end - rewind); + var url = try ArrayList(u8).initCapacity(self.allocator, 7 + link_end - rewind); try url.appendSlice("mailto:"); try url.appendSlice(self.text.*[i - rewind .. link_end + i]); @@ -303,7 +304,7 @@ pub const AutolinkProcessor = struct { fn makeInline(self: AutolinkProcessor, value: nodes.NodeValue) !*nodes.AstNode { return nodes.AstNode.create(self.allocator, .{ .value = value, - .content = std.ArrayList(u8).init(self.allocator), + .content = ArrayList(u8).init(self.allocator), }); } }; diff --git a/src/html.zig b/src/html.zig index f32e2ec..21b843e 100644 --- a/src/html.zig +++ b/src/html.zig @@ -1,5 +1,6 @@ const std = @import("std"); const ascii = std.ascii; +const ArrayList = std.array_list.Managed; const assert = std.debug.assert; const Options = @import("options.zig").Options; @@ -120,7 +121,7 @@ pub fn HtmlFormatter(comptime Writer: type) type { phase: Phase, }; - var stack = std.ArrayList(StackEntry).init(self.allocator); + var stack = ArrayList(StackEntry).init(self.allocator); defer stack.deinit(); try stack.append(.{ .node = input_node, .plain = plain, .phase = .Pre }); @@ -418,12 +419,12 @@ pub fn HtmlFormatter(comptime Writer: type) type { } fn collectText(self: *Self, node: *nodes.AstNode) ![]u8 { - var out = std.ArrayList(u8).init(self.allocator); + var out = ArrayList(u8).init(self.allocator); try collectTextInto(&out, node); return out.toOwnedSlice(); } - fn collectTextInto(out: *std.ArrayList(u8), node: *nodes.AstNode) std.mem.Allocator.Error!void { + fn collectTextInto(out: *ArrayList(u8), node: *nodes.AstNode) std.mem.Allocator.Error!void { switch (node.data.value) { .Text, .Code => |literal| { try out.appendSlice(literal); @@ -541,7 +542,7 @@ pub fn HtmlFormatter(comptime Writer: type) type { } test "escaping works as expected" { - var buffer = std.ArrayList(u8).init(std.testing.allocator); + var buffer = ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); var formatter = makeHtmlFormatter(buffer.writer(), std.testing.allocator, .{}); diff --git a/src/inlines.zig b/src/inlines.zig index 1410b1c..7fb4db8 100644 --- a/src/inlines.zig +++ b/src/inlines.zig @@ -1,6 +1,7 @@ const std = @import("std"); const mem = std.mem; const ascii = std.ascii; +const ArrayList = std.array_list.Managed; const assert = std.debug.assert; const zunicode = @import("zunicode"); @@ -22,7 +23,7 @@ pub const Subject = struct { input: []const u8, pos: usize = 0, last_delimiter: ?*Delimiter = null, - brackets: std.ArrayList(Bracket), + brackets: ArrayList(Bracket), backticks: [MAX_BACKTICKS + 1]usize = [_]usize{0} ** (MAX_BACKTICKS + 1), scanned_for_backticks: bool = false, special_chars: *const [256]bool, @@ -34,7 +35,7 @@ pub const Subject = struct { .refmap = refmap, .options = options, .input = input, - .brackets = std.ArrayList(Bracket).init(allocator), + .brackets = ArrayList(Bracket).init(allocator), .special_chars = special_chars, .skip_chars = skip_chars, }; @@ -121,7 +122,7 @@ pub const Subject = struct { fn makeInline(self: *Subject, value: nodes.NodeValue) !*nodes.AstNode { return nodes.AstNode.create(self.allocator, .{ .value = value, - .content = std.ArrayList(u8).init(self.allocator), + .content = ArrayList(u8).init(self.allocator), }); } @@ -363,7 +364,7 @@ pub const Subject = struct { fn handleEntity(self: *Subject) !*nodes.AstNode { self.pos += 1; - var out = std.ArrayList(u8).init(self.allocator); + var out = ArrayList(u8).init(self.allocator); if (try strings.unescapeInto(self.input[self.pos..], &out)) |len| { self.pos += len; return try self.makeInline(.{ .Text = try out.toOwnedSlice() }); @@ -440,7 +441,7 @@ pub const Subject = struct { else [2]usize{ 2, (num_hyphens - 4) / 3 }; - var text = std.ArrayList(u8).init(self.allocator); + var text = ArrayList(u8).init(self.allocator); while (ens_ems[1] > 0) : (ens_ems[1] -= 1) try text.appendSlice("—"); diff --git a/src/main.zig b/src/main.zig index 54bcfb5..5e26186 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); +const ArrayList = std.array_list.Managed; const assert = std.debug.assert; const clap = @import("clap"); @@ -10,6 +11,9 @@ const Options = koino.Options; const nodes = koino.nodes; const html = koino.html; +const MAX_BUFFER_SIZE = 64 * 1024; +const MAX_CONTENT_SIZE = 1024 * 1024 * 1024; + pub fn main() !void { // In debug, use the GeneralPurposeAllocator as the Parser internal allocator // to shake out memory issues. There should be no leaks in normal operation. @@ -40,18 +44,26 @@ pub fn main() !void { var parser = try Parser.init(allocator, options); if (args.positionals[0]) |pos| { - const markdown = try std.fs.cwd().readFileAlloc(allocator, pos, 1024 * 1024 * 1024); + const markdown = try std.fs.cwd().readFileAlloc(allocator, pos, MAX_CONTENT_SIZE); defer allocator.free(markdown); try parser.feed(markdown); } else { - const markdown = try std.io.getStdIn().reader().readAllAlloc(allocator, 1024 * 1024 * 1024); - defer allocator.free(markdown); + var stdin_buf: [MAX_BUFFER_SIZE]u8 = undefined; + var stdin_reader = std.fs.File.stdin().readerStreaming(&stdin_buf); + + var alloc_writer = std.Io.Writer.Allocating.init(allocator); + errdefer alloc_writer.deinit(); + + _ = try stdin_reader.interface.streamRemaining(&alloc_writer.writer); + const markdown = alloc_writer.written(); try parser.feed(markdown); + + alloc_writer.deinit(); } var doc = try parser.finish(); const output = blk: { - var arr = std.ArrayList(u8).init(allocator); + var arr = ArrayList(u8).init(allocator); errdefer arr.deinit(); try html.print(arr.writer(), allocator, options, doc); break :blk try arr.toOwnedSlice(); @@ -64,7 +76,10 @@ pub fn main() !void { doc.deinit(); } - try std.io.getStdOut().writer().writeAll(output); + var buf: [MAX_BUFFER_SIZE]u8 = undefined; + var stdout_writer = std.fs.File.stdout().writer(&buf); + try stdout_writer.interface.writeAll(output); + try stdout_writer.interface.flush(); } const params = clap.parseParamsComptime("-h, --help Display this help and exit\n" ++ @@ -77,15 +92,17 @@ const params = clap.parseParamsComptime("-h, --help Display this const ClapResult = clap.Result(clap.Help, ¶ms, clap.parsers.default); fn parseArgs(options: *Options, allocator: std.mem.Allocator) !ClapResult { - var stderr = std.io.getStdErr().writer(); + var stderr_buf: [MAX_BUFFER_SIZE]u8 = undefined; + var stderr = std.fs.File.stderr().writer(&stderr_buf); const res = try clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .allocator = allocator }); if (res.args.help != 0) { - try stderr.writeAll("Usage: koino "); - try clap.usage(stderr, clap.Help, ¶ms); - try stderr.writeAll("\n\nOptions:\n"); - try clap.help(stderr, clap.Help, ¶ms, .{}); + try stderr.interface.writeAll("Usage: koino "); + try clap.usage(&stderr.interface, clap.Help, ¶ms); + try stderr.interface.writeAll("\n\nOptions:\n"); + try clap.help(&stderr.interface, clap.Help, ¶ms, .{}); + try stderr.interface.flush(); std.process.exit(0); } @@ -132,7 +149,7 @@ fn enableExtension(extension: []const u8, options: *Options) !void { return; } } - try std.fmt.format(std.io.getStdErr().writer(), "unknown extension: {s}\n", .{extension}); + std.log.err("unknown extension: {s}\n", .{extension}); std.process.exit(1); } @@ -145,7 +162,7 @@ pub fn testMarkdownToHtml(options: Options, markdown: []const u8) ![]u8 { var doc = try koino.parse(gpa.allocator(), markdown, options); defer doc.deinit(); - var result = std.ArrayList(u8).init(std.testing.allocator); + var result = ArrayList(u8).init(std.testing.allocator); errdefer result.deinit(); try html.print(result.writer(), gpa.allocator(), options, doc); return result.toOwnedSlice(); diff --git a/src/nodes.zig b/src/nodes.zig index 23be0c3..d2e9273 100644 --- a/src/nodes.zig +++ b/src/nodes.zig @@ -1,12 +1,13 @@ const std = @import("std"); const mem = std.mem; +const ArrayList = std.array_list.Managed; const ast = @import("ast.zig"); pub const Node = struct { value: NodeValue, start_line: u32 = 0, - content: std.ArrayList(u8), + content: ArrayList(u8), open: bool = true, last_line_blank: bool = false, @@ -168,7 +169,7 @@ pub const NodeList = struct { pub const NodeHtmlBlock = struct { block_type: u8, - literal: std.ArrayList(u8), + literal: ArrayList(u8), }; pub const NodeCodeBlock = struct { @@ -177,7 +178,7 @@ pub const NodeCodeBlock = struct { fence_length: usize, fence_offset: usize, info: ?[]u8, - literal: std.ArrayList(u8), + literal: ArrayList(u8), }; pub const NodeHeading = struct { diff --git a/src/parser.zig b/src/parser.zig index 6903013..fcb8974 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1,6 +1,7 @@ const std = @import("std"); const assert = std.debug.assert; const ascii = std.ascii; +const ArrayList = std.array_list.Managed; const main = @import("main.zig"); const strings = @import("strings.zig"); @@ -22,7 +23,7 @@ pub const Reference = struct { pub const Parser = struct { allocator: std.mem.Allocator, refmap: std.StringHashMap(Reference), - hack_refmapKeys: std.ArrayList([]u8), + hack_refmapKeys: ArrayList([]u8), root: *nodes.AstNode, current: *nodes.AstNode, options: Options, @@ -43,13 +44,13 @@ pub const Parser = struct { pub fn init(allocator: std.mem.Allocator, options: Options) !Parser { const root = try nodes.AstNode.create(allocator, .{ .value = .Document, - .content = std.ArrayList(u8).init(allocator), + .content = ArrayList(u8).init(allocator), }); var parser = Parser{ .allocator = allocator, .refmap = std.StringHashMap(Reference).init(allocator), - .hack_refmapKeys = std.ArrayList([]u8).init(allocator), + .hack_refmapKeys = ArrayList([]u8).init(allocator), .root = root, .current = root, .options = options, @@ -73,7 +74,7 @@ pub const Parser = struct { pub fn feed(self: *Parser, s: []const u8) !void { var i: usize = 0; const sz = s.len; - var linebuf = std.ArrayList(u8).init(self.allocator); + var linebuf = ArrayList(u8).init(self.allocator); defer linebuf.deinit(); while (i < sz) { @@ -315,7 +316,7 @@ pub const Parser = struct { .fence_length = matched, .fence_offset = first_nonspace - offset, .info = null, - .literal = std.ArrayList(u8).init(self.allocator), + .literal = ArrayList(u8).init(self.allocator), }; container = try self.addChild(container, .{ .CodeBlock = ncb }); self.advanceOffset(line, first_nonspace + matched - offset, false); @@ -325,7 +326,7 @@ pub const Parser = struct { })) { const nhb = nodes.NodeHtmlBlock{ .block_type = @truncate(matched), - .literal = std.ArrayList(u8).init(self.allocator), + .literal = ArrayList(u8).init(self.allocator), }; container = try self.addChild(container, .{ .HtmlBlock = nhb }); } else if (!indented and switch (container.data.value) { @@ -402,7 +403,7 @@ pub const Parser = struct { .fence_length = 0, .fence_offset = 0, .info = null, - .literal = std.ArrayList(u8).init(self.allocator), + .literal = ArrayList(u8).init(self.allocator), }, }); } else { @@ -444,7 +445,7 @@ pub const Parser = struct { const node = try nodes.AstNode.create(self.allocator, .{ .value = value, .start_line = self.line_number, - .content = std.ArrayList(u8).init(self.allocator), + .content = ArrayList(u8).init(self.allocator), }); parent.append(node); return node; @@ -589,10 +590,10 @@ pub const Parser = struct { try node.data.content.replaceRange(0, pos, ""); } - std.mem.swap(std.ArrayList(u8), &ncb.literal, &node.data.content); + std.mem.swap(ArrayList(u8), &ncb.literal, &node.data.content); }, .HtmlBlock => |*nhb| { - std.mem.swap(std.ArrayList(u8), &nhb.literal, &node.data.content); + std.mem.swap(ArrayList(u8), &nhb.literal, &node.data.content); }, .List => |*nl| { nl.tight = true; @@ -627,9 +628,9 @@ pub const Parser = struct { } fn postprocessTextNodes(self: *Parser) !void { - var stack = try std.ArrayList(*nodes.AstNode).initCapacity(self.allocator, 1); + var stack = try ArrayList(*nodes.AstNode).initCapacity(self.allocator, 1); defer stack.deinit(); - var children = std.ArrayList(*nodes.AstNode).init(self.allocator); + var children = ArrayList(*nodes.AstNode).init(self.allocator); defer children.deinit(); try stack.append(self.root); @@ -686,7 +687,7 @@ pub const Parser = struct { } } - fn resolveReferenceLinkDefinitions(self: *Parser, content: *std.ArrayList(u8)) !bool { + fn resolveReferenceLinkDefinitions(self: *Parser, content: *ArrayList(u8)) !bool { var seeked: usize = 0; var pos: usize = undefined; var seek = content.items; diff --git a/src/scanners.zig b/src/scanners.zig index 5bc9e2c..a8ab638 100644 --- a/src/scanners.zig +++ b/src/scanners.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const ArrayList = std.array_list.Managed; const testing = std.testing; const Regex = @import("libpcre").Regex; @@ -412,7 +413,7 @@ test "tableRowEnd" { pub fn removeAnchorizeRejectedChars(allocator: std.mem.Allocator, src: []const u8) Error![]u8 { const re = try acquire(@src().fn_name, "[^\\p{L}\\p{M}\\p{N}\\p{Pc} -]"); - var output = std.ArrayList(u8).init(allocator); + var output = ArrayList(u8).init(allocator); errdefer output.deinit(); var org: usize = 0; diff --git a/src/strings.zig b/src/strings.zig index b1cb092..806b24d 100644 --- a/src/strings.zig +++ b/src/strings.zig @@ -2,6 +2,7 @@ const std = @import("std"); const mem = std.mem; const testing = std.testing; const ascii = std.ascii; +const ArrayList = std.array_list.Managed; const nodes = @import("nodes.zig"); const htmlentities = @import("htmlentities"); @@ -75,7 +76,7 @@ test "trim" { try testing.expectEqualStrings("abc \n zz", trim(" \nabc \n zz \n")); } -pub fn trimIt(al: *std.ArrayList(u8)) void { +pub fn trimIt(al: *ArrayList(u8)) void { const trimmed = trim(al.items); if (al.items.ptr == trimmed.ptr and al.items.len == trimmed.len) return; if (&al.items != &trimmed) { @@ -85,7 +86,7 @@ pub fn trimIt(al: *std.ArrayList(u8)) void { } test "trimIt" { - var buf = std.ArrayList(u8).init(std.testing.allocator); + var buf = ArrayList(u8).init(std.testing.allocator); defer buf.deinit(); try buf.appendSlice("abc"); @@ -132,7 +133,7 @@ test "chopTrailingHashtags" { } pub fn normalizeCode(allocator: mem.Allocator, s: []const u8) mem.Allocator.Error![]u8 { - var code = try std.ArrayList(u8).initCapacity(allocator, s.len); + var code = try ArrayList(u8).initCapacity(allocator, s.len); errdefer code.deinit(); var i: usize = 0; @@ -186,7 +187,7 @@ test "normalizeCode" { }); } -pub fn removeTrailingBlankLines(line: *std.ArrayList(u8)) void { +pub fn removeTrailingBlankLines(line: *ArrayList(u8)) void { var i = line.items.len - 1; while (true) : (i -= 1) { const c = line.items[i]; @@ -215,7 +216,7 @@ test "removeTrailingBlankLines" { .{ .in = "yep ", .out = "yep " }, }; - var line = std.ArrayList(u8).init(std.testing.allocator); + var line = ArrayList(u8).init(std.testing.allocator); defer line.deinit(); for (cases) |case| { line.items.len = 0; @@ -232,7 +233,7 @@ pub fn isPunct(char: u8) bool { }; } -fn encodeUtf8Into(in_cp: u21, al: *std.ArrayList(u8)) !void { +fn encodeUtf8Into(in_cp: u21, al: *ArrayList(u8)) !void { // utf8Encode throws: // - Utf8CannotEncodeSurrogateHalf, which we guard against that by // rewriting 0xd800..0xe0000 to 0xfffd. @@ -250,7 +251,7 @@ fn encodeUtf8Into(in_cp: u21, al: *std.ArrayList(u8)) !void { const ENTITY_MIN_LENGTH: u8 = 2; const ENTITY_MAX_LENGTH: u8 = 32; -pub fn unescapeInto(text: []const u8, out: *std.ArrayList(u8)) !?usize { +pub fn unescapeInto(text: []const u8, out: *ArrayList(u8)) !?usize { if (text.len >= 3 and text[0] == '#') { var codepoint: u32 = 0; var i: usize = 0; @@ -301,7 +302,7 @@ pub fn unescapeInto(text: []const u8, out: *std.ArrayList(u8)) !?usize { return null; } -fn unescapeHtmlInto(html: []const u8, out: *std.ArrayList(u8)) !void { +fn unescapeHtmlInto(html: []const u8, out: *ArrayList(u8)) !void { const size = html.len; var i: usize = 0; @@ -333,7 +334,7 @@ fn unescapeHtmlInto(html: []const u8, out: *std.ArrayList(u8)) !void { } pub fn unescapeHtml(allocator: mem.Allocator, html: []const u8) ![]u8 { - var al = std.ArrayList(u8).init(allocator); + var al = ArrayList(u8).init(allocator); errdefer al.deinit(); try unescapeHtmlInto(html, &al); return al.toOwnedSlice(); @@ -358,7 +359,7 @@ pub fn cleanAutolink(allocator: mem.Allocator, url: []const u8, kind: nodes.Auto if (trimmed.len == 0) return &[_]u8{}; - var buf = try std.ArrayList(u8).initCapacity(allocator, trimmed.len); + var buf = try ArrayList(u8).initCapacity(allocator, trimmed.len); errdefer buf.deinit(); if (kind == .Email) try buf.appendSlice("mailto:"); @@ -378,7 +379,7 @@ test "cleanAutolink" { } fn unescape(allocator: mem.Allocator, s: []const u8) ![]u8 { - var buffer = try std.ArrayList(u8).initCapacity(allocator, s.len); + var buffer = try ArrayList(u8).initCapacity(allocator, s.len); errdefer buffer.deinit(); var r: usize = 0; @@ -432,7 +433,7 @@ test "cleanTitle" { pub fn normalizeLabel(allocator: mem.Allocator, s: []const u8) ![]u8 { const trimmed = trim(s); - var buffer = try std.ArrayList(u8).initCapacity(allocator, trimmed.len); + var buffer = try ArrayList(u8).initCapacity(allocator, trimmed.len); errdefer buffer.deinit(); var last_was_whitespace = false; @@ -463,7 +464,7 @@ test "normalizeLabel" { } pub fn toLower(allocator: mem.Allocator, s: []const u8) ![]u8 { - var buffer = try std.ArrayList(u8).initCapacity(allocator, s.len); + var buffer = try ArrayList(u8).initCapacity(allocator, s.len); errdefer buffer.deinit(); var view = try std.unicode.Utf8View.init(s); var it = view.iterator(); diff --git a/src/table.zig b/src/table.zig index 44691e9..0b3b4ce 100644 --- a/src/table.zig +++ b/src/table.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const ArrayList = std.array_list.Managed; const Parser = @import("parser.zig").Parser; const nodes = @import("nodes.zig"); const scanners = @import("scanners.zig"); @@ -19,7 +20,7 @@ pub fn freeNested(allocator: std.mem.Allocator, v: [][]u8) void { fn row(allocator: std.mem.Allocator, line: []const u8) !?[][]u8 { const len = line.len; - var v = std.ArrayList([]u8).init(allocator); + var v = ArrayList([]u8).init(allocator); errdefer freeNested(allocator, v.toOwnedSlice() catch unreachable); var offset: usize = 0; @@ -103,7 +104,7 @@ fn tryOpeningHeader(parser: *Parser, container: *nodes.AstNode, line: []const u8 const table = try nodes.AstNode.create(parser.allocator, .{ .value = .{ .Table = alignments }, .start_line = parser.line_number, - .content = std.ArrayList(u8).init(parser.allocator), + .content = ArrayList(u8).init(parser.allocator), }); container.append(table); @@ -145,8 +146,8 @@ fn tryOpeningRow(parser: *Parser, container: *nodes.AstNode, aligns: []nodes.Tab return new_row; } -fn unescapePipes(allocator: std.mem.Allocator, string: []const u8) !std.ArrayList(u8) { - var v = try std.ArrayList(u8).initCapacity(allocator, string.len); +fn unescapePipes(allocator: std.mem.Allocator, string: []const u8) !ArrayList(u8) { + var v = try ArrayList(u8).initCapacity(allocator, string.len); for (string, 0..) |c, i| { if (c == '\\' and i + 1 < string.len and string[i + 1] == '|') {