A => README.md +32 -0
@@ 1,32 @@
+# Zig-shell
+
+My best attempt at making a shell in zig.
+
+Only cd is implemented for now.
+
+Based on https://zig-by-example.github.io/shell.html
+
+# Installation and usage
+
+```
+git clone https://git.sr.ht/~tieong/zig-shell
+cd zig-shell
+zig build
+./zig-out/bin/zig-shell
+```
+
+# TODO
+
+- Autocompletion
+
+- Understand shortcuts such as ctrl+l to clear the screen, see the resources section.
+
+- Fix the stderr of externals commands such as rm, right now the errors are not displayed
+
+# Resources
+
+- https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
+
+- https://ziglang.org/documentation/master/std/#stdmem
+
+- https://github.com/Hejsil/zig-clap/tree/09d8261653cbdc61d926fbbdf8bd805abc3f5e5e
A => build.zig +33 -0
@@ 1,33 @@
+const std = @import("std");
+
+pub fn build(b: *std.build.Builder) void {
+ // Standard target options allows the person running `zig build` to choose
+ // what target to build for. Here we do not override the defaults, which
+ // means any target is allowed, and the default is native. Other options
+ // for restricting supported target set are available.
+ const target = b.standardTargetOptions(.{});
+
+ // Standard release options allow the person running `zig build` to select
+ // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
+ const mode = b.standardReleaseOptions();
+
+ const exe = b.addExecutable("zig-shell", "src/main.zig");
+ exe.setTarget(target);
+ exe.setBuildMode(mode);
+ exe.install();
+
+ const run_cmd = exe.run();
+ run_cmd.step.dependOn(b.getInstallStep());
+ if (b.args) |args| {
+ run_cmd.addArgs(args);
+ }
+
+ const run_step = b.step("run", "Run the app");
+ run_step.dependOn(&run_cmd.step);
+
+ const exe_tests = b.addTest("src/main.zig");
+ exe_tests.setBuildMode(mode);
+
+ const test_step = b.step("test", "Run unit tests");
+ test_step.dependOn(&exe_tests.step);
+}
A => src/main.zig +81 -0
@@ 1,81 @@
+const std = @import("std");
+const os = std.os;
+const mem = std.mem;
+
+const stdout = std.io.getStdOut().writer();
+const stdin = std.io.getStdIn().reader();
+
+const ArrayList = std.ArrayList;
+const test_allocator = std.testing.allocator;
+
+const shell_primitives = enum {
+ cd,
+};
+
+const Error = error{
+ CommandFailed,
+ UnknownCommand,
+ EmptyCommand,
+};
+
+fn changeDir(directory: []const u8) !void {
+ try std.os.chdir(directory);
+}
+
+fn autoComplete() !void {}
+
+fn getArgs(line: []const u8) ![]const []const u8 {
+ var args = ArrayList([]const u8).init(test_allocator);
+ defer args.deinit();
+
+ var tokens = mem.split(u8, line, " ");
+
+ while (tokens.next()) |string| {
+ try args.append(string);
+ }
+
+ return args.toOwnedSlice();
+}
+
+fn evalArgs(args: []const []const u8) !void {
+ if (args.len == 0) {
+ return;
+ } else if (mem.eql(u8, args[0], "clear")) {
+ try stdout.writeAll("\x1b[1;1H\x1b[2J");
+ } else if (mem.eql(u8, args[0], "cd") and args.len > 1) {
+ try changeDir(args[1]);
+ } else if (mem.eql(u8, args[0], "exit")) {
+ os.exit(0);
+ } else {
+ const res = std.ChildProcess.exec(.{ .allocator = std.heap.page_allocator, .argv = args }) catch |err| {
+ switch (err) {
+ error.FileNotFound => return Error.UnknownCommand,
+ else => {
+ try stdout.writeAll(@errorName(err));
+ return Error.CommandFailed;
+ },
+ }
+ };
+ try stdout.writeAll(res.stdout);
+ }
+}
+
+fn mainLoop() !void {
+ var buf: [512]u8 = undefined;
+ // TODO Implement raw mode to enable ^L to clear the terminal
+ while (true) {
+ try stdout.print("> ", .{});
+ var line = try stdin.readUntilDelimiter(&buf, '\n');
+ const actual_line = mem.trim(u8, line, "\r\n ");
+ const args = try getArgs(actual_line);
+ evalArgs(args) catch |err| {
+ if (err == Error.UnknownCommand) {
+ _ = try stdout.write("Command not found!\n");
+ }
+ };
+ }
+}
+
+pub fn main() anyerror!void {
+ try mainLoop();
+}