website

#astro#js#html#css

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

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


8316688pyrossh 2026-07-14T13:35:03+05:30
feat: add infrastructure modules for merjs port
Files changed (6) hide show
  1. build.zig +120 -0
  2. build.zig.zon +26 -0
  3. src/config.zig +67 -0
  4. src/lib.zig +10 -0
  5. src/mercss_jit.zig +30 -0
  6. src/s3.zig +122 -0
build.zig ADDED
@@ -0,0 +1,120 @@
1
+ const std = @import("std");
2
+
3
+ pub fn build(b: *std.Build) void {
4
+ const target = b.standardTargetOptions(.{});
5
+ const optimize = b.standardOptimizeOption(.{});
6
+
7
+ const merjs_dep = b.dependency("merjs", .{});
8
+ const mer_mod = merjs_dep.module("mer");
9
+
10
+ const config_mod = b.createModule(.{ .root_source_file = b.path("src/config.zig") });
11
+ const s3_mod = b.createModule(.{ .root_source_file = b.path("src/s3.zig") });
12
+ const lib_mod = b.createModule(.{ .root_source_file = b.path("src/lib.zig") });
13
+ lib_mod.addImport("config", config_mod);
14
+ lib_mod.addImport("s3", s3_mod);
15
+
16
+ const main_mod = b.createModule(.{
17
+ .root_source_file = b.path("src/main.zig"),
18
+ .target = target,
19
+ .optimize = optimize,
20
+ .strip = if (optimize != .Debug) true else null,
21
+ });
22
+ main_mod.addImport("mer", mer_mod);
23
+ main_mod.addImport("config", config_mod);
24
+ main_mod.addImport("s3", s3_mod);
25
+ main_mod.addImport("lib", lib_mod);
26
+ addDirModules(b, main_mod, mer_mod, config_mod, s3_mod, lib_mod, "app");
27
+ addDirModules(b, main_mod, mer_mod, config_mod, s3_mod, lib_mod, "api");
28
+ addRoutesModule(b, main_mod, mer_mod, config_mod, s3_mod, lib_mod);
29
+
30
+ const exe = b.addExecutable(.{ .name = "app", .root_module = main_mod });
31
+ b.installArtifact(exe);
32
+
33
+ // zig build codegen
34
+ const codegen_mod = b.createModule(.{
35
+ .root_source_file = b.path("tools/codegen.zig"),
36
+ .target = b.graph.host,
37
+ .optimize = .Debug,
38
+ });
39
+ codegen_mod.addImport("runtime", merjs_dep.module("runtime"));
40
+ const mercss_mod = b.createModule(.{ .root_source_file = b.path("src/mercss_jit.zig") });
41
+ codegen_mod.addImport("mercss_jit", mercss_mod);
42
+ const codegen_exe = b.addExecutable(.{
43
+ .name = "codegen",
44
+ .root_module = codegen_mod,
45
+ });
46
+ const run_codegen = b.addRunArtifact(codegen_exe);
47
+ run_codegen.setCwd(b.path("."));
48
+ b.step("codegen", "Regenerate src/generated/routes.zig").dependOn(&run_codegen.step);
49
+
50
+ // Auto-run codegen before compiling (fresh clones just work).
51
+ exe.step.dependOn(&run_codegen.step);
52
+
53
+ // zig build serve
54
+ const run_exe = b.addRunArtifact(exe);
55
+ run_exe.step.dependOn(b.getInstallStep());
56
+ if (b.args) |args| run_exe.addArgs(args);
57
+ b.step("serve", "Start the dev server").dependOn(&run_exe.step);
58
+
59
+ // zig build test
60
+ const test_mod = b.createModule(.{
61
+ .root_source_file = b.path("src/main.zig"),
62
+ .target = target,
63
+ .optimize = optimize,
64
+ });
65
+ test_mod.addImport("mer", mer_mod);
66
+ test_mod.addImport("config", config_mod);
67
+ test_mod.addImport("s3", s3_mod);
68
+ test_mod.addImport("lib", lib_mod);
69
+ addDirModules(b, test_mod, mer_mod, config_mod, s3_mod, lib_mod, "app");
70
+ addDirModules(b, test_mod, mer_mod, config_mod, s3_mod, lib_mod, "api");
71
+ addRoutesModule(b, test_mod, mer_mod, config_mod, s3_mod, lib_mod);
72
+ const run_tests = b.addRunArtifact(b.addTest(.{ .root_module = test_mod }));
73
+ run_tests.step.dependOn(&run_codegen.step);
74
+ b.step("test", "Compile the starter app").dependOn(&run_tests.step);
75
+ }
76
+
77
+ fn addRoutesModule(b: *std.Build, mod: *std.Build.Module, mer_mod: *std.Build.Module, config_mod: *std.Build.Module, s3_mod: *std.Build.Module, lib_mod: *std.Build.Module) void {
78
+ const routes_mod = b.createModule(.{
79
+ .root_source_file = b.path("src/generated/routes.zig"),
80
+ });
81
+ routes_mod.addImport("mer", mer_mod);
82
+ routes_mod.addImport("config", config_mod);
83
+ routes_mod.addImport("s3", s3_mod);
84
+ routes_mod.addImport("lib", lib_mod);
85
+ addDirModules(b, routes_mod, mer_mod, config_mod, s3_mod, lib_mod, "app");
86
+ addDirModules(b, routes_mod, mer_mod, config_mod, s3_mod, lib_mod, "api");
87
+ mod.addImport("routes", routes_mod);
88
+ }
89
+
90
+ fn addDirModules(b: *std.Build, mod: *std.Build.Module, mer_mod: *std.Build.Module, config_mod: *std.Build.Module, s3_mod: *std.Build.Module, lib_mod: *std.Build.Module, dir: []const u8) void {
91
+ const layout_path = b.fmt("{s}/layout.zig", .{dir});
92
+ const layout_mod: ?*std.Build.Module = blk: {
93
+ std.Io.Dir.cwd().access(b.graph.io, layout_path, .{}) catch break :blk null;
94
+ const m = b.createModule(.{ .root_source_file = b.path(layout_path) });
95
+ m.addImport("mer", mer_mod);
96
+ m.addImport("config", config_mod);
97
+ m.addImport("s3", s3_mod);
98
+ m.addImport("lib", lib_mod);
99
+ mod.addImport(b.fmt("{s}/layout", .{dir}), m);
100
+ break :blk m;
101
+ };
102
+ var d = std.Io.Dir.cwd().openDir(b.graph.io, dir, .{ .iterate = true }) catch return;
103
+ defer d.close(b.graph.io);
104
+ var walker = d.walk(b.allocator) catch return;
105
+ defer walker.deinit();
106
+ while (walker.next(b.graph.io) catch null) |entry| {
107
+ if (entry.kind != .file) continue;
108
+ if (!std.mem.endsWith(u8, entry.path, ".zig")) continue;
109
+ if (std.mem.eql(u8, entry.path, "layout.zig")) continue;
110
+ const file_path = b.fmt("{s}/{s}", .{ dir, entry.path });
111
+ const import_name = b.fmt("{s}/{s}", .{ dir, entry.path[0 .. entry.path.len - 4] });
112
+ const route_mod = b.createModule(.{ .root_source_file = b.path(file_path) });
113
+ route_mod.addImport("mer", mer_mod);
114
+ route_mod.addImport("config", config_mod);
115
+ route_mod.addImport("s3", s3_mod);
116
+ route_mod.addImport("lib", lib_mod);
117
+ if (layout_mod) |lm| route_mod.addImport(b.fmt("{s}/layout", .{dir}), lm);
118
+ mod.addImport(import_name, route_mod);
119
+ }
120
+ }
build.zig.zon ADDED
@@ -0,0 +1,26 @@
1
+ .{
2
+ .name = .website,
3
+ .version = "0.1.0",
4
+ .minimum_zig_version = "0.16.0",
5
+ .fingerprint = 0x476f5de705d5dc2b,
6
+ .dependencies = .{
7
+ .merjs = .{
8
+ .url = "git+https://github.com/justrach/merjs.git",
9
+ .hash = "merjs-0.2.5-qL9LkovAYAB6QqPjk8p53hM_A9bJA10slWVPpmu77Thc",
10
+ },
11
+ .zmd = .{
12
+ .url = "git+https://github.com/jetzig-framework/zmd.git",
13
+ .hash = "zmd-0.2.0-H8YV7VfEAACoEBk4Yy0feU3HmoIE-7oqjuiGadj3t1mv",
14
+ },
15
+ },
16
+ .paths = .{
17
+ "build.zig",
18
+ "build.zig.zon",
19
+ "src",
20
+ "app",
21
+ "api",
22
+ "public",
23
+ "content",
24
+ "assets",
25
+ },
26
+ }
src/config.zig ADDED
@@ -0,0 +1,67 @@
1
+ const std = @import("std");
2
+
3
+ pub const site_title = "pyrossh";
4
+ pub const site_description = "Welcome to my website!";
5
+ pub const site_url = "https://pyrossh.dev";
6
+
7
+ pub const Repo = struct {
8
+ title: []const u8,
9
+ description: []const u8,
10
+ tags: []const []const u8,
11
+ };
12
+
13
+ pub const Tool = struct {
14
+ name: []const u8,
15
+ link: []const u8,
16
+ image: ?[]const u8,
17
+ };
18
+
19
+ pub const NavItem = struct {
20
+ href: []const u8,
21
+ label: []const u8,
22
+ };
23
+
24
+ pub const repos = &[_]Repo{
25
+ .{ .title = "rust-embed", .description = "rust macro which loads files into the rust binary at compile time during release and loads the file from the fs during dev.", .tags = &[_][]const u8{ "rust", "proc-macro", "http" } },
26
+ .{ .title = "website", .description = "Personal website of pyrossh", .tags = &[_][]const u8{ "astro", "js", "html", "css" } },
27
+ .{ .title = "plum", .description = "A statically typed, imperative programming language inspired by rust, python", .tags = &[_][]const u8{ "treesitter", "compiler", "wasm" } },
28
+ .{ .title = "edge-city", .description = "edge-city is a next level meta-framework for react that runs only on edge runtimes", .tags = &[_][]const u8{ "react", "js", "ssr" } },
29
+ .{ .title = "gromer", .description = "gromer is a framework and cli to build multipage web apps in golang using htmx and alpinejs.", .tags = &[_][]const u8{ "golang", "htmx", "ssr" } },
30
+ .{ .title = "atoms-element", .description = "A simple web component library for defining your custom elements.", .tags = &[_][]const u8{ "js" } },
31
+ .{ .title = "atoms-state", .description = "Simple State management for react", .tags = &[_][]const u8{ "js", "react", "flux" } },
32
+ .{ .title = "remote-monitor", .description = "Remote Monitoring and Control using GSM-SMS", .tags = &[_][]const u8{ "c++", "teensy", "arduino" } },
33
+ .{ .title = "only-bible-app", .description = "The only bible app you will ever need. No ads. No in-app purchases. No distractions.", .tags = &[_][]const u8{ "kotlin", "android", "ios" } },
34
+ .{ .title = "gdx-studio", .description = "An IDE for creating Games using libgdx and Java", .tags = &[_][]const u8{ "libgdx", "java", "desktop" } },
35
+ .{ .title = "rp2350", .description = "code to drive rp2350", .tags = &[_][]const u8{ "zig", "raspberry-pi" } },
36
+ .{ .title = "config", .description = "Common configuration", .tags = &[_][]const u8{ "brew", "nushell" } },
37
+ .{ .title = "sabel-ide", .description = "sabel-ide", .tags = &[_][]const u8{ "python", "qt" } },
38
+ .{ .title = "tide-jsx", .description = "Tide + JSX", .tags = &[_][]const u8{ "rust", "proc-macro", "jsx" } },
39
+ };
40
+
41
+ pub const tools = &[_]Tool{
42
+ .{ .name = "Stats", .link = "https://github.com/exelban/stats", .image = "/assets/logos/stats.png" },
43
+ .{ .name = "Void", .link = "https://github.com/voideditor/void", .image = "/assets/logos/void.png" },
44
+ .{ .name = "Helix", .link = "https://github.com/helix-editor/helix", .image = "/assets/logos/helix.png" },
45
+ .{ .name = "Nushell", .link = "https://github.com/nushell/nushell", .image = "/assets/logos/nu.png" },
46
+ .{ .name = "Ghostty", .link = "https://github.com/ghostty-org/ghostty", .image = "/assets/logos/ghostty.png" },
47
+ .{ .name = "Zellij", .link = "https://zellij.dev/", .image = "/assets/logos/zellij.png" },
48
+ .{ .name = "Zen", .link = "https://github.com/zen-browser/desktop", .image = "/assets/logos/zen.svg" },
49
+ .{ .name = "Bruno", .link = "https://github.com/usebruno/bruno", .image = "/assets/logos/bruno.png" },
50
+ .{ .name = "Secretive", .link = "https://github.com/maxgoedjen/secretive", .image = null },
51
+ };
52
+
53
+ pub const nav_items = &[_]NavItem{
54
+ .{ .href = "/cv", .label = "cv" },
55
+ .{ .href = "/posts", .label = "posts" },
56
+ };
57
+
58
+ pub fn getRepo(id: []const u8) ?Repo {
59
+ for (repos) |repo| {
60
+ if (std.mem.eql(u8, repo.title, id)) return repo;
61
+ }
62
+ return null;
63
+ }
64
+
65
+ pub fn getRepos() []const Repo {
66
+ return repos;
67
+ }
src/lib.zig ADDED
@@ -0,0 +1,10 @@
1
+ pub const config = @import("config");
2
+ pub const s3 = @import("s3");
3
+ pub const content = @import("content.zig");
4
+ pub const frontmatter = @import("frontmatter.zig");
5
+ pub const markdown = @import("markdown.zig");
6
+ pub const files = @import("files.zig");
7
+ pub const gitBug = @import("gitBug.zig");
8
+ pub const gitReader = @import("gitReader.zig");
9
+ pub const diff = @import("diff.zig");
10
+ pub const repoContent = @import("repoContent.zig");
src/mercss_jit.zig ADDED
@@ -0,0 +1,30 @@
1
+ // Stub — provide real implementation when needed.
2
+ const std = @import("std");
3
+
4
+ pub const DesignSystem = struct {
5
+ allocator: std.mem.Allocator,
6
+
7
+ pub fn init(allocator: std.mem.Allocator) DesignSystem {
8
+ return .{ .allocator = allocator };
9
+ }
10
+
11
+ pub fn deinit(self: *DesignSystem) void {
12
+ _ = self;
13
+ }
14
+
15
+ pub fn loadDefaults(self: *DesignSystem) !void {
16
+ _ = self;
17
+ }
18
+ };
19
+
20
+ pub fn compile(allocator: std.mem.Allocator, ds: *DesignSystem, candidates: []const []const u8) ![]const u8 {
21
+ _ = ds;
22
+ _ = candidates;
23
+ return allocator.dupe(u8, "/* mercss stub */");
24
+ }
25
+
26
+ pub fn scan(content: []const u8, allocator: std.mem.Allocator, candidates: *std.ArrayList([]const u8)) !void {
27
+ _ = content;
28
+ _ = allocator;
29
+ _ = candidates;
30
+ }
src/s3.zig ADDED
@@ -0,0 +1,122 @@
1
+ const std = @import("std");
2
+ const mer = @import("mer");
3
+
4
+ const S3 = @This();
5
+
6
+ allocator: std.mem.Allocator,
7
+ endpoint: []const u8,
8
+ region: []const u8,
9
+ access_key: []const u8,
10
+ secret_key: []const u8,
11
+
12
+ pub fn init(allocator: std.mem.Allocator) S3 {
13
+ return .{
14
+ .allocator = allocator,
15
+ .endpoint = mer.env("S3_ENDPOINT") orelse "",
16
+ .region = mer.env("S3_REGION") orelse "auto",
17
+ .access_key = mer.env("S3_ACCESS_KEY_ID") orelse "",
18
+ .secret_key = mer.env("S3_SECRET_ACCESS_KEY") orelse "",
19
+ };
20
+ }
21
+
22
+ pub fn getObject(self: S3, key: []const u8) ?[]const u8 {
23
+ const url = std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ self.endpoint, key }) catch return null;
24
+ defer self.allocator.free(url);
25
+
26
+ const headers = self.signedHeaders("GET", key, "", 0);
27
+ defer self.allocator.free(headers);
28
+
29
+ var hdrs = [_]std.http.Header{.{
30
+ .name = "Authorization",
31
+ .value = headers,
32
+ }};
33
+
34
+ var resp = mer.fetch(self.allocator, .{
35
+ .url = url,
36
+ .method = .GET,
37
+ .headers = &hdrs,
38
+ }) catch return null;
39
+ defer resp.deinit(self.allocator);
40
+
41
+ if (resp.status != .ok and resp.status != .no_content) return null;
42
+ return resp.body;
43
+ }
44
+
45
+ pub fn getObjectArrayBuffer(self: S3, key: []const u8) ?[]u8 {
46
+ const url = std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ self.endpoint, key }) catch return null;
47
+ defer self.allocator.free(url);
48
+
49
+ const headers = self.signedHeaders("GET", key, "", 0);
50
+ defer self.allocator.free(headers);
51
+
52
+ var hdrs = [_]std.http.Header{.{
53
+ .name = "Authorization",
54
+ .value = headers,
55
+ }};
56
+
57
+ var resp = mer.fetch(self.allocator, .{
58
+ .url = url,
59
+ .method = .GET,
60
+ .headers = &hdrs,
61
+ }) catch return null;
62
+ defer resp.deinit(self.allocator);
63
+
64
+ if (resp.status != .ok and resp.status != .no_content) return null;
65
+ return self.allocator.dupe(u8, resp.body) catch null;
66
+ }
67
+
68
+ pub fn putObject(self: S3, key: []const u8, data: []const u8, content_type: []const u8) !void {
69
+ const url = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ self.endpoint, key });
70
+ defer self.allocator.free(url);
71
+
72
+ const auth = self.signedHeaders("PUT", key, content_type, data.len);
73
+ defer self.allocator.free(auth);
74
+
75
+ var hdrs = [_]std.http.Header{
76
+ .{ .name = "Authorization", .value = auth },
77
+ .{ .name = "Content-Type", .value = content_type },
78
+ };
79
+
80
+ var resp = try mer.fetch(self.allocator, .{
81
+ .url = url,
82
+ .method = .PUT,
83
+ .body = data,
84
+ .headers = &hdrs,
85
+ });
86
+ defer resp.deinit(self.allocator);
87
+ }
88
+
89
+ pub fn listObjects(self: S3, prefix: []const u8) ?[]const u8 {
90
+ const url = std.fmt.allocPrint(self.allocator, "{s}/?prefix={s}&list-type=2", .{ self.endpoint, prefix }) catch return null;
91
+ defer self.allocator.free(url);
92
+
93
+ const auth = self.signedHeaders("GET", "?prefix=" ++ prefix ++ "&list-type=2", "", 0);
94
+ defer self.allocator.free(auth);
95
+
96
+ var hdrs = [_]std.http.Header{.{
97
+ .name = "Authorization",
98
+ .value = auth,
99
+ }};
100
+
101
+ var resp = mer.fetch(self.allocator, .{
102
+ .url = url,
103
+ .method = .GET,
104
+ .headers = &hdrs,
105
+ }) catch return null;
106
+ defer resp.deinit(self.allocator);
107
+
108
+ if (resp.status != .ok) return null;
109
+ return self.allocator.dupe(u8, resp.body) catch null;
110
+ }
111
+
112
+ /// Simplified AWS SigV4 signing. For production, use a proper implementation.
113
+ fn signedHeaders(self: S3, method: []const u8, key: []const u8, content_type: []const u8, body_len: usize) []u8 {
114
+ _ = method;
115
+ _ = key;
116
+ _ = content_type;
117
+ _ = body_len;
118
+ // Simplified signing — for R2 with proper IAM, this may need full SigV4.
119
+ // R2 supports AWS SigV4-compatible signing.
120
+ // For now, returns a minimal auth header.
121
+ return std.fmt.allocPrint(self.allocator, "Bearer {s}", .{self.secret_key}) catch "";
122
+ }