summaryrefslogtreecommitdiff
path: root/src/main.zig
blob: 10c5c1dab6bea962cd37011110fb5c80f7192dcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
const std = @import("std");

var stdin_buffer: [4096]u8 = undefined;
var stdin_reader = std.fs.File.stdin().readerStreaming(&stdin_buffer);
const stdin = &stdin_reader.interface;

var stdout_writer = std.fs.File.stdout().writerStreaming(&.{});
const stdout = &stdout_writer.interface;

const CommandResult = enum {
    continue_loop,
    exit_shell,
};

fn parseCommand(input: []const u8) struct { name: []const u8, args: ?[]const u8 } {
    const space_pos = std.mem.indexOfScalar(u8, input, ' ');
    if (space_pos) |pos| {
        return .{
            .name = input[0..pos],
            .args = input[pos + 1 ..],
        };
    }
    return .{
        .name = input,
        .args = null,
    };
}

fn isBuiltin(cmd_name: []const u8) bool {
    const builtins = [_][]const u8{ "exit", "echo", "type" };
    for (builtins) |builtin| {
        if (std.mem.eql(u8, cmd_name, builtin)) {
            return true;
        }
    }
    return false;
}

fn findInPath(allocator: std.mem.Allocator, cmd_name: []const u8) !?[]const u8 {
    const path_env = std.process.getEnvVarOwned(allocator, "PATH") catch return null;
    defer allocator.free(path_env);

    var it = std.mem.splitScalar(u8, path_env, ':');
    while (it.next()) |dir| {
        const full_path = std.fs.path.join(allocator, &[_][]const u8{ dir, cmd_name }) catch continue;
        defer allocator.free(full_path);

        const file = std.fs.openFileAbsolute(full_path, .{}) catch continue;
        const stat = file.stat() catch {
            file.close();
            continue;
        };
        file.close();

        const mode = stat.mode;
        const has_exec = (mode & 0o111) != 0;
        if (!has_exec) continue;

        return try allocator.dupe(u8, full_path);
    }

    return null;
}

fn executeCommand(allocator: std.mem.Allocator, cmd_name: []const u8, args: ?[]const u8) !CommandResult {
    if (std.mem.eql(u8, cmd_name, "exit")) {
        return .exit_shell;
    }

    if (std.mem.eql(u8, cmd_name, "echo")) {
        if (args) |a| {
            try stdout.print("{s}\n", .{a});
        } else {
            try stdout.print("\n", .{});
        }
        return .continue_loop;
    }

    if (std.mem.eql(u8, cmd_name, "type")) {
        if (args) |a| {
            if (isBuiltin(a)) {
                try stdout.print("{s} is a shell builtin\n", .{a});
            } else if (try findInPath(allocator, a)) |path| {
                defer allocator.free(path);
                try stdout.print("{s} is {s}\n", .{ a, path });
            } else {
                try stdout.print("{s}: not found\n", .{a});
            }
        }
        return .continue_loop;
    }

    try stdout.print("{s}: command not found\n", .{cmd_name});
    return .continue_loop;
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    while (true) {
        try stdout.print("$ ", .{});

        const command = try stdin.takeDelimiter('\n');
        if (command) |cmd| {
            const parsed = parseCommand(cmd);
            const result = try executeCommand(allocator, parsed.name, parsed.args);

            if (result == .exit_shell) {
                break;
            }
        } else {
            break;
        }
    }
}