website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


a5ae6b8pyrossh 2026-07-14T14:04:21+05:30
Task 2: core lib modules - frontmatter, markdown, content
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
+ }