aboutsummaryrefslogtreecommitdiff
path: root/lib/docs
diff options
context:
space:
mode:
authorIan Johnson <ian@ianjohnson.dev>2024-03-18 22:12:06 -0400
committerIan Johnson <ian@ianjohnson.dev>2024-03-22 20:03:32 -0400
commitd3ca9d55d9bae81aa3d01cd9936ff66bb26a8e9c (patch)
tree81da60ff9f20902d0031e8b81644733630331e9d /lib/docs
parent13a9d94a8038727469cf11b72273ce4ea6d89faa (diff)
downloadzig-d3ca9d55d9bae81aa3d01cd9936ff66bb26a8e9c.tar.gz
zig-d3ca9d55d9bae81aa3d01cd9936ff66bb26a8e9c.zip
Autodoc: implement Markdown autolinks
Closes #19265 This commit implements support for Markdown autolinks delimited by angle brackets. The precise syntax accepted is documented in the doc comment of `markdown.zig`.
Diffstat (limited to 'lib/docs')
-rw-r--r--lib/docs/wasm/markdown.zig30
-rw-r--r--lib/docs/wasm/markdown/Document.zig2
-rw-r--r--lib/docs/wasm/markdown/Parser.zig47
-rw-r--r--lib/docs/wasm/markdown/renderer.zig6
4 files changed, 84 insertions, 1 deletions
diff --git a/lib/docs/wasm/markdown.zig b/lib/docs/wasm/markdown.zig
index 4ce1ee15b4..092906c46a 100644
--- a/lib/docs/wasm/markdown.zig
+++ b/lib/docs/wasm/markdown.zig
@@ -75,6 +75,12 @@
//! content. `target` may contain `\`-escaped characters and balanced
//! parentheses.
//!
+//! - **Autolink** - an abbreviated link, of the format `<target>`, where
+//! `target` serves as both the link target and text. `target` may not
+//! contain spaces or `<`, and any `\` in it are interpreted literally (not as
+//! escapes). `target` is expected to be an absolute URI: an autolink will not
+//! be recognized unless `target` starts with a URI scheme followed by a `:`.
+//!
//! - **Image** - a link directly preceded by a `!`. The link text is
//! interpreted as the alt text of the image.
//!
@@ -710,6 +716,30 @@ test "links" {
);
}
+test "autolinks" {
+ try testRender(
+ \\<https://example.com>
+ \\**This is important: <https://example.com/strong>**
+ \\<https://example.com?query=abc.123#page(parens)>
+ \\<placeholder>
+ \\<data:>
+ \\1 < 2
+ \\4 > 3
+ \\Unclosed: <
+ \\
+ ,
+ \\<p><a href="https://example.com">https://example.com</a>
+ \\<strong>This is important: <a href="https://example.com/strong">https://example.com/strong</a></strong>
+ \\<a href="https://example.com?query=abc.123#page(parens)">https://example.com?query=abc.123#page(parens)</a>
+ \\&lt;placeholder&gt;
+ \\<a href="data:">data:</a>
+ \\1 &lt; 2
+ \\4 &gt; 3
+ \\Unclosed: &lt;</p>
+ \\
+ );
+}
+
test "images" {
try testRender(
\\![Alt text](https://example.com/image.png)
diff --git a/lib/docs/wasm/markdown/Document.zig b/lib/docs/wasm/markdown/Document.zig
index 9e43e35795..f3c0fdeed0 100644
--- a/lib/docs/wasm/markdown/Document.zig
+++ b/lib/docs/wasm/markdown/Document.zig
@@ -51,6 +51,8 @@ pub const Node = struct {
// Inlines
/// Data is `link`.
link,
+ /// Data is `text`.
+ autolink,
/// Data is `link`.
image,
/// Data is `container`.
diff --git a/lib/docs/wasm/markdown/Parser.zig b/lib/docs/wasm/markdown/Parser.zig
index 7cee596746..5a52882e48 100644
--- a/lib/docs/wasm/markdown/Parser.zig
+++ b/lib/docs/wasm/markdown/Parser.zig
@@ -985,6 +985,7 @@ const InlineParser = struct {
ip.pos += 1;
},
']' => try ip.parseLink(),
+ '<' => try ip.parseAutolink(),
'*', '_' => try ip.parseEmphasis(),
'`' => try ip.parseCodeSpan(),
else => {},
@@ -1076,6 +1077,52 @@ const InlineParser = struct {
return @enumFromInt(string_top);
}
+ /// Parses an autolink, starting at the opening `<`. `ip.pos` is left at the
+ /// closing `>`, or remains unchanged at the opening `<` if there is none.
+ fn parseAutolink(ip: *InlineParser) !void {
+ const start = ip.pos;
+ ip.pos += 1;
+ var state: enum {
+ start,
+ scheme,
+ target,
+ } = .start;
+ while (ip.pos < ip.content.len) : (ip.pos += 1) {
+ switch (state) {
+ .start => switch (ip.content[ip.pos]) {
+ 'A'...'Z', 'a'...'z' => state = .scheme,
+ else => break,
+ },
+ .scheme => switch (ip.content[ip.pos]) {
+ 'A'...'Z', 'a'...'z', '0'...'9', '+', '.', '-' => {},
+ ':' => state = .target,
+ else => break,
+ },
+ .target => switch (ip.content[ip.pos]) {
+ '<', ' ', '\t', '\n' => break, // Not allowed in autolinks
+ '>' => {
+ // Backslash escapes are not recognized in autolink targets.
+ const target = try ip.parent.addString(ip.content[start + 1 .. ip.pos]);
+ const node = try ip.parent.addNode(.{
+ .tag = .autolink,
+ .data = .{ .text = .{
+ .content = target,
+ } },
+ });
+ try ip.completed_inlines.append(ip.parent.allocator, .{
+ .node = node,
+ .start = start,
+ .len = ip.pos - start + 1,
+ });
+ return;
+ },
+ else => {},
+ },
+ }
+ }
+ ip.pos = start;
+ }
+
/// Parses emphasis, starting at the beginning of a run of `*` or `_`
/// characters. `ip.pos` is left at the last character in the run after
/// parsing.
diff --git a/lib/docs/wasm/markdown/renderer.zig b/lib/docs/wasm/markdown/renderer.zig
index fd361a379e..1e6041399a 100644
--- a/lib/docs/wasm/markdown/renderer.zig
+++ b/lib/docs/wasm/markdown/renderer.zig
@@ -140,6 +140,10 @@ pub fn Renderer(comptime Writer: type, comptime Context: type) type {
}
try writer.writeAll("</a>");
},
+ .autolink => {
+ const target = doc.string(data.text.content);
+ try writer.print("<a href=\"{0}\">{0}</a>", .{fmtHtml(target)});
+ },
.image => {
const target = doc.string(data.link.target);
try writer.print("<img src=\"{}\" alt=\"", .{fmtHtml(target)});
@@ -215,7 +219,7 @@ pub fn renderInlineNodeText(
try renderInlineNodeText(doc, child, writer);
}
},
- .code_span, .text => {
+ .autolink, .code_span, .text => {
const content = doc.string(data.text.content);
try writer.print("{}", .{fmtHtml(content)});
},