diff options
author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2025-08-13 21:07:01 +0200 |
---|---|---|
committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2025-08-13 21:07:01 +0200 |
commit | 4a5f0120b68e752a0c61597eec6daf55f01c2582 (patch) | |
tree | d5e87e3d27aa199b3c57244d91c85e403cc5cd85 | |
parent | bf1dd870c839a447d6a1f7035fac2f347185925a (diff) |
Add CD, COPY, DEL, MD.
-rw-r--r-- | src/eval.zig | 247 | ||||
-rw-r--r-- | src/syntax.zig | 72 |
2 files changed, 313 insertions, 6 deletions
diff --git a/src/eval.zig b/src/eval.zig index ba7cff0..e320713 100644 --- a/src/eval.zig +++ b/src/eval.zig @@ -469,6 +469,253 @@ 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 }; + } + }, + .Copy => |copy| { + // Handle source file + const source_path = switch (copy.from) { + .Con => { + const error_msg = "Cannot copy 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 copy from device\n"; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } + return CommandStatus{ .Code = 1 }; + }, + .Path => |path| path, + }; + + // Handle destination + const dest_path = switch (copy.to) { + .Con => { + // Copy to console (display file contents) + const source_file = std.fs.cwd().openFile(source_path, .{}) catch |err| { + const error_msg = switch (err) { + error.FileNotFound => "File not found\n", + error.AccessDenied => "Access denied\n", + else => "Cannot access source file\n", + }; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } + return CommandStatus{ .Code = 1 }; + }; + defer source_file.close(); + + // Read and display file contents + var buffer: [4096]u8 = undefined; + while (true) { + const bytes_read = source_file.readAll(&buffer) catch { + const error_msg = "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; + + if (output_capture) |capture| { + try capture.write(buffer[0..bytes_read]); + } else { + print("{s}", .{buffer[0..bytes_read]}); + } + + if (bytes_read < buffer.len) break; + } + + const msg = " 1 File(s) copied\n"; + if (output_capture) |capture| { + try capture.write(msg); + } else { + print("{s}", .{msg}); + } + return CommandStatus{ .Code = 0 }; + }, + .Lpt1, .Lpt2, .Lpt3, .Prn => { + const error_msg = "Cannot copy to device\n"; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } + return CommandStatus{ .Code = 1 }; + }, + .Path => |path| path, + }; + + // Regular file-to-file copy + std.fs.cwd().copyFile(source_path, std.fs.cwd(), dest_path, .{}) catch |err| { + const error_msg = switch (err) { + error.FileNotFound => "File not found\n", + error.AccessDenied => "Access denied\n", + error.PathAlreadyExists => "File already exists\n", + else => "Cannot copy file\n", + }; + if (output_capture) |capture| { + try capture.write(error_msg); + } else { + print("{s}", .{error_msg}); + } + return CommandStatus{ .Code = 1 }; + }; + + const msg = " 1 File(s) copied\n"; + if (output_capture) |capture| { + try capture.write(msg); + } else { + print("{s}", .{msg}); + } + return CommandStatus{ .Code = 0 }; + }, + .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 }; + }, + .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 }; + }, else => { const error_msg = try std.fmt.allocPrint(allocator, "Command not implemented: {any}\n", .{builtin_cmd}); defer allocator.free(error_msg); diff --git a/src/syntax.zig b/src/syntax.zig index 6d5ae2e..dde6dba 100644 --- a/src/syntax.zig +++ b/src/syntax.zig @@ -190,11 +190,43 @@ pub const Command = union(enum) { .Redirect => |*redirect| { redirect.command.deinit(allocator); allocator.destroy(redirect.command); + for (redirect.redirects.items) |redir| { + switch (redir.target) { + .Path => |path| allocator.free(path), + else => {}, + } + } redirect.redirects.deinit(); }, .External => |*external| { external.args.deinit(); }, + .Builtin => |builtin| { + switch (builtin) { + .Dir => |dir| allocator.free(dir.path), + .Chdir => |chdir| allocator.free(chdir.path), + .EchoText => |echo| allocator.free(echo.message), + .Type => |type_cmd| { + switch (type_cmd.file) { + .Path => |path| allocator.free(path), + else => {}, + } + }, + .Copy => |copy| { + switch (copy.from) { + .Path => |path| allocator.free(path), + else => {}, + } + switch (copy.to) { + .Path => |path| allocator.free(path), + else => {}, + } + }, + .Remove => |remove| allocator.free(remove.path), + .Mkdir => |mkdir| allocator.free(mkdir.path), + else => {}, + } + }, else => {}, } } @@ -417,7 +449,7 @@ const Parser = struct { self.advance(); // consume redirect token const target_str = try self.expectWord(); - const target = parseFilespec(target_str); + const target = try parseFilespec(self.allocator, target_str); try redirects.append(Redirect{ .redirect_type = redirect_type, @@ -488,7 +520,10 @@ const Parser = struct { } else if (std.mem.eql(u8, cmd_upper, "VERIFY")) { return Command{ .Builtin = BuiltinCommand.Verify }; } else if (std.mem.eql(u8, cmd_upper, "DIR")) { - const path = if (args.items.len == 0) "." else args.items[0]; + const path = if (args.items.len == 0) + try self.allocator.dupe(u8, ".") + else + try self.allocator.dupe(u8, args.items[0]); return Command{ .Builtin = BuiltinCommand{ .Dir = .{ .path = path } } }; } else if (std.mem.eql(u8, cmd_upper, "VER")) { return Command{ .Builtin = BuiltinCommand.Ver }; @@ -500,10 +535,35 @@ const Parser = struct { if (args.items.len == 0) { return error.ExpectedWord; // Will be caught and show "Bad command or file name" } - const file_spec = parseFilespec(args.items[0]); + const file_spec = try parseFilespec(self.allocator, args.items[0]); return Command{ .Builtin = BuiltinCommand{ .Type = .{ .file = file_spec } } }; } else if (std.mem.eql(u8, cmd_upper, "SORT")) { return Command{ .Builtin = BuiltinCommand.Sort }; + } else if (std.mem.eql(u8, cmd_upper, "CD") or std.mem.eql(u8, cmd_upper, "CHDIR")) { + const path = if (args.items.len == 0) + try self.allocator.dupe(u8, "") + else + try self.allocator.dupe(u8, args.items[0]); + return Command{ .Builtin = BuiltinCommand{ .Chdir = .{ .path = path } } }; + } else if (std.mem.eql(u8, cmd_upper, "COPY")) { + if (args.items.len < 2) { + return error.ExpectedWord; // Will show "Bad command or file name" + } + const from_spec = try parseFilespec(self.allocator, args.items[0]); + const to_spec = try parseFilespec(self.allocator, args.items[1]); + return Command{ .Builtin = BuiltinCommand{ .Copy = .{ .from = from_spec, .to = to_spec } } }; + } else if (std.mem.eql(u8, cmd_upper, "DEL") or std.mem.eql(u8, cmd_upper, "ERASE")) { + if (args.items.len == 0) { + return error.ExpectedWord; // Will show "Bad command or file name" + } + const path = try self.allocator.dupe(u8, args.items[0]); + return Command{ .Builtin = BuiltinCommand{ .Remove = .{ .path = path } } }; + } else if (std.mem.eql(u8, cmd_upper, "MD") or std.mem.eql(u8, cmd_upper, "MKDIR")) { + if (args.items.len == 0) { + return error.ExpectedWord; // Will show "Bad command or file name" + } + const path = try self.allocator.dupe(u8, args.items[0]); + return Command{ .Builtin = BuiltinCommand{ .Mkdir = .{ .path = path } } }; } else { // External command return Command{ .External = .{ .program = command_name, .args = args } }; @@ -511,9 +571,9 @@ const Parser = struct { } }; -fn parseFilespec(path_str: []const u8) FileSpec { +fn parseFilespec(allocator: Allocator, path_str: []const u8) !FileSpec { var upper_buf: [256]u8 = undefined; - if (path_str.len >= upper_buf.len) return FileSpec{ .Path = path_str }; + if (path_str.len >= upper_buf.len) return FileSpec{ .Path = try allocator.dupe(u8, path_str) }; const upper_str = std.ascii.upperString(upper_buf[0..path_str.len], path_str); if (std.mem.eql(u8, upper_str, "CON")) return FileSpec.Con; @@ -521,7 +581,7 @@ fn parseFilespec(path_str: []const u8) FileSpec { if (std.mem.eql(u8, upper_str, "LPT2")) return FileSpec.Lpt2; if (std.mem.eql(u8, upper_str, "LPT3")) return FileSpec.Lpt3; if (std.mem.eql(u8, upper_str, "PRN")) return FileSpec.Prn; - return FileSpec{ .Path = path_str }; + return FileSpec{ .Path = try allocator.dupe(u8, path_str) }; } pub fn parse(input: []const u8, allocator: Allocator) !Command { |