This repository has no description
3

Configure Feed

Select the types of activity you want to include in your feed.

wip: init

Co-authored-by: june <me@koi.rip>

robin (Jul 15, 2026, 3:45 PM +0200) 4a5e316d

+534
+1
.envrc
··· 1 + use flake
+7
.gitignore
··· 1 + # zig 2 + /.zig-cache/ 3 + /zig-out/ 4 + /zig-pkg/ 5 + 6 + # dev 7 + /.direnv/
+57
build.zig
··· 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 mod = b.addModule("cdl", .{ 8 + .root_source_file = b.path("src/root.zig"), 9 + .target = target, 10 + .optimize = optimize, 11 + }); 12 + 13 + const exe = b.addExecutable(.{ 14 + .name = "cdl", 15 + .root_module = b.createModule(.{ 16 + .root_source_file = b.path("src/main.zig"), 17 + .target = target, 18 + .optimize = optimize, 19 + }), 20 + }); 21 + 22 + exe.root_module.addImport("cdl", mod); 23 + 24 + b.installArtifact(exe); 25 + 26 + // deps =================================================================== 27 + 28 + const c_wchar = b.addTranslateC(.{ 29 + .root_source_file = b.path("src/wchar.h"), 30 + .target = target, 31 + .optimize = optimize, 32 + }); 33 + 34 + mod.addImport("wchar", c_wchar.createModule()); 35 + 36 + // run ==================================================================== 37 + 38 + const run_step = b.step("run", "Run"); 39 + 40 + const run_cmd = b.addRunArtifact(exe); 41 + run_step.dependOn(&run_cmd.step); 42 + 43 + run_cmd.step.dependOn(b.getInstallStep()); 44 + 45 + if (b.args) |args| { 46 + run_cmd.addArgs(args); 47 + } 48 + 49 + // test =================================================================== 50 + 51 + const test_step = b.step("test", "Test"); 52 + 53 + const test_step_run = b.addRunArtifact(b.addTest(.{ 54 + .root_module = mod, 55 + })); 56 + test_step.dependOn(&test_step_run.step); 57 + }
+8
build.zig.zon
··· 1 + .{ 2 + .name = .cdl, 3 + .version = "0.0.1", 4 + .dependencies = .{}, 5 + .minimum_zig_version = "0.16.0", 6 + .paths = .{""}, 7 + .fingerprint = 0xf0452fbb07a47cf5, 8 + }
+27
flake.lock
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1783776592, 6 + "narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=", 7 + "owner": "nixos", 8 + "repo": "nixpkgs", 9 + "rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3", 10 + "type": "github" 11 + }, 12 + "original": { 13 + "owner": "nixos", 14 + "ref": "nixos-unstable", 15 + "repo": "nixpkgs", 16 + "type": "github" 17 + } 18 + }, 19 + "root": { 20 + "inputs": { 21 + "nixpkgs": "nixpkgs" 22 + } 23 + } 24 + }, 25 + "root": "root", 26 + "version": 7 27 + }
+23
flake.nix
··· 1 + { 2 + description = "cdl"; 3 + 4 + inputs = { 5 + nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 + }; 7 + 8 + outputs = 9 + { self, nixpkgs }: 10 + let 11 + lib = nixpkgs.lib; 12 + forAllSystems = f: lib.genAttrs lib.systems.flakeExposed (s: f nixpkgs.legacyPackages.${s}); 13 + in 14 + { 15 + packages = forAllSystems (pkgs: { 16 + default = pkgs.callPackage ./nix/package.nix { }; 17 + }); 18 + devShells = forAllSystems (pkgs: { 19 + default = pkgs.callPackage ./nix/shell.nix { }; 20 + }); 21 + formatter = forAllSystems (pkgs: pkgs.callPackage ./nix/formatter.nix { }); 22 + }; 23 + }
+8
meow.sh
··· 1 + #!/usr/bin/env bash 2 + 3 + len=4 4 + 5 + for text in 'meow' 'rawr' 'nya' ':3'; do 6 + printf '%s\033[%bG' "$text" $((len + 2)) 7 + echo ':3' 8 + done
+4
nix/deps.nix
··· 1 + { 2 + linkFarm, 3 + }: 4 + linkFarm "zig-packages" [ ]
+34
nix/package.nix
··· 1 + { 2 + lib, 3 + stdenv, 4 + callPackage, 5 + 6 + zig, 7 + }: 8 + stdenv.mkDerivation { 9 + pname = "cdl"; 10 + version = "0.1.0"; 11 + src = ../.; 12 + 13 + nativeBuildInputs = [ zig ]; 14 + buildInputs = [ ]; 15 + 16 + doCheck = false; 17 + checkPhase = '' 18 + runHook preCheck 19 + zig build test 20 + runHook postCheck 21 + ''; 22 + 23 + zigBuildFlags = [ 24 + "--system" 25 + "${callPackage ./deps.nix { }}" 26 + ]; 27 + 28 + meta = { 29 + homepage = "https://codeberg.org/comfysage/cdl"; 30 + license = lib.licenses.asl20; 31 + platforms = [ "x86_64-linux" ]; 32 + mainProgram = "maivi"; 33 + }; 34 + }
+14
nix/shell.nix
··· 1 + { 2 + zig, 3 + just, 4 + watchexec, 5 + 6 + mkShellNoCC, 7 + }: 8 + mkShellNoCC { 9 + packages = [ 10 + zig 11 + just 12 + watchexec 13 + ]; 14 + }
+3
readme.md
··· 1 + cute fetch client written in zig :3 2 + 3 + pronounced like "cuddle"
+4
src/ascii.txt
··· 1 + へ ♡ ╱|、 2 + ૮ - ՛) (` - 7 3 + / ⁻ ៸| |、⁻〵 4 + 乀(ˍ, ل ل じしˍ,)ノ
+46
src/cmd.zig
··· 1 + const std = @import("std"); 2 + 3 + const cdl = @import("root.zig"); 4 + 5 + pub const Options = struct { 6 + trimmed: bool = false, 7 + }; 8 + 9 + pub fn spawn(argv: []const []const u8, opts: ?Options) ![]const u8 { 10 + if (!std.process.can_spawn) { 11 + return error.NoChildProcess; 12 + } 13 + 14 + var child = try std.process.spawn(cdl.io, .{ 15 + .argv = argv, 16 + .environ_map = cdl.environ_map, 17 + .stdin = .ignore, 18 + .stdout = .pipe, 19 + .stderr = .ignore, 20 + }); 21 + 22 + var stdout_reader = child.stdout.?.readerStreaming(cdl.io, &.{}); 23 + const stdout = stdout_reader.interface.allocRemaining(cdl.gpa, .limited(0x100)) catch return error.ReadFailure; 24 + errdefer cdl.gpa.free(stdout); 25 + 26 + const term = try child.wait(cdl.io); 27 + 28 + const raw = blk: switch (term) { 29 + .exited => |code| { 30 + if (code != 0) { 31 + return error.ExitCodeFailure; 32 + } 33 + break :blk stdout; 34 + }, 35 + .signal, .stopped => return error.ProcessTerminated, 36 + .unknown => return error.ProcessTerminated, 37 + }; 38 + if (opts == null) return raw; 39 + 40 + if (opts) |o| if (o.trimmed) { 41 + defer cdl.gpa.free(raw); 42 + return try cdl.gpa.dupe(u8, std.mem.trim(u8, raw, " \n\r")); 43 + }; 44 + 45 + return raw; 46 + }
+123
src/info.zig
··· 1 + const std = @import("std"); 2 + 3 + const cdl = @import("root.zig"); 4 + 5 + /// owned 6 + pub fn hostname() ![]const u8 { 7 + var buf: [std.posix.HOST_NAME_MAX]u8 = undefined; 8 + const hostname_buf = try std.posix.gethostname(&buf); 9 + return try cdl.gpa.dupe(u8, hostname_buf); 10 + } 11 + 12 + pub fn user() !?[]const u8 { 13 + const pwinfo = try PWInfo.get(); 14 + if (pwinfo) |pw| return pw.user; 15 + 16 + if (cdl.environ_map) |envmap| return envmap.get("USER"); 17 + if (cdl.environ_map) |envmap| return envmap.get("LOGNAME"); 18 + 19 + return null; 20 + } 21 + 22 + pub fn shell() !?[]const u8 { 23 + const shell_str = blk: { 24 + const pwinfo = try PWInfo.get(); 25 + if (pwinfo) |pw| break :blk pw.shell; 26 + 27 + break :blk if (cdl.environ_map) |envmap| 28 + envmap.get("SHELL") 29 + else 30 + null; 31 + } orelse return null; 32 + 33 + if (std.mem.cutScalarLast(u8, shell_str, '/')) |parts| 34 + return parts[1]; 35 + 36 + return shell_str; 37 + } 38 + 39 + pub fn osname() !?[]const u8 { 40 + const f = try std.Io.Dir.openFileAbsolute(cdl.io, "/etc/os-release", .{}); 41 + 42 + var buf: [0x1000]u8 = undefined; 43 + var file_reader = f.reader(cdl.io, &buf); 44 + var reader = &file_reader.interface; 45 + 46 + while (try reader.takeDelimiter('\n')) |line| 47 + if (std.mem.cutPrefix(u8, line, "NAME=")) |name| 48 + return try cdl.allocator.dupe(u8, trimquotes(name)); 49 + return null; 50 + } 51 + 52 + const PWInfo = struct { 53 + user: ?[]const u8, 54 + shell: ?[]const u8, 55 + dir: ?[]const u8, 56 + 57 + var cached: ?@This() = null; 58 + 59 + pub fn get() !?PWInfo { 60 + if (PWInfo.cached) |c| return c; 61 + 62 + const f = try std.Io.Dir.openFileAbsolute(cdl.io, "/etc/passwd", .{}); 63 + 64 + var buf: [0x1000]u8 = undefined; 65 + var file_reader = f.reader(cdl.io, &buf); 66 + var reader = &file_reader.interface; 67 + 68 + const uid = std.os.linux.getuid(); 69 + w: while (try reader.takeDelimiter('\n')) |line| { 70 + var parts = std.mem.splitScalar(u8, line, ':'); 71 + var pw_user: ?[]const u8 = null; 72 + var pw_shell: ?[]const u8 = null; 73 + var pw_dir: ?[]const u8 = null; 74 + 75 + var i: usize = 0; 76 + while (parts.next()) |part| : (i = i + 1) switch (i) { 77 + 0 => pw_user = part, 78 + 2 => if (try std.fmt.parseInt(u32, part, 10) != uid) continue :w, 79 + 5 => pw_dir = part, 80 + 6 => pw_shell = part, 81 + else => {}, 82 + }; 83 + 84 + return .{ 85 + .user = if (pw_user) |user_| try cdl.allocator.dupe(u8, user_) else null, 86 + .shell = if (pw_shell) |shell_| try cdl.allocator.dupe(u8, shell_) else null, 87 + .dir = if (pw_dir) |dir_| try cdl.allocator.dupe(u8, dir_) else null, 88 + }; 89 + } 90 + 91 + return null; 92 + } 93 + }; 94 + 95 + // helpers ==================================================================== 96 + 97 + fn trimquotes(s: []const u8) []const u8 { 98 + if (s.len < 2) return s; 99 + const open = switch (s[0]) { 100 + '"' => s[0], 101 + '\'' => s[0], 102 + else => return s, 103 + }; 104 + // assume malformed 105 + if (s[s.len - 1] != open) return s[1..]; 106 + return s[1 .. s.len - 1]; 107 + } 108 + 109 + test "trim quotes" { 110 + try std.testing.expectEqualStrings("meow", trimquotes("meow")); 111 + try std.testing.expectEqualStrings("meow", trimquotes( 112 + \\"meow" 113 + )); 114 + try std.testing.expectEqualStrings("meow", trimquotes( 115 + \\'meow' 116 + )); 117 + try std.testing.expectEqualStrings("meow", trimquotes( 118 + \\'meow 119 + )); 120 + try std.testing.expectEqualStrings("meow", trimquotes( 121 + \\"meow 122 + )); 123 + }
+108
src/main.zig
··· 1 + const std = @import("std"); 2 + 3 + const cdl = @import("cdl"); 4 + 5 + const ascii = @embedFile("ascii.txt"); 6 + const ascii_lines = blk: { 7 + var ar: []const []const u8 = &.{}; 8 + var it = std.mem.splitScalar(u8, ascii, '\n'); 9 + while (it.next()) |line| if (line.len != 0) { 10 + ar = ar ++ .{line}; 11 + }; 12 + break :blk ar; 13 + }; 14 + 15 + const base_padding = 1; 16 + 17 + // へ ♡ ╱|、 SYS 󰧟 bmo 18 + // ૮ - ՛) (` - 7 ├  󰧟 NixOS 26.11 (Zokor) x86_64 19 + // / ⁻ ៸| |、⁻〵 └  󰧟 zsh 20 + // 乀(ˍ, ل ل じしˍ,)ノ WM 󰧟 niri (Wayland) 21 + // ├  󰧟 ghostty 1.3.1 22 + // └  󰧟 nvim 23 + // THEME 󰧟 evergarden 24 + // └  󰧟 Fantasque Sans Mono (14pt) [GTK2/3/4] 25 + // ● ● ● ● ● ● ● ● 26 + 27 + pub fn main(init: std.process.Init) !void { 28 + cdl.init(init); 29 + defer cdl.deinit(); 30 + 31 + const stdout = std.Io.File.stdout(); 32 + var buf: [0x400]u8 = undefined; 33 + var file_writer = stdout.writer(cdl.io, &buf); 34 + try term.init(&file_writer.interface); 35 + 36 + // SYS ==================================================================== 37 + 38 + const hostname = try cdl.info.hostname(); 39 + defer cdl.gpa.free(hostname); 40 + 41 + const user = try cdl.info.user(); 42 + const shell = try cdl.info.shell(); 43 + 44 + const osname = try cdl.info.osname(); 45 + 46 + try term.writefmt( 47 + \\sys {s} 48 + , .{hostname}); 49 + if (user) |v| 50 + try term.writefmt( 51 + \\ ├ 󰋞 {s} 52 + , .{v}); 53 + if (osname) |v| 54 + try term.writefmt( 55 + \\ ├  {s} 56 + , .{v}); 57 + if (shell) |v| 58 + try term.writefmt( 59 + \\ └  {s} 60 + , .{v}); 61 + 62 + try term.writer.flush(); 63 + } 64 + 65 + const term = struct { 66 + pub var writer: *std.Io.Writer = undefined; 67 + 68 + var ascii_line_widths: [ascii_lines.len]usize = undefined; 69 + var longest: usize = undefined; 70 + 71 + pub fn init(w: *std.Io.Writer) !void { 72 + writer = w; 73 + 74 + _ = std.c.setlocale(.ALL, ""); 75 + 76 + for (ascii_lines, 0..) |line, c| { 77 + const len = try cdl.strwidth.width(line); 78 + ascii_line_widths[c] = len; 79 + } 80 + 81 + longest = blk: { 82 + var max: usize = 0; 83 + for (ascii_line_widths) |len| if (len > max) { 84 + max = len; 85 + }; 86 + break :blk max; 87 + }; 88 + } 89 + 90 + pub fn writefmt(comptime format: []const u8, args: anytype) !void { 91 + try writeascii(); 92 + 93 + const buf = try std.fmt.allocPrint(cdl.gpa, format, args); 94 + defer cdl.gpa.free(buf); 95 + 96 + try writer.writeAll(buf); 97 + try writer.writeByte('\n'); 98 + try writer.flush(); 99 + } 100 + 101 + var i: usize = 0; 102 + pub fn writeascii() !void { 103 + defer i += 1; 104 + try writer.writeAll(ascii_lines[i]); 105 + const len = ascii_line_widths[i]; 106 + for (0..longest - len + base_padding) |_| try writer.writeByte(' '); 107 + } 108 + };
+25
src/root.zig
··· 1 + const std = @import("std"); 2 + 3 + pub const cmd = @import("cmd.zig"); 4 + pub const info = @import("info.zig"); 5 + pub const strwidth = @import("strwidth.zig"); 6 + 7 + pub var io: std.Io = undefined; 8 + pub var gpa: std.mem.Allocator = undefined; 9 + pub var environ_map: ?*std.process.Environ.Map = null; 10 + 11 + var arena: std.heap.ArenaAllocator = undefined; 12 + pub var allocator: std.mem.Allocator = undefined; 13 + 14 + pub fn init(p_init: std.process.Init) void { 15 + io = p_init.io; 16 + gpa = p_init.gpa; 17 + environ_map = p_init.environ_map; 18 + 19 + arena = .init(gpa); 20 + allocator = arena.allocator(); 21 + } 22 + 23 + pub fn deinit() void { 24 + arena.deinit(); 25 + }
+40
src/strwidth.zig
··· 1 + // Copyright june 2026 2 + 3 + const std = @import("std"); 4 + const unicode = std.unicode; 5 + const mem = std.mem; 6 + const c = std.c; 7 + 8 + const wc = @import("wchar"); 9 + 10 + pub fn width(s: []const u8) !usize { 11 + var w: usize = 0; 12 + var i: usize = 0; 13 + 14 + while (i < s.len) { 15 + const len = try unicode.utf8ByteSequenceLength(s[i]); 16 + const dec = try unicode.utf8Decode(s[i .. i + len]); 17 + 18 + const cw = wc.wcwidth(@intCast(dec)); 19 + if (cw > 0) w += @intCast(cw); 20 + 21 + i += len; 22 + } 23 + 24 + return w; 25 + } 26 + 27 + test "strwidth" { 28 + try std.testing.expectEqual(16, width( 29 + \\ へ ♡ ╱|、 30 + )); 31 + try std.testing.expectEqual(17, width( 32 + \\ ૮ - ՛) (` - 7 33 + )); 34 + try std.testing.expectEqual(18, width( 35 + \\ / ⁻ ៸| |、⁻〵 36 + )); 37 + try std.testing.expectEqual(22, width( 38 + \\ 乀(ˍ, ل ل じしˍ,)ノ 39 + )); 40 + }
+2
src/wchar.h
··· 1 + #define _XOPEN_SOURCE 700 2 + #include <wchar.h> // mbrtowc, wcwidth