better error messaging
This commit is contained in:
parent
a559a621c3
commit
3b195a9eb6
4 changed files with 156 additions and 12 deletions
39
src/atom.zig
39
src/atom.zig
|
@ -169,9 +169,9 @@ pub fn generateFeed(allocator: Allocator, releases: []const Release) ![]u8 {
|
|||
\\<feed xmlns="http://www.w3.org/2005/Atom">
|
||||
\\<title>Repository Releases</title>
|
||||
\\<subtitle>New releases from starred repositories</subtitle>
|
||||
\\<link href="https://github.com" rel="alternate"/>
|
||||
\\<link href="https://example.com/releases.xml" rel="self"/>
|
||||
\\<id>https://example.com/releases</id>
|
||||
\\<link href="https://releases.lerch.org" rel="alternate"/>
|
||||
\\<link href="https://releases.lerch.org/atom.xml" rel="self"/>
|
||||
\\<id>https://releases.lerch.org</id>
|
||||
\\
|
||||
);
|
||||
|
||||
|
@ -303,6 +303,37 @@ test "Atom feed generation with markdown" {
|
|||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<ul>") != null);
|
||||
}
|
||||
|
||||
test "Atom feed with fenced code blocks" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
const releases = [_]Release{
|
||||
Release{
|
||||
.repo_name = "test/repo",
|
||||
.tag_name = "v1.0.0",
|
||||
.published_at = @intCast(@divTrunc(
|
||||
(try zeit.instant(.{ .source = .{ .iso8601 = "2024-01-01T00:00:00Z" } })).timestamp,
|
||||
std.time.ns_per_s,
|
||||
)),
|
||||
.html_url = "https://github.com/test/repo/releases/tag/v1.0.0",
|
||||
.description = "Here's some code:\n```javascript\nconst greeting = 'Hello World';\nconsole.log(greeting);\n```\nEnd of example.",
|
||||
.provider = "github",
|
||||
},
|
||||
};
|
||||
|
||||
const atom_content = try generateFeed(allocator, &releases);
|
||||
defer allocator.free(atom_content);
|
||||
|
||||
// Should NOT contain fallback metadata since fenced code blocks are now supported
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "markdown-fallback") == null);
|
||||
|
||||
// Should contain proper HTML code block structure
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<pre><code class="language-javascript">") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "</code></pre>") != null);
|
||||
|
||||
// Should contain the escaped code content
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "const greeting = &apos;Hello World&apos;;") != null);
|
||||
}
|
||||
|
||||
test "Atom feed with fallback markdown" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
|
@ -315,7 +346,7 @@ test "Atom feed with fallback markdown" {
|
|||
std.time.ns_per_s,
|
||||
)),
|
||||
.html_url = "https://github.com/test/repo/releases/tag/v1.0.0",
|
||||
.description = "```javascript\nconst x = 1;\n```",
|
||||
.description = "| Column 1 | Column 2 |\n|----------|----------|\n| Value 1 | Value 2 |",
|
||||
.provider = "github",
|
||||
},
|
||||
};
|
||||
|
|
|
@ -376,9 +376,9 @@ test "atom feed generation" {
|
|||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<feed xmlns=\"http://www.w3.org/2005/Atom\">") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<title>Repository Releases</title>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<subtitle>New releases from starred repositories</subtitle>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<link href=\"https://github.com\" rel=\"alternate\"/>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<link href=\"https://example.com/releases.xml\" rel=\"self\"/>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<id>https://example.com/releases</id>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<link href=\"https://releases.lerch.org\" rel=\"alternate\"/>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<link href=\"https://releases.lerch.org/atom.xml\" rel=\"self\"/>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<id>https://releases.lerch.org</id>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<updated>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "<entry>") != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, atom_content, "</feed>") != null);
|
||||
|
|
112
src/markdown.zig
112
src/markdown.zig
|
@ -21,10 +21,53 @@ pub fn convertMarkdownToHtml(allocator: Allocator, markdown: []const u8) !Conver
|
|||
var lines = std.mem.splitScalar(u8, markdown, '\n');
|
||||
var in_list = false;
|
||||
var list_type: ?u8 = null; // '*' or '-'
|
||||
var in_code_block = false;
|
||||
var code_block_fence: []const u8 = "";
|
||||
|
||||
while (lines.next()) |line| {
|
||||
const trimmed = std.mem.trim(u8, line, " \t\r");
|
||||
|
||||
// Handle fenced code blocks
|
||||
if (std.mem.startsWith(u8, trimmed, "```") or std.mem.startsWith(u8, trimmed, "~~~")) {
|
||||
const fence = if (std.mem.startsWith(u8, trimmed, "```")) "```" else "~~~";
|
||||
|
||||
if (!in_code_block) {
|
||||
// Starting a code block
|
||||
if (in_list) {
|
||||
try result.appendSlice("</ul>\n");
|
||||
in_list = false;
|
||||
list_type = null;
|
||||
}
|
||||
|
||||
in_code_block = true;
|
||||
code_block_fence = fence;
|
||||
|
||||
// Extract language hint if present
|
||||
const lang_hint = std.mem.trim(u8, trimmed[fence.len..], " \t\r");
|
||||
if (lang_hint.len > 0) {
|
||||
try result.appendSlice("<pre><code class=\"language-");
|
||||
try appendEscapedHtml(&result, lang_hint);
|
||||
try result.appendSlice("\">");
|
||||
} else {
|
||||
try result.appendSlice("<pre><code>");
|
||||
}
|
||||
continue;
|
||||
} else if (std.mem.eql(u8, fence, code_block_fence)) {
|
||||
// Ending the code block
|
||||
in_code_block = false;
|
||||
code_block_fence = "";
|
||||
try result.appendSlice("</code></pre>\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're inside a code block, just add the line as-is (escaped)
|
||||
if (in_code_block) {
|
||||
try appendEscapedHtml(&result, line);
|
||||
try result.appendSlice("\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trimmed.len == 0) {
|
||||
try result.appendSlice("<br/>\n");
|
||||
continue;
|
||||
|
@ -130,6 +173,11 @@ pub fn convertMarkdownToHtml(allocator: Allocator, markdown: []const u8) !Conver
|
|||
try result.appendSlice("</ul>\n");
|
||||
}
|
||||
|
||||
// Close any unclosed code block
|
||||
if (in_code_block) {
|
||||
try result.appendSlice("</code></pre>\n");
|
||||
}
|
||||
|
||||
return ConversionResult{
|
||||
.html = try result.toOwnedSlice(),
|
||||
.has_fallback = has_fallback,
|
||||
|
@ -313,9 +361,6 @@ fn findInlineCode(text: []const u8) ?TextInfo {
|
|||
|
||||
/// Check if text contains complex markdown patterns we don't handle
|
||||
fn hasComplexMarkdown(text: []const u8) bool {
|
||||
// Code blocks
|
||||
if (std.mem.indexOf(u8, text, "```") != null) return true;
|
||||
|
||||
// Tables
|
||||
if (std.mem.indexOf(u8, text, "|") != null) return true;
|
||||
|
||||
|
@ -334,7 +379,66 @@ fn hasComplexMarkdown(text: []const u8) bool {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Tests
|
||||
test "convert fenced code blocks" {
|
||||
const allocator = testing.allocator;
|
||||
|
||||
// Test basic fenced code block with backticks
|
||||
const markdown1 = "Here's some code:\n```\nconst x = 42;\nconsole.log(x);\n```\nEnd of code.";
|
||||
const result1 = try convertMarkdownToHtml(allocator, markdown1);
|
||||
defer result1.deinit(allocator);
|
||||
|
||||
const expected1 = "<p>Here's some code:</p>\n<pre><code>const x = 42;\nconsole.log(x);\n</code></pre>\n<p>End of code.</p>\n";
|
||||
try testing.expectEqualStrings(expected1, result1.html);
|
||||
try testing.expect(!result1.has_fallback);
|
||||
|
||||
// Test fenced code block with language hint
|
||||
const markdown2 = "```javascript\nconst greeting = 'Hello World';\nconsole.log(greeting);\n```";
|
||||
const result2 = try convertMarkdownToHtml(allocator, markdown2);
|
||||
defer result2.deinit(allocator);
|
||||
|
||||
const expected2 = "<pre><code class=\"language-javascript\">const greeting = 'Hello World';\nconsole.log(greeting);\n</code></pre>\n";
|
||||
try testing.expectEqualStrings(expected2, result2.html);
|
||||
try testing.expect(!result2.has_fallback);
|
||||
|
||||
// Test fenced code block with tildes
|
||||
const markdown3 = "~~~python\ndef hello():\n print('Hello!')\n~~~";
|
||||
const result3 = try convertMarkdownToHtml(allocator, markdown3);
|
||||
defer result3.deinit(allocator);
|
||||
|
||||
const expected3 = "<pre><code class=\"language-python\">def hello():\n print('Hello!')\n</code></pre>\n";
|
||||
try testing.expectEqualStrings(expected3, result3.html);
|
||||
try testing.expect(!result3.has_fallback);
|
||||
|
||||
// Test unclosed code block (should auto-close)
|
||||
const markdown4 = "```\nunclosed code block\nmore code";
|
||||
const result4 = try convertMarkdownToHtml(allocator, markdown4);
|
||||
defer result4.deinit(allocator);
|
||||
|
||||
const expected4 = "<pre><code>unclosed code block\nmore code\n</code></pre>\n";
|
||||
try testing.expectEqualStrings(expected4, result4.html);
|
||||
try testing.expect(!result4.has_fallback);
|
||||
|
||||
if (std.process.hasEnvVar(allocator, "test-debug") catch false) {
|
||||
std.debug.print("Fenced code blocks test - Input: {s}\nOutput: {s}\n", .{ markdown1, result1.html });
|
||||
}
|
||||
}
|
||||
|
||||
test "inline code with backticks" {
|
||||
const allocator = testing.allocator;
|
||||
|
||||
const markdown = "Use `const` for constants and `let` for variables.";
|
||||
const result = try convertMarkdownToHtml(allocator, markdown);
|
||||
defer result.deinit(allocator);
|
||||
|
||||
const expected = "<p>Use <code>const</code> for constants and <code>let</code> for variables.</p>\n";
|
||||
try testing.expectEqualStrings(expected, result.html);
|
||||
try testing.expect(!result.has_fallback);
|
||||
|
||||
if (std.process.hasEnvVar(allocator, "test-debug") catch false) {
|
||||
std.debug.print("Inline code test - Input: {s}\nOutput: {s}\n", .{ markdown, result.html });
|
||||
}
|
||||
}
|
||||
|
||||
test "convert headers" {
|
||||
const allocator = testing.allocator;
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ fn fetchRepoReleasesTask(task: *RepoFetchTask) void {
|
|||
defer client.deinit();
|
||||
|
||||
const repo_releases = getRepoReleases(task.allocator, &client, task.token, task.repo) catch |err| {
|
||||
task.error_msg = std.fmt.allocPrint(task.allocator, "{}", .{err}) catch "Unknown error";
|
||||
task.error_msg = std.fmt.allocPrint(task.allocator, "{s}: {}", .{ task.repo, err }) catch "Unknown error";
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -378,6 +378,15 @@ fn getRepoReleases(allocator: Allocator, client: *http.Client, token: []const u8
|
|||
try req.wait();
|
||||
|
||||
if (req.response.status != .ok) {
|
||||
const is_test = @import("builtin").is_test;
|
||||
if (!is_test) {
|
||||
// Try to read the error response body for more details
|
||||
const error_body = req.reader().readAllAlloc(allocator, 4096) catch "";
|
||||
defer if (error_body.len > 0) allocator.free(error_body);
|
||||
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
stderr.print("GitHub: Failed to fetch releases for {s}: HTTP {} - {s}\n", .{ repo, @intFromEnum(req.response.status), error_body }) catch {};
|
||||
}
|
||||
return error.HttpRequestFailed;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue