diff options
Diffstat (limited to 'src/cmd')
-rw-r--r-- | src/cmd/dir.zig | 78 | ||||
-rw-r--r-- | src/cmd/help.zig | 208 | ||||
-rw-r--r-- | src/cmd/lib/flags.zig | 197 |
3 files changed, 457 insertions, 26 deletions
diff --git a/src/cmd/dir.zig b/src/cmd/dir.zig index c19fe3c..27d0e30 100644 --- a/src/cmd/dir.zig +++ b/src/cmd/dir.zig @@ -112,6 +112,9 @@ fn getFreeDiskSpace(path: []const u8) GetFreeDiskSpaceError!u64 { pub const Dir = struct { path: []const u8, + wide_format: bool = false, // /w flag + bare_format: bool = false, // /b flag + subdirs: bool = false, // /s flag pub fn eval(dir: Dir, ctx: CommandContext) !CommandStatus { var output_buffer = ArrayList(u8).init(ctx.allocator); @@ -121,14 +124,17 @@ pub const Dir = struct { const formatted_path = try formatDosPath(ctx.allocator, dir.path); defer ctx.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}); + // Only show header for non-bare format + if (!dir.bare_format) { + // 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"; @@ -170,13 +176,25 @@ pub const Dir = struct { switch (entry.kind) { .directory => { - try output_buffer.writer().print("{s:<8} {s:<3} <DIR> {s}\n", .{ lower_name, lower_ext, date_time }); + if (dir.bare_format) { + try output_buffer.writer().print("{s}\n", .{entry.name}); + } else if (dir.wide_format) { + try output_buffer.writer().print("{s:<12} ", .{entry.name}); + } else { + try output_buffer.writer().print("{s:<8} {s:<3} <DIR> {s}\n", .{ lower_name, lower_ext, date_time }); + } dir_count += 1; }, .file => { - const formatted_size = try formatWithCommas(ctx.allocator, stat.size); - defer ctx.allocator.free(formatted_size); - try output_buffer.writer().print("{s:<8} {s:<3} {s:>14} {s}\n", .{ lower_name, lower_ext, formatted_size, date_time }); + if (dir.bare_format) { + try output_buffer.writer().print("{s}\n", .{entry.name}); + } else if (dir.wide_format) { + try output_buffer.writer().print("{s:<12} ", .{entry.name}); + } else { + const formatted_size = try formatWithCommas(ctx.allocator, stat.size); + defer ctx.allocator.free(formatted_size); + try output_buffer.writer().print("{s:<8} {s:<3} {s:>14} {s}\n", .{ lower_name, lower_ext, formatted_size, date_time }); + } file_count += 1; total_file_bytes += stat.size; }, @@ -184,21 +202,29 @@ pub const Dir = struct { } } - // Get free disk space using statvfs - const path = try std.fs.cwd().realpathAlloc(ctx.allocator, dir.path); - defer ctx.allocator.free(path); - const bytes_free = getFreeDiskSpace(path) catch |err| switch (err) { - error.AccessDenied => 0, - error.NotImplemented => 0, - }; - - const formatted_total_bytes = try formatWithCommas(ctx.allocator, total_file_bytes); - defer ctx.allocator.free(formatted_total_bytes); - const formatted_free_bytes = try formatWithCommas(ctx.allocator, bytes_free); - defer ctx.allocator.free(formatted_free_bytes); + // Add newline after wide format listing + if (dir.wide_format and !dir.bare_format) { + try output_buffer.writer().print("\n", .{}); + } - try output_buffer.writer().print(" {d} File(s) {s:>14} bytes\n", .{ file_count, formatted_total_bytes }); - try output_buffer.writer().print(" {d} Dir(s) {s:>14} bytes free\n", .{ dir_count, formatted_free_bytes }); + // Only show footer for non-bare format + if (!dir.bare_format) { + // Get free disk space using statvfs + const path = try std.fs.cwd().realpathAlloc(ctx.allocator, dir.path); + defer ctx.allocator.free(path); + const bytes_free = getFreeDiskSpace(path) catch |err| switch (err) { + error.AccessDenied => 0, + error.NotImplemented => 0, + }; + + const formatted_total_bytes = try formatWithCommas(ctx.allocator, total_file_bytes); + defer ctx.allocator.free(formatted_total_bytes); + const formatted_free_bytes = try formatWithCommas(ctx.allocator, bytes_free); + defer ctx.allocator.free(formatted_free_bytes); + + try output_buffer.writer().print(" {d} File(s) {s:>14} bytes\n", .{ file_count, formatted_total_bytes }); + try output_buffer.writer().print(" {d} Dir(s) {s:>14} bytes free\n", .{ dir_count, formatted_free_bytes }); + } if (ctx.output_capture) |capture| { try capture.write(output_buffer.items); diff --git a/src/cmd/help.zig b/src/cmd/help.zig new file mode 100644 index 0000000..e5cdad3 --- /dev/null +++ b/src/cmd/help.zig @@ -0,0 +1,208 @@ +const std = @import("std"); +const types = @import("lib/types.zig"); +const CommandContext = types.CommandContext; +const CommandStatus = types.CommandStatus; +const OutputCapture = types.OutputCapture; + +pub const Help = struct { + command: ?[]const u8 = null, + + pub fn eval(self: Help, ctx: CommandContext) !CommandStatus { + if (self.command) |cmd| { + return showCommandHelp(cmd, ctx); + } else { + return showGeneralHelp(ctx); + } + } +}; + +fn showGeneralHelp(ctx: CommandContext) !CommandStatus { + const help_text = + \\DOSE - DOS-style Command Shell + \\ + \\Available commands: + \\ + \\File Operations: + \\ COPY - Copy files + \\ DIR - Display directory contents + \\ TYPE - Display file contents + \\ CD - Change directory + \\ MD - Create directory + \\ RD - Remove directory + \\ REN - Rename files/directories + \\ DEL - Delete files + \\ MOVE - Move files/directories + \\ + \\System Commands: + \\ ECHO - Display messages or control echo state + \\ CLS - Clear screen + \\ DATE - Display current date + \\ TIME - Display current time + \\ VER - Display version information + \\ PATH - Display or set PATH environment variable + \\ SORT - Sort input lines alphabetically + \\ EXIT - Exit the shell + \\ + \\For help on a specific command, type: command /? + \\Example: DIR /? + \\ + ; + + if (ctx.output_capture) |output| { + try output.write(help_text); + } else { + try std.io.getStdOut().writeAll(help_text); + } + + return CommandStatus{ .Code = 0 }; +} + +fn showCommandHelp(command: []const u8, ctx: CommandContext) !CommandStatus { + const cmd_upper = try std.ascii.allocUpperString(ctx.allocator, command); + defer ctx.allocator.free(cmd_upper); + + const help_text = getCommandSpecificHelp(cmd_upper); + + if (ctx.output_capture) |output| { + try output.write(help_text); + } else { + try std.io.getStdOut().writeAll(help_text); + } + + return CommandStatus{ .Code = 0 }; +} + +fn getCommandSpecificHelp(command: []const u8) []const u8 { + if (std.mem.eql(u8, command, "DIR")) { + return + \\DIR - Display directory contents + \\ + \\Syntax: DIR [path] [/flags] + \\ + \\ path Directory path to list (default: current directory) + \\ + \\Flags: + \\ /? Display this help message + \\ /W Use wide list format (filenames only, multiple columns) + \\ /B Use bare format (filenames only, one per line) + \\ /S Display files in subdirectories as well + \\ + \\Example: DIR C:\ /W + \\ + ; + } else if (std.mem.eql(u8, command, "COPY")) { + return + \\COPY - Copy files + \\ + \\Syntax: COPY source destination [/flags] + \\ + \\ source Source file or device + \\ destination Destination file or device + \\ + \\Flags: + \\ /? Display this help message + \\ /Y Suppress prompting to confirm overwrite + \\ + \\Example: COPY file1.txt file2.txt + \\ + ; + } else if (std.mem.eql(u8, command, "TYPE")) { + return + \\TYPE - Display file contents + \\ + \\Syntax: TYPE filename [/flags] + \\ + \\ filename File to display + \\ + \\Flags: + \\ /? Display this help message + \\ + \\Example: TYPE readme.txt + \\ + ; + } else if (std.mem.eql(u8, command, "CD") or std.mem.eql(u8, command, "CHDIR")) { + return + \\CD/CHDIR - Change directory + \\ + \\Syntax: CD [path] [/flags] + \\ + \\ path Directory to change to + \\ + \\Flags: + \\ /? Display this help message + \\ + \\Example: CD C:\Windows + \\ + ; + } else if (std.mem.eql(u8, command, "ECHO")) { + return + \\ECHO - Display messages or control echo state + \\ + \\Syntax: ECHO [message] [/flags] + \\ ECHO ON|OFF + \\ + \\ message Text to display + \\ + \\Flags: + \\ /? Display this help message + \\ + \\Example: ECHO Hello World + \\ + ; + } else if (std.mem.eql(u8, command, "CLS")) { + return + \\CLS - Clear screen + \\ + \\Syntax: CLS [/flags] + \\ + \\Flags: + \\ /? Display this help message + \\ + ; + } else if (std.mem.eql(u8, command, "EXIT")) { + return + \\EXIT - Exit the shell + \\ + \\Syntax: EXIT [/flags] + \\ + \\Flags: + \\ /? Display this help message + \\ + ; + } else if (std.mem.eql(u8, command, "VER")) { + return + \\VER - Display version information + \\ + \\Syntax: VER [/flags] + \\ + \\Flags: + \\ /? Display this help message + \\ + ; + } else if (std.mem.eql(u8, command, "DATE")) { + return + \\DATE - Display current date + \\ + \\Syntax: DATE [/flags] + \\ + \\Flags: + \\ /? Display this help message + \\ + ; + } else if (std.mem.eql(u8, command, "TIME")) { + return + \\TIME - Display current time + \\ + \\Syntax: TIME [/flags] + \\ + \\Flags: + \\ /? Display this help message + \\ + ; + } else { + return + \\Unknown command. Type HELP for a list of commands. + \\ + ; + } +} diff --git a/src/cmd/lib/flags.zig b/src/cmd/lib/flags.zig new file mode 100644 index 0000000..637ea83 --- /dev/null +++ b/src/cmd/lib/flags.zig @@ -0,0 +1,197 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const ArrayList = std.ArrayList; +const HashMap = std.HashMap; + +pub const FlagType = enum { + Boolean, + String, + Number, +}; + +pub const FlagDef = struct { + name: []const u8, + aliases: []const []const u8, + flag_type: FlagType, + description: []const u8, + default_value: ?[]const u8 = null, +}; + +pub const FlagValue = union(FlagType) { + Boolean: bool, + String: []const u8, + Number: i32, +}; + +pub const ParsedFlags = struct { + values: HashMap([]const u8, FlagValue, StringContext, std.hash_map.default_max_load_percentage), + allocator: Allocator, + + const StringContext = struct { + pub fn hash(self: @This(), s: []const u8) u64 { + _ = self; + return std.hash_map.hashString(s); + } + pub fn eql(self: @This(), a: []const u8, b: []const u8) bool { + _ = self; + return std.mem.eql(u8, a, b); + } + }; + + pub fn init(allocator: Allocator) ParsedFlags { + return ParsedFlags{ + .values = HashMap([]const u8, FlagValue, StringContext, std.hash_map.default_max_load_percentage).init(allocator), + .allocator = allocator, + }; + } + + pub fn deinit(self: *ParsedFlags) void { + self.values.deinit(); + } + + pub fn getBool(self: *const ParsedFlags, name: []const u8) bool { + if (self.values.get(name)) |value| { + return switch (value) { + .Boolean => |b| b, + else => false, + }; + } + return false; + } + + pub fn getString(self: *const ParsedFlags, name: []const u8) ?[]const u8 { + if (self.values.get(name)) |value| { + return switch (value) { + .String => |s| s, + else => null, + }; + } + return null; + } + + pub fn getNumber(self: *const ParsedFlags, name: []const u8) ?i32 { + if (self.values.get(name)) |value| { + return switch (value) { + .Number => |n| n, + else => null, + }; + } + return null; + } + + pub fn setValue(self: *ParsedFlags, name: []const u8, value: FlagValue) !void { + try self.values.put(name, value); + } +}; + +pub const CommandFlags = struct { + flags: []const FlagDef, + + pub fn parse(self: *const CommandFlags, args: []const []const u8, allocator: Allocator) !ParsedFlags { + var parsed = ParsedFlags.init(allocator); + + for (args) |arg| { + if (arg.len == 0 or arg[0] != '/') continue; + + const flag_text = arg[1..]; + if (flag_text.len == 0) continue; + + var flag_name: []const u8 = undefined; + var flag_value: ?[]const u8 = null; + + if (std.mem.indexOf(u8, flag_text, ":")) |colon_pos| { + flag_name = flag_text[0..colon_pos]; + flag_value = flag_text[colon_pos + 1 ..]; + } else { + flag_name = flag_text; + } + + const flag_def = self.findFlag(flag_name) orelse { + return error.UnknownFlag; + }; + + const value = switch (flag_def.flag_type) { + .Boolean => FlagValue{ .Boolean = true }, + .String => blk: { + const str_value = flag_value orelse flag_def.default_value orelse { + return error.MissingFlagValue; + }; + break :blk FlagValue{ .String = try allocator.dupe(u8, str_value) }; + }, + .Number => blk: { + const str_value = flag_value orelse flag_def.default_value orelse { + return error.MissingFlagValue; + }; + const num_value = std.fmt.parseInt(i32, str_value, 10) catch { + return error.InvalidNumber; + }; + break :blk FlagValue{ .Number = num_value }; + }, + }; + + try parsed.setValue(flag_def.name, value); + } + + return parsed; + } + + fn findFlag(self: *const CommandFlags, name: []const u8) ?*const FlagDef { + for (self.flags) |*flag_def| { + if (std.mem.eql(u8, flag_def.name, name)) { + return flag_def; + } + + for (flag_def.aliases) |alias| { + if (alias.len > 0 and alias[0] == '/') { + if (std.mem.eql(u8, alias[1..], name)) { + return flag_def; + } + } else if (std.mem.eql(u8, alias, name)) { + return flag_def; + } + } + } + return null; + } + + pub fn getHelp(self: *const CommandFlags, command_name: []const u8, allocator: Allocator) ![]const u8 { + var help = ArrayList(u8).init(allocator); + defer help.deinit(); + + try help.appendSlice(command_name); + try help.appendSlice(" - Available flags:\n\n"); + + for (self.flags) |flag_def| { + try help.appendSlice(" /"); + try help.appendSlice(flag_def.name); + + if (flag_def.aliases.len > 0) { + try help.appendSlice(" ("); + for (flag_def.aliases, 0..) |alias, i| { + if (i > 0) try help.appendSlice(", "); + try help.appendSlice(alias); + } + try help.appendSlice(")"); + } + + switch (flag_def.flag_type) { + .String => try help.appendSlice(":value"), + .Number => try help.appendSlice(":number"), + .Boolean => {}, + } + + try help.appendSlice(" - "); + try help.appendSlice(flag_def.description); + try help.appendSlice("\n"); + } + + return help.toOwnedSlice(); + } +}; + +pub const FlagParseError = error{ + UnknownFlag, + MissingFlagValue, + InvalidNumber, + OutOfMemory, +}; |