summaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/dir.zig78
-rw-r--r--src/cmd/help.zig208
-rw-r--r--src/cmd/lib/flags.zig197
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,
+};