summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cmd.zig44
-rw-r--r--src/cmd/chdir.zig99
-rw-r--r--src/cmd/dir.zig157
-rw-r--r--src/cmd/mkdir.zig38
-rw-r--r--src/cmd/path.zig67
-rw-r--r--src/cmd/remove.zig50
-rw-r--r--src/cmd/rename.zig66
-rw-r--r--src/cmd/rmdir.zig39
-rw-r--r--src/cmd/type.zig109
-rw-r--r--src/eval.zig466
10 files changed, 656 insertions, 479 deletions
diff --git a/src/cmd.zig b/src/cmd.zig
index 665b2bf..c24cea5 100644
--- a/src/cmd.zig
+++ b/src/cmd.zig
@@ -7,6 +7,15 @@ const FileSpec = syntax.FileSpec;
const Redirect = syntax.Redirect;
const Copy = @import("cmd/copy.zig").Copy;
+const Chdir = @import("cmd/chdir.zig").Chdir;
+const Remove = @import("cmd/remove.zig").Remove;
+const Mkdir = @import("cmd/mkdir.zig").Mkdir;
+const Rmdir = @import("cmd/rmdir.zig").Rmdir;
+const Rename = @import("cmd/rename.zig").Rename;
+const PathGet = @import("cmd/path.zig").PathGet;
+const PathSet = @import("cmd/path.zig").PathSet;
+const Dir = @import("cmd/dir.zig").Dir;
+const Type = @import("cmd/type.zig").Type;
pub const BuiltinCommand = union(enum) {
// File-oriented
@@ -14,33 +23,20 @@ pub const BuiltinCommand = union(enum) {
Deltree: struct {
path: []const u8,
},
- Dir: struct {
- path: []const u8,
- },
+ Dir: Dir,
Fc,
Find,
- Mkdir: struct {
- path: []const u8,
- },
+ Mkdir: Mkdir,
Move,
- Remove: struct {
- path: []const u8,
- },
- Rename: struct {
- from: FileSpec,
- to: FileSpec,
- },
+ Remove: Remove,
+ Rename: Rename,
Replace,
- Rmdir: struct {
- path: []const u8,
- },
+ Rmdir: Rmdir,
Sort,
Tree: struct {
path: []const u8,
},
- Type: struct {
- file: FileSpec,
- },
+ Type: Type,
Xcopy: struct {
from: FileSpec,
to: FileSpec,
@@ -49,9 +45,7 @@ pub const BuiltinCommand = union(enum) {
// Shell-oriented
Append,
- Chdir: struct {
- path: []const u8,
- },
+ Chdir: Chdir,
EchoOff,
EchoOn,
EchoPlain,
@@ -59,10 +53,8 @@ pub const BuiltinCommand = union(enum) {
message: []const u8,
},
Exit,
- PathGet,
- PathSet: struct {
- value: []const u8,
- },
+ PathGet: PathGet,
+ PathSet: PathSet,
PromptGet,
PromptSet: struct {
message: []const u8,
diff --git a/src/cmd/chdir.zig b/src/cmd/chdir.zig
new file mode 100644
index 0000000..fa2da83
--- /dev/null
+++ b/src/cmd/chdir.zig
@@ -0,0 +1,99 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const print = std.debug.print;
+
+const paths = @import("../paths.zig");
+const formatDosPath = paths.formatDosPath;
+
+const types = @import("./types.zig");
+const CommandStatus = types.CommandStatus;
+const OutputCapture = types.OutputCapture;
+const InputSource = types.InputSource;
+
+pub const Chdir = struct {
+ path: []const u8,
+
+ pub fn eval(chdir: Chdir, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = input_source;
+
+ if (chdir.path.len == 0) {
+ // No arguments - display current directory
+ const cwd = std.fs.cwd().realpathAlloc(allocator, ".") catch {
+ const error_msg = "Unable to determine current directory\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ defer allocator.free(cwd);
+
+ const formatted_path = try formatDosPath(allocator, cwd);
+ defer allocator.free(formatted_path);
+
+ const output = try std.fmt.allocPrint(allocator, "{s}\n", .{formatted_path});
+ defer allocator.free(output);
+
+ if (output_capture) |capture| {
+ try capture.write(output);
+ } else {
+ print("{s}", .{output});
+ }
+ return CommandStatus{ .Code = 0 };
+ } else {
+ // Change directory
+ const target_path = chdir.path;
+
+ // Handle special cases
+ if (std.mem.eql(u8, target_path, "..")) {
+ // Go to parent directory
+ std.process.changeCurDir("..") catch {
+ const error_msg = "The system cannot find the path specified.\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ } else if (std.mem.eql(u8, target_path, "\\") or std.mem.eql(u8, target_path, "/")) {
+ // Go to root directory - simplified to just go to "/"
+ std.process.changeCurDir("/") catch {
+ const error_msg = "The system cannot find the path specified.\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ } else {
+ // Regular directory change
+ // Make sure the path doesn't contain null bytes
+ for (target_path) |ch| {
+ if (ch == 0) {
+ const error_msg = "Invalid path: contains null character\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ }
+ }
+
+ std.process.changeCurDir(target_path) catch {
+ const error_msg = "The system cannot find the path specified.\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ }
+ return CommandStatus{ .Code = 0 };
+ }
+ }
+};
diff --git a/src/cmd/dir.zig b/src/cmd/dir.zig
new file mode 100644
index 0000000..0c57e6f
--- /dev/null
+++ b/src/cmd/dir.zig
@@ -0,0 +1,157 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const ArrayList = std.ArrayList;
+const print = std.debug.print;
+
+const c = @cImport({
+ @cInclude("errno.h");
+ @cInclude("stdio.h");
+ if (@import("builtin").os.tag != .windows) {
+ @cInclude("sys/statvfs.h");
+ }
+});
+
+const paths = @import("../paths.zig");
+const formatDosPath = paths.formatDosPath;
+const convertTo83 = paths.convertTo83;
+
+const types = @import("./types.zig");
+const CommandStatus = types.CommandStatus;
+const OutputCapture = types.OutputCapture;
+const InputSource = types.InputSource;
+
+fn formatDosDateTime(allocator: Allocator, timestamp_secs: i64) ![]const u8 {
+ const epoch_seconds = @as(u64, @intCast(@max(timestamp_secs, 0)));
+ const epoch_day = @divFloor(epoch_seconds, std.time.s_per_day);
+ const day_seconds = epoch_seconds % std.time.s_per_day;
+
+ // Calculate date (simplified)
+ var year: u32 = 1970; // Start from Unix epoch year
+ var remaining_days = epoch_day;
+
+ // Simple year calculation
+ while (remaining_days >= 365) {
+ const days_in_year: u64 = if (isLeapYear(year)) 366 else 365;
+ if (remaining_days < days_in_year) break;
+ remaining_days -= days_in_year;
+ year += 1;
+ }
+
+ // Simple month/day calculation (approximate)
+ const month = @min(@divFloor(remaining_days, 30) + 1, 12);
+ const day = @min(remaining_days % 30 + 1, 31);
+
+ // Calculate time
+ const hours = day_seconds / std.time.s_per_hour;
+ const minutes = (day_seconds % std.time.s_per_hour) / std.time.s_per_min;
+
+ // Format as MM-DD-YY HH:MMa (DOS style)
+ const am_pm = if (hours < 12) "a" else "p";
+ const display_hour = if (hours == 0) 12 else if (hours > 12) hours - 12 else hours;
+
+ return try std.fmt.allocPrint(allocator, "{d:0>2}-{d:0>2}-{d:0>2} {d:>2}:{d:0>2}{s}", .{ @as(u32, @intCast(month)), @as(u32, @intCast(day)), @as(u32, @intCast(year % 100)), @as(u32, @intCast(display_hour)), @as(u32, @intCast(minutes)), am_pm });
+}
+
+fn isLeapYear(year: u32) bool {
+ return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0);
+}
+
+const GetFreeDiskSpaceError = error{ NotImplemented, AccessDenied };
+
+fn getFreeDiskSpace(path: []const u8) GetFreeDiskSpaceError!u64 {
+ if (@import("builtin").os.tag == .windows) {
+ return error.NotImplemented;
+ }
+
+ var stat: c.struct_statvfs = undefined;
+ if (c.statvfs(path.ptr, &stat) != 0) {
+ _ = c.perror("statvfs");
+ return error.AccessDenied;
+ }
+
+ return stat.f_bsize * stat.f_bfree;
+}
+
+pub const Dir = struct {
+ path: []const u8,
+
+ pub fn eval(dir: Dir, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = input_source;
+
+ var output_buffer = ArrayList(u8).init(allocator);
+ defer output_buffer.deinit();
+
+ // Format path in DOS style with backslashes and uppercase drive letter
+ const formatted_path = try formatDosPath(allocator, dir.path);
+ defer allocator.free(formatted_path);
+
+ // Get volume label (simplified - just show drive)
+ const drive_letter = if (formatted_path.len >= 2 and formatted_path[1] == ':')
+ formatted_path[0]
+ else
+ 'C';
+ try output_buffer.writer().print(" Volume in drive {c} has no label\n", .{drive_letter});
+ try output_buffer.writer().print(" Volume Serial Number is 1234-5678\n", .{});
+ try output_buffer.writer().print("\n Directory of {s}\n\n", .{formatted_path});
+
+ var dir_iterator = std.fs.cwd().openDir(dir.path, .{ .iterate = true }) catch {
+ const error_msg = "File not found\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ defer dir_iterator.close();
+
+ var iterator = dir_iterator.iterate();
+ var file_count: u32 = 0;
+ var dir_count: u32 = 0;
+ var total_file_bytes: u64 = 0;
+
+ while (try iterator.next()) |entry| {
+ const stat = dir_iterator.statFile(entry.name) catch continue;
+
+ // Convert timestamp to DOS date/time format
+ const mtime_secs = @divFloor(stat.mtime, std.time.ns_per_s);
+ const date_time = try formatDosDateTime(allocator, @intCast(mtime_secs));
+ defer allocator.free(date_time);
+
+ // Convert filename to 8.3 format
+ const short_name = try convertTo83(allocator, entry.name);
+ defer allocator.free(short_name);
+
+ switch (entry.kind) {
+ .directory => {
+ try output_buffer.writer().print("{s} <DIR> {s}\n", .{ date_time, short_name });
+ dir_count += 1;
+ },
+ .file => {
+ try output_buffer.writer().print("{s} {d:>14} {s}\n", .{ date_time, stat.size, short_name });
+ file_count += 1;
+ total_file_bytes += stat.size;
+ },
+ else => {},
+ }
+ }
+
+ // Get free disk space using statvfs
+ const path = try std.fs.cwd().realpathAlloc(allocator, dir.path);
+ defer allocator.free(path);
+ const bytes_free = getFreeDiskSpace(path) catch |err| switch (err) {
+ error.AccessDenied => 0,
+ error.NotImplemented => 0,
+ };
+
+ try output_buffer.writer().print(" {d} File(s) {d:>14} bytes\n", .{ file_count, total_file_bytes });
+ try output_buffer.writer().print(" {d} Dir(s) {d:>14} bytes free\n", .{ dir_count, bytes_free });
+
+ if (output_capture) |capture| {
+ try capture.write(output_buffer.items);
+ } else {
+ print("{s}", .{output_buffer.items});
+ }
+ return CommandStatus{ .Code = 0 };
+ }
+};
diff --git a/src/cmd/mkdir.zig b/src/cmd/mkdir.zig
new file mode 100644
index 0000000..55d2ab6
--- /dev/null
+++ b/src/cmd/mkdir.zig
@@ -0,0 +1,38 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const print = std.debug.print;
+
+const types = @import("./types.zig");
+const CommandStatus = types.CommandStatus;
+const OutputCapture = types.OutputCapture;
+const InputSource = types.InputSource;
+
+pub const Mkdir = struct {
+ path: []const u8,
+
+ pub fn eval(mkdir: Mkdir, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = allocator;
+ _ = input_source;
+
+ const dir_path = mkdir.path;
+
+ std.fs.cwd().makeDir(dir_path) catch |err| {
+ const error_msg = switch (err) {
+ error.PathAlreadyExists => "A subdirectory or file already exists\n",
+ error.AccessDenied => "Access denied\n",
+ error.FileNotFound => "The system cannot find the path specified\n",
+ error.NotDir => "The system cannot find the path specified\n",
+ else => "Unable to create directory\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+
+ // No output for successful creation (DOS style)
+ return CommandStatus{ .Code = 0 };
+ }
+};
diff --git a/src/cmd/path.zig b/src/cmd/path.zig
new file mode 100644
index 0000000..734350a
--- /dev/null
+++ b/src/cmd/path.zig
@@ -0,0 +1,67 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const print = std.debug.print;
+
+const types = @import("./types.zig");
+const CommandStatus = types.CommandStatus;
+const OutputCapture = types.OutputCapture;
+const InputSource = types.InputSource;
+
+pub const PathGet = struct {
+ pub fn eval(path_get: PathGet, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = path_get;
+ _ = input_source;
+
+ const current_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) {
+ error.EnvironmentVariableNotFound => {
+ // PATH not set, show empty
+ const output = "PATH=(not set)\n";
+ if (output_capture) |capture| {
+ try capture.write(output);
+ } else {
+ print("{s}", .{output});
+ }
+ return CommandStatus{ .Code = 0 };
+ },
+ else => {
+ const error_msg = "Cannot access PATH environment variable\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ },
+ };
+ defer allocator.free(current_path);
+
+ const output = try std.fmt.allocPrint(allocator, "PATH={s}\n", .{current_path});
+ defer allocator.free(output);
+ if (output_capture) |capture| {
+ try capture.write(output);
+ } else {
+ print("{s}", .{output});
+ }
+ return CommandStatus{ .Code = 0 };
+ }
+};
+
+pub const PathSet = struct {
+ value: []const u8,
+
+ pub fn eval(path_set: PathSet, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = input_source;
+
+ // Note: In a real DOS system, this would persist for the session
+ // Here we just show what would be set but don't actually set it
+ // since Zig's std.process doesn't provide a simple way to set env vars
+ const output = try std.fmt.allocPrint(allocator, "PATH would be set to: {s}\n(Note: Environment variable setting not implemented in this shell)\n", .{path_set.value});
+ defer allocator.free(output);
+ if (output_capture) |capture| {
+ try capture.write(output);
+ } else {
+ print("{s}", .{output});
+ }
+ return CommandStatus{ .Code = 0 };
+ }
+};
diff --git a/src/cmd/remove.zig b/src/cmd/remove.zig
new file mode 100644
index 0000000..91acf31
--- /dev/null
+++ b/src/cmd/remove.zig
@@ -0,0 +1,50 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const print = std.debug.print;
+
+const types = @import("./types.zig");
+const CommandStatus = types.CommandStatus;
+const OutputCapture = types.OutputCapture;
+const InputSource = types.InputSource;
+
+pub const Remove = struct {
+ path: []const u8,
+
+ pub fn eval(remove: Remove, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = allocator;
+ _ = input_source;
+
+ const file_path = remove.path;
+
+ // Check for wildcards (basic support)
+ if (std.mem.indexOf(u8, file_path, "*") != null or std.mem.indexOf(u8, file_path, "?") != null) {
+ // Simple wildcard deletion - just show error for now
+ const error_msg = "Wildcard deletion not yet implemented\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ }
+
+ // Delete single file
+ std.fs.cwd().deleteFile(file_path) catch |err| {
+ const error_msg = switch (err) {
+ error.FileNotFound => "File not found\n",
+ error.AccessDenied => "Access denied\n",
+ error.IsDir => "Access denied - cannot delete directory\n",
+ else => "Cannot delete file\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+
+ // No output for successful deletion (DOS style)
+ return CommandStatus{ .Code = 0 };
+ }
+};
diff --git a/src/cmd/rename.zig b/src/cmd/rename.zig
new file mode 100644
index 0000000..e3401d2
--- /dev/null
+++ b/src/cmd/rename.zig
@@ -0,0 +1,66 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const print = std.debug.print;
+
+const syntax = @import("../syntax.zig");
+const FileSpec = syntax.FileSpec;
+
+const types = @import("./types.zig");
+const CommandStatus = types.CommandStatus;
+const OutputCapture = types.OutputCapture;
+const InputSource = types.InputSource;
+
+pub const Rename = struct {
+ from: FileSpec,
+ to: FileSpec,
+
+ pub fn eval(rename: Rename, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = allocator;
+ _ = input_source;
+
+ const from_path = switch (rename.from) {
+ .Con, .Lpt1, .Lpt2, .Lpt3, .Prn => {
+ const error_msg = "Cannot rename device\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ },
+ .Path => |path| path,
+ };
+
+ const to_path = switch (rename.to) {
+ .Con, .Lpt1, .Lpt2, .Lpt3, .Prn => {
+ const error_msg = "Cannot rename to device\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ },
+ .Path => |path| path,
+ };
+
+ std.fs.cwd().rename(from_path, to_path) catch |err| {
+ const error_msg = switch (err) {
+ error.FileNotFound => "The system cannot find the file specified\n",
+ error.AccessDenied => "Access denied\n",
+ error.PathAlreadyExists => "A duplicate file name exists, or the file cannot be found\n",
+ error.RenameAcrossMountPoints => "Cannot rename across different drives\n",
+ else => "Cannot rename file\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+
+ // No output for successful rename (DOS style)
+ return CommandStatus{ .Code = 0 };
+ }
+};
diff --git a/src/cmd/rmdir.zig b/src/cmd/rmdir.zig
new file mode 100644
index 0000000..03be046
--- /dev/null
+++ b/src/cmd/rmdir.zig
@@ -0,0 +1,39 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const print = std.debug.print;
+
+const types = @import("./types.zig");
+const CommandStatus = types.CommandStatus;
+const OutputCapture = types.OutputCapture;
+const InputSource = types.InputSource;
+
+pub const Rmdir = struct {
+ path: []const u8,
+
+ pub fn eval(rmdir: Rmdir, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = allocator;
+ _ = input_source;
+
+ const dir_path = rmdir.path;
+
+ std.fs.cwd().deleteDir(dir_path) catch |err| {
+ const error_msg = switch (err) {
+ error.FileNotFound => "The system cannot find the path specified\n",
+ error.AccessDenied => "Access denied\n",
+ error.DirNotEmpty => "The directory is not empty\n",
+ error.FileBusy => "The directory is in use\n",
+ error.NotDir => "The system cannot find the path specified\n",
+ else => "Unable to remove directory\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+
+ // No output for successful removal (DOS style)
+ return CommandStatus{ .Code = 0 };
+ }
+};
diff --git a/src/cmd/type.zig b/src/cmd/type.zig
new file mode 100644
index 0000000..b908da9
--- /dev/null
+++ b/src/cmd/type.zig
@@ -0,0 +1,109 @@
+const std = @import("std");
+const Allocator = std.mem.Allocator;
+const ArrayList = std.ArrayList;
+const print = std.debug.print;
+
+const syntax = @import("../syntax.zig");
+const FileSpec = syntax.FileSpec;
+
+const types = @import("./types.zig");
+const CommandStatus = types.CommandStatus;
+const OutputCapture = types.OutputCapture;
+const InputSource = types.InputSource;
+
+pub const Type = struct {
+ file: FileSpec,
+
+ pub fn eval(type_cmd: Type, allocator: Allocator, output_capture: ?*OutputCapture, input_source: ?*InputSource) !CommandStatus {
+ _ = input_source;
+
+ const file_path = switch (type_cmd.file) {
+ .Con => {
+ const error_msg = "Cannot TYPE from CON\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ },
+ .Lpt1, .Lpt2, .Lpt3, .Prn => {
+ const error_msg = "Cannot TYPE from device\n";
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ },
+ .Path => |path| path,
+ };
+
+ const file = std.fs.cwd().openFile(file_path, .{}) catch |err| {
+ const error_msg = switch (err) {
+ error.FileNotFound => "The system cannot find the file specified.\n",
+ error.IsDir => "Access is denied.\n",
+ error.AccessDenied => "Access is denied.\n",
+ else => "Cannot access file.\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+ defer file.close();
+
+ // Read and display file contents
+ var buffer: [4096]u8 = undefined;
+ while (true) {
+ const bytes_read = file.readAll(&buffer) catch |err| {
+ const error_msg = switch (err) {
+ error.AccessDenied => "Access is denied.\n",
+ else => "Error reading file.\n",
+ };
+ if (output_capture) |capture| {
+ try capture.write(error_msg);
+ } else {
+ print("{s}", .{error_msg});
+ }
+ return CommandStatus{ .Code = 1 };
+ };
+
+ if (bytes_read == 0) break;
+
+ // Process buffer contents for output
+ var processed_output = ArrayList(u8).init(allocator);
+ defer processed_output.deinit();
+
+ for (buffer[0..bytes_read]) |byte| {
+ // Convert to printable characters, similar to DOS TYPE behavior
+ if (byte >= 32 and byte <= 126) {
+ try processed_output.append(byte);
+ } else if (byte == '\n') {
+ try processed_output.append('\n');
+ } else if (byte == '\r') {
+ // Skip carriage return in DOS-style line endings
+ continue;
+ } else if (byte == '\t') {
+ try processed_output.append('\t');
+ } else {
+ // Replace non-printable characters with '?'
+ try processed_output.append('?');
+ }
+ }
+
+ if (output_capture) |capture| {
+ try capture.write(processed_output.items);
+ } else {
+ print("{s}", .{processed_output.items});
+ }
+
+ // If we read less than the buffer size, we're done
+ if (bytes_read < buffer.len) break;
+ }
+
+ return CommandStatus{ .Code = 0 };
+ }
+};
diff --git a/src/eval.zig b/src/eval.zig
index dcf34f4..9b00462 100644
--- a/src/eval.zig
+++ b/src/eval.zig
@@ -34,36 +34,8 @@ const InputSource = cmdTypes.InputSource;
const STDOUT_BUFFER_SIZE: usize = 1024;
const STDERR_BUFFER_SIZE: usize = 1024;
-fn formatDosDateTime(allocator: Allocator, timestamp_secs: i64) ![]const u8 {
- const epoch_seconds = @as(u64, @intCast(@max(timestamp_secs, 0)));
- const epoch_day = @divFloor(epoch_seconds, std.time.s_per_day);
- const day_seconds = epoch_seconds % std.time.s_per_day;
-
- // Calculate date (simplified)
- var year: u32 = 1970; // Start from Unix epoch year
- var remaining_days = epoch_day;
-
- // Simple year calculation
- while (remaining_days >= 365) {
- const days_in_year: u64 = if (isLeapYear(year)) 366 else 365;
- if (remaining_days < days_in_year) break;
- remaining_days -= days_in_year;
- year += 1;
- }
-
- // Simple month/day calculation (approximate)
- const month = @min(@divFloor(remaining_days, 30) + 1, 12);
- const day = @min(remaining_days % 30 + 1, 31);
-
- // Calculate time
- const hours = day_seconds / std.time.s_per_hour;
- const minutes = (day_seconds % std.time.s_per_hour) / std.time.s_per_min;
-
- // Format as MM-DD-YY HH:MMa (DOS style)
- const am_pm = if (hours < 12) "a" else "p";
- const display_hour = if (hours == 0) 12 else if (hours > 12) hours - 12 else hours;
-
- return try std.fmt.allocPrint(allocator, "{d:0>2}-{d:0>2}-{d:0>2} {d:>2}:{d:0>2}{s}", .{ @as(u32, @intCast(month)), @as(u32, @intCast(day)), @as(u32, @intCast(year % 100)), @as(u32, @intCast(display_hour)), @as(u32, @intCast(minutes)), am_pm });
+fn isLeapYear(year: u32) bool {
+ return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0);
}
pub fn executeCommand(command: Command, allocator: Allocator) !CommandStatus {
@@ -194,171 +166,10 @@ fn executeCommandWithOutput(command: Command, allocator: Allocator, output_captu
return CommandStatus{ .Code = 0 };
},
.Dir => |dir| {
- var output_buffer = ArrayList(u8).init(allocator);
- defer output_buffer.deinit();
-
- // Format path in DOS style with backslashes and uppercase drive letter
- const formatted_path = try formatDosPath(allocator, dir.path);
- defer allocator.free(formatted_path);
-
- // Get volume label (simplified - just show drive)
- const drive_letter = if (formatted_path.len >= 2 and formatted_path[1] == ':')
- formatted_path[0]
- else
- 'C';
- try output_buffer.writer().print(" Volume in drive {c} has no label\n", .{drive_letter});
- try output_buffer.writer().print(" Volume Serial Number is 1234-5678\n", .{});
- try output_buffer.writer().print("\n Directory of {s}\n\n", .{formatted_path});
-
- var dir_iterator = std.fs.cwd().openDir(dir.path, .{ .iterate = true }) catch {
- const error_msg = "File not found\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
- defer dir_iterator.close();
-
- var iterator = dir_iterator.iterate();
- var file_count: u32 = 0;
- var dir_count: u32 = 0;
- var total_file_bytes: u64 = 0;
-
- while (try iterator.next()) |entry| {
- const stat = dir_iterator.statFile(entry.name) catch continue;
-
- // Convert timestamp to DOS date/time format
- const mtime_secs = @divFloor(stat.mtime, std.time.ns_per_s);
- const date_time = try formatDosDateTime(allocator, @intCast(mtime_secs));
- defer allocator.free(date_time);
-
- // Convert filename to 8.3 format
- const short_name = try convertTo83(allocator, entry.name);
- defer allocator.free(short_name);
-
- switch (entry.kind) {
- .directory => {
- try output_buffer.writer().print("{s} <DIR> {s}\n", .{ date_time, short_name });
- dir_count += 1;
- },
- .file => {
- try output_buffer.writer().print("{s} {d:>14} {s}\n", .{ date_time, stat.size, short_name });
- file_count += 1;
- total_file_bytes += stat.size;
- },
- else => {},
- }
- }
-
- // Get free disk space using statvfs
- const path = try std.fs.cwd().realpathAlloc(allocator, dir.path);
- defer allocator.free(path);
- const bytes_free = getFreeDiskSpace(path) catch |err| switch (err) {
- error.AccessDenied => 0,
- error.NotImplemented => 0,
- };
-
- try output_buffer.writer().print(" {d} File(s) {d:>14} bytes\n", .{ file_count, total_file_bytes });
- try output_buffer.writer().print(" {d} Dir(s) {d:>14} bytes free\n", .{ dir_count, bytes_free });
-
- if (output_capture) |capture| {
- try capture.write(output_buffer.items);
- } else {
- print("{s}", .{output_buffer.items});
- }
- return CommandStatus{ .Code = 0 };
+ return dir.eval(allocator, output_capture, input_source);
},
.Type => |type_cmd| {
- const file_path = switch (type_cmd.file) {
- .Con => {
- const error_msg = "Cannot TYPE from CON\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- .Lpt1, .Lpt2, .Lpt3, .Prn => {
- const error_msg = "Cannot TYPE from device\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- .Path => |path| path,
- };
-
- const file = std.fs.cwd().openFile(file_path, .{}) catch |err| {
- const error_msg = switch (err) {
- error.FileNotFound => "The system cannot find the file specified.\n",
- error.IsDir => "Access is denied.\n",
- error.AccessDenied => "Access is denied.\n",
- else => "Cannot access file.\n",
- };
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
- defer file.close();
-
- // Read and display file contents
- var buffer: [4096]u8 = undefined;
- while (true) {
- const bytes_read = file.readAll(&buffer) catch |err| {
- const error_msg = switch (err) {
- error.AccessDenied => "Access is denied.\n",
- else => "Error reading file.\n",
- };
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
-
- if (bytes_read == 0) break;
-
- // Process buffer contents for output
- var processed_output = ArrayList(u8).init(allocator);
- defer processed_output.deinit();
-
- for (buffer[0..bytes_read]) |byte| {
- // Convert to printable characters, similar to DOS TYPE behavior
- if (byte >= 32 and byte <= 126) {
- try processed_output.append(byte);
- } else if (byte == '\n') {
- try processed_output.append('\n');
- } else if (byte == '\r') {
- // Skip carriage return in DOS-style line endings
- continue;
- } else if (byte == '\t') {
- try processed_output.append('\t');
- } else {
- // Replace non-printable characters with '?'
- try processed_output.append('?');
- }
- }
-
- if (output_capture) |capture| {
- try capture.write(processed_output.items);
- } else {
- print("{s}", .{processed_output.items});
- }
-
- // If we read less than the buffer size, we're done
- if (bytes_read < buffer.len) break;
- }
-
- return CommandStatus{ .Code = 0 };
+ return type_cmd.eval(allocator, output_capture, input_source);
},
.Sort => {
var lines = ArrayList([]const u8).init(allocator);
@@ -410,213 +221,22 @@ fn executeCommandWithOutput(command: Command, allocator: Allocator, output_captu
return CommandStatus{ .Code = 0 };
},
.Chdir => |chdir| {
- if (chdir.path.len == 0) {
- // No arguments - display current directory
- const cwd = std.fs.cwd().realpathAlloc(allocator, ".") catch {
- const error_msg = "Unable to determine current directory\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
- defer allocator.free(cwd);
-
- const formatted_path = try formatDosPath(allocator, cwd);
- defer allocator.free(formatted_path);
-
- const output = try std.fmt.allocPrint(allocator, "{s}\n", .{formatted_path});
- defer allocator.free(output);
-
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
- } else {
- // Change directory
- const target_path = chdir.path;
-
- // Handle special cases
- if (std.mem.eql(u8, target_path, "..")) {
- // Go to parent directory
- std.process.changeCurDir("..") catch {
- const error_msg = "The system cannot find the path specified.\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
- } else if (std.mem.eql(u8, target_path, "\\") or std.mem.eql(u8, target_path, "/")) {
- // Go to root directory - simplified to just go to "/"
- std.process.changeCurDir("/") catch {
- const error_msg = "The system cannot find the path specified.\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
- } else {
- // Regular directory change
- // Make sure the path doesn't contain null bytes
- for (target_path) |ch| {
- if (ch == 0) {
- const error_msg = "Invalid path: contains null character\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- }
- }
-
- std.process.changeCurDir(target_path) catch {
- const error_msg = "The system cannot find the path specified.\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
- }
- return CommandStatus{ .Code = 0 };
- }
+ return chdir.eval(allocator, output_capture, input_source);
},
.Copy => |copy| {
return copy.eval(allocator, output_capture, input_source);
},
.Remove => |remove| {
- const file_path = remove.path;
-
- // Check for wildcards (basic support)
- if (std.mem.indexOf(u8, file_path, "*") != null or std.mem.indexOf(u8, file_path, "?") != null) {
- // Simple wildcard deletion - just show error for now
- const error_msg = "Wildcard deletion not yet implemented\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- }
-
- // Delete single file
- std.fs.cwd().deleteFile(file_path) catch |err| {
- const error_msg = switch (err) {
- error.FileNotFound => "File not found\n",
- error.AccessDenied => "Access denied\n",
- error.IsDir => "Access denied - cannot delete directory\n",
- else => "Cannot delete file\n",
- };
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
-
- // No output for successful deletion (DOS style)
- return CommandStatus{ .Code = 0 };
+ return remove.eval(allocator, output_capture, input_source);
},
.Mkdir => |mkdir| {
- const dir_path = mkdir.path;
-
- std.fs.cwd().makeDir(dir_path) catch |err| {
- const error_msg = switch (err) {
- error.PathAlreadyExists => "A subdirectory or file already exists\n",
- error.AccessDenied => "Access denied\n",
- error.FileNotFound => "The system cannot find the path specified\n",
- error.NotDir => "The system cannot find the path specified\n",
- else => "Unable to create directory\n",
- };
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
-
- // No output for successful creation (DOS style)
- return CommandStatus{ .Code = 0 };
+ return mkdir.eval(allocator, output_capture, input_source);
},
.Rmdir => |rmdir| {
- const dir_path = rmdir.path;
-
- std.fs.cwd().deleteDir(dir_path) catch |err| {
- const error_msg = switch (err) {
- error.FileNotFound => "The system cannot find the path specified\n",
- error.AccessDenied => "Access denied\n",
- error.DirNotEmpty => "The directory is not empty\n",
- error.FileBusy => "The directory is in use\n",
- error.NotDir => "The system cannot find the path specified\n",
- else => "Unable to remove directory\n",
- };
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
-
- // No output for successful removal (DOS style)
- return CommandStatus{ .Code = 0 };
+ return rmdir.eval(allocator, output_capture, input_source);
},
.Rename => |rename| {
- const from_path = switch (rename.from) {
- .Con, .Lpt1, .Lpt2, .Lpt3, .Prn => {
- const error_msg = "Cannot rename device\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- .Path => |path| path,
- };
-
- const to_path = switch (rename.to) {
- .Con, .Lpt1, .Lpt2, .Lpt3, .Prn => {
- const error_msg = "Cannot rename to device\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- .Path => |path| path,
- };
-
- std.fs.cwd().rename(from_path, to_path) catch |err| {
- const error_msg = switch (err) {
- error.FileNotFound => "The system cannot find the file specified\n",
- error.AccessDenied => "Access denied\n",
- error.PathAlreadyExists => "A duplicate file name exists, or the file cannot be found\n",
- error.RenameAcrossMountPoints => "Cannot rename across different drives\n",
- else => "Cannot rename file\n",
- };
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- };
-
- // No output for successful rename (DOS style)
- return CommandStatus{ .Code = 0 };
+ return rename.eval(allocator, output_capture, input_source);
},
.Move => {
const error_msg = "MOVE command not yet implemented\n";
@@ -627,51 +247,11 @@ fn executeCommandWithOutput(command: Command, allocator: Allocator, output_captu
}
return CommandStatus{ .Code = 1 };
},
- .PathGet => {
- const current_path = std.process.getEnvVarOwned(allocator, "PATH") catch |err| switch (err) {
- error.EnvironmentVariableNotFound => {
- // PATH not set, show empty
- const output = "PATH=(not set)\n";
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
- },
- else => {
- const error_msg = "Cannot access PATH environment variable\n";
- if (output_capture) |capture| {
- try capture.write(error_msg);
- } else {
- print("{s}", .{error_msg});
- }
- return CommandStatus{ .Code = 1 };
- },
- };
- defer allocator.free(current_path);
-
- const output = try std.fmt.allocPrint(allocator, "PATH={s}\n", .{current_path});
- defer allocator.free(output);
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
+ .PathGet => |path_get| {
+ return path_get.eval(allocator, output_capture, input_source);
},
- .PathSet => |pathset| {
- // Note: In a real DOS system, this would persist for the session
- // Here we just show what would be set but don't actually set it
- // since Zig's std.process doesn't provide a simple way to set env vars
- const output = try std.fmt.allocPrint(allocator, "PATH would be set to: {s}\n(Note: Environment variable setting not implemented in this shell)\n", .{pathset.value});
- defer allocator.free(output);
- if (output_capture) |capture| {
- try capture.write(output);
- } else {
- print("{s}", .{output});
- }
- return CommandStatus{ .Code = 0 };
+ .PathSet => |path_set| {
+ return path_set.eval(allocator, output_capture, input_source);
},
else => {
const error_msg = try std.fmt.allocPrint(allocator, "Command not implemented: {any}\n", .{builtin_cmd});
@@ -971,23 +551,3 @@ fn executeCommandWithOutput(command: Command, allocator: Allocator, output_captu
},
}
}
-
-fn isLeapYear(year: u32) bool {
- return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0);
-}
-
-const GetFreeDiskSpaceError = error{ NotImplemented, AccessDenied };
-
-fn getFreeDiskSpace(path: []const u8) GetFreeDiskSpaceError!u64 {
- if (@import("builtin").os.tag == .windows) {
- return error.NotImplemented;
- }
-
- var stat: c.struct_statvfs = undefined;
- if (c.statvfs(path.ptr, &stat) != 0) {
- _ = c.perror("statvfs");
- return error.AccessDenied;
- }
-
- return stat.f_bsize * stat.f_bfree;
-}