const std = @import("std"); // this is the whole personality budget. spend it here, not in the logic. const BANNER = \\oh you bubbly fuck \\ ; pub fn main() !void { // an allocator: memory, but you have to ask nicely and give it back. var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); // deinit yells at you if you forgot to give something back const allocator = gpa.allocator(); // a shell is just two file handles and a loop. everything else is decoration. const stdout = std.io.getStdOut().writer(); const stdin = std.io.getStdIn().reader(); try stdout.print("{s}", .{BANNER}); // one buffer, reused every line. no line, no command, gets longer than this. var line_buf: [1024]u8 = undefined; while (true) { try stdout.print("nish> ", .{}); // block here until a human (or a pipe) gives us something to chew on. const maybe_line = stdin.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| { try stdout.print("oh you bubbly fuck!: {s}\n", .{@errorName(err)}); continue; }; const raw_line = maybe_line orelse break; // null means EOF, i.e. Ctrl-D. politely leave. // trim the newline and any stray whitespace off both ends. const line = std.mem.trim(u8, raw_line, " \t\r\n"); if (line.len == 0) continue; // empty enter is not a crime, just say nothing. // chop the line on spaces/tabs. no quotes, no escapes — say what you mean. var args = std.ArrayList([]const u8).init(allocator); defer args.deinit(); var it = std.mem.tokenizeAny(u8, line, " \t"); while (it.next()) |tok| try args.append(tok); if (args.items.len == 0) continue; // --- builtins: these HAVE to live in the parent process. // a "cd" run as a subprocess changes that subprocess's directory // and then immediately vanishes, taking the change with it. if (std.mem.eql(u8, args.items[0], "exit")) break; // go on then. bubble off. if (std.mem.eql(u8, args.items[0], "cd")) { const target = if (args.items.len > 1) args.items[1] else "/"; std.posix.chdir(target) catch |err| { try stdout.print("nish: cd: {s}: {s}\n", .{ target, @errorName(err) }); }; continue; // cd never launches anything, so skip straight back to the prompt } // --- everything else: launch it and get out of the way. var child = std.process.Child.init(args.items, allocator); // Inherit = the child gets our real terminal, not a pipe. it can see, it can be seen. child.stdin_behavior = .Inherit; child.stdout_behavior = .Inherit; child.stderr_behavior = .Inherit; child.spawn() catch |err| { // fork/exec failed — almost always means "no such command" try stdout.print("nish: {s}: {s}\n", .{ args.items[0], @errorName(err) }); continue; }; // wait for it to finish before showing another prompt. no backgrounding, no &. _ = child.wait() catch |err| { try stdout.print("nish: racist ball: {s}\n", .{@errorName(err)}); }; } try stdout.print("\n", .{}); // one last newline so your shell prompt doesn't get glued on }