website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
a5ae6b8
— pyrossh
2026-07-14T14:04:21+05:30
Task 2: core lib modules - frontmatter, markdown, content
- build.zig +3 -0
- src/lib.zig +3 -3
- src/lib/content.zig +85 -0
- src/lib/frontmatter.zig +39 -0
- src/lib/markdown.zig +6 -0
build.zig
CHANGED
|
@@ -13,6 +13,9 @@ pub fn build(b: *std.Build) void {
|
|
|
13
13
|
lib_mod.addImport("config", config_mod);
|
|
14
14
|
lib_mod.addImport("s3", s3_mod);
|
|
15
15
|
|
|
16
|
+
const zmd_mod = b.dependency("zmd", .{}).module("zmd");
|
|
17
|
+
lib_mod.addImport("zmd", zmd_mod);
|
|
18
|
+
|
|
16
19
|
const main_mod = b.createModule(.{
|
|
17
20
|
.root_source_file = b.path("src/main.zig"),
|
|
18
21
|
.target = target,
|
src/lib.zig
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
pub const config = @import("config");
|
|
2
2
|
pub const s3 = @import("s3");
|
|
3
|
-
pub const content = @import("content.zig");
|
|
3
|
+
pub const content = @import("lib/content.zig");
|
|
4
|
-
pub const frontmatter = @import("frontmatter.zig");
|
|
4
|
+
pub const frontmatter = @import("lib/frontmatter.zig");
|
|
5
|
-
pub const markdown = @import("markdown.zig");
|
|
5
|
+
pub const markdown = @import("lib/markdown.zig");
|
|
6
6
|
pub const files = @import("files.zig");
|
|
7
7
|
pub const gitBug = @import("gitBug.zig");
|
|
8
8
|
pub const gitReader = @import("gitReader.zig");
|
src/lib/content.zig
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const frontmatter = @import("frontmatter.zig");
|
|
3
|
+
const markdown = @import("markdown.zig");
|
|
4
|
+
|
|
5
|
+
pub const Post = struct {
|
|
6
|
+
id: []const u8,
|
|
7
|
+
title: []const u8,
|
|
8
|
+
description: []const u8,
|
|
9
|
+
pubDate: []const u8,
|
|
10
|
+
body: []const u8,
|
|
11
|
+
html: []const u8,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const content_dir = "content";
|
|
15
|
+
|
|
16
|
+
pub fn getPosts(allocator: std.mem.Allocator) ![]Post {
|
|
17
|
+
var dir = try std.fs.cwd().openDir(content_dir, .{ .iterate = true });
|
|
18
|
+
defer dir.close();
|
|
19
|
+
|
|
20
|
+
var posts = std.ArrayList(Post).init(allocator);
|
|
21
|
+
var walker = try dir.walk(allocator);
|
|
22
|
+
defer walker.deinit();
|
|
23
|
+
|
|
24
|
+
while (try walker.next()) |entry| {
|
|
25
|
+
if (entry.kind != .file) continue;
|
|
26
|
+
if (!std.mem.endsWith(u8, entry.path, ".md")) continue;
|
|
27
|
+
|
|
28
|
+
const file = try entry.dir.openFile(entry.basename, .{});
|
|
29
|
+
defer file.close();
|
|
30
|
+
|
|
31
|
+
const source = try file.readToEndAlloc(allocator, 10 * 1024 * 1024);
|
|
32
|
+
defer allocator.free(source);
|
|
33
|
+
|
|
34
|
+
const parsed = frontmatter.parse(source) orelse continue;
|
|
35
|
+
if (!parsed.fm.published) continue;
|
|
36
|
+
|
|
37
|
+
const id = try allocator.dupe(u8, entry.path[0 .. entry.path.len - 3]);
|
|
38
|
+
const html = markdown.render(allocator, parsed.body) catch continue;
|
|
39
|
+
|
|
40
|
+
try posts.append(.{
|
|
41
|
+
.id = id,
|
|
42
|
+
.title = try allocator.dupe(u8, parsed.fm.title),
|
|
43
|
+
.description = try allocator.dupe(u8, parsed.fm.description),
|
|
44
|
+
.pubDate = try allocator.dupe(u8, parsed.fm.pubDate),
|
|
45
|
+
.body = try allocator.dupe(u8, parsed.body),
|
|
46
|
+
.html = html,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
std.mem.sort(Post, posts.items, {}, struct {
|
|
51
|
+
fn lessThan(_: void, a: Post, b: Post) bool {
|
|
52
|
+
return std.mem.order(u8, a.pubDate, b.pubDate) == .gt;
|
|
53
|
+
}
|
|
54
|
+
}.lessThan);
|
|
55
|
+
|
|
56
|
+
return posts.toOwnedSlice();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
pub fn getPost(allocator: std.mem.Allocator, id: []const u8) !?Post {
|
|
60
|
+
const posts = try getPosts(allocator);
|
|
61
|
+
defer {
|
|
62
|
+
for (posts) |p| {
|
|
63
|
+
allocator.free(p.id);
|
|
64
|
+
allocator.free(p.title);
|
|
65
|
+
allocator.free(p.description);
|
|
66
|
+
allocator.free(p.pubDate);
|
|
67
|
+
allocator.free(p.body);
|
|
68
|
+
allocator.free(p.html);
|
|
69
|
+
}
|
|
70
|
+
allocator.free(posts);
|
|
71
|
+
}
|
|
72
|
+
for (posts) |p| {
|
|
73
|
+
if (std.mem.eql(u8, p.id, id)) {
|
|
74
|
+
return .{
|
|
75
|
+
.id = try allocator.dupe(u8, p.id),
|
|
76
|
+
.title = try allocator.dupe(u8, p.title),
|
|
77
|
+
.description = try allocator.dupe(u8, p.description),
|
|
78
|
+
.pubDate = try allocator.dupe(u8, p.pubDate),
|
|
79
|
+
.body = try allocator.dupe(u8, p.body),
|
|
80
|
+
.html = try allocator.dupe(u8, p.html),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
src/lib/frontmatter.zig
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
pub const Frontmatter = struct {
|
|
4
|
+
title: []const u8 = "",
|
|
5
|
+
description: []const u8 = "",
|
|
6
|
+
pubDate: []const u8 = "",
|
|
7
|
+
tags: []const []const u8 = &.{},
|
|
8
|
+
heroImage: ?[]const u8 = null,
|
|
9
|
+
published: bool = true,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
pub fn parse(source: []const u8) ?struct { fm: Frontmatter, body: []const u8 } {
|
|
13
|
+
const trimmed = std.mem.trim(u8, source, " \n\r");
|
|
14
|
+
if (trimmed.len < 6) return null;
|
|
15
|
+
if (!std.mem.startsWith(u8, trimmed, "---")) return null;
|
|
16
|
+
|
|
17
|
+
const end = std.mem.indexOf(u8, trimmed[3..], "---") orelse return null;
|
|
18
|
+
const raw_fm = trimmed[3 .. 3 + end];
|
|
19
|
+
const body_start = 6 + end;
|
|
20
|
+
const body = if (body_start < trimmed.len) std.mem.trimLeft(u8, trimmed[body_start..], "\n\r") else "";
|
|
21
|
+
|
|
22
|
+
var fm = Frontmatter{};
|
|
23
|
+
var lines = std.mem.splitScalar(u8, raw_fm, '\n');
|
|
24
|
+
while (lines.next()) |line| {
|
|
25
|
+
const trimmed_line = std.mem.trim(u8, line, " \r");
|
|
26
|
+
if (trimmed_line.len == 0) continue;
|
|
27
|
+
const colon = std.mem.indexOfScalar(u8, trimmed_line, ':') orelse continue;
|
|
28
|
+
const key = std.mem.trim(u8, trimmed_line[0..colon], " ");
|
|
29
|
+
const value = std.mem.trim(u8, trimmed_line[colon + 1 ..], " \"'");
|
|
30
|
+
|
|
31
|
+
if (std.mem.eql(u8, key, "title")) fm.title = value;
|
|
32
|
+
else if (std.mem.eql(u8, key, "description")) fm.description = value;
|
|
33
|
+
else if (std.mem.eql(u8, key, "pubDate")) fm.pubDate = value;
|
|
34
|
+
else if (std.mem.eql(u8, key, "heroImage")) fm.heroImage = value;
|
|
35
|
+
else if (std.mem.eql(u8, key, "published")) fm.published = std.mem.eql(u8, value, "true");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return .{ .fm = fm, .body = body };
|
|
39
|
+
}
|
src/lib/markdown.zig
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const zmd = @import("zmd");
|
|
3
|
+
|
|
4
|
+
pub fn render(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
|
|
5
|
+
return try zmd.parseAlloc(allocator, input, .{});
|
|
6
|
+
}
|