~tieong/zig-shell

6d2618382625fde75bb340bffa966655fa5b64d5 — Thomas Ieong 1 year, 7 months ago master
Init
3 files changed, 146 insertions(+), 0 deletions(-)

A README.md
A build.zig
A src/main.zig
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();
}