const std = @import("std"); const types = @import("types.zig"); const print = std.debug.print; pub const HELP_TEXT = \\Usage: manwhere [OPTIONS] \\Find man pages containing a keyword. \\ \\OPTIONS: \\ -h, --help Show this help message \\ -v, --verbose Show timing and extra details \\ --paths Show file paths (slower) \\ -s, --section NUM Only show results from section NUM (1-9) \\ Can be specified multiple times \\ \\EXAMPLES: \\ manwhere sleep # find all man pages mentioning "sleep" \\ manwhere -s 1 sleep # find only commands (section 1) mentioning "sleep" \\ manwhere -s 1 -s 3 sleep # find in sections 1 and 3 \\ manwhere -v --paths ssl # detailed search with paths and timing \\ manwhere --section 3 printf # find only library functions (section 3) \\ ; pub fn parseArgs(allocator: std.mem.Allocator, args: [][:0]u8) !types.SearchConfig { if (args.len < 2) { print("{s}", .{HELP_TEXT}); return error.NoKeyword; } var keyword: []const u8 = ""; var sections_list = std.ArrayList([]const u8){}; defer sections_list.deinit(allocator); var show_paths = false; var verbose = false; var i: usize = 1; while (i < args.len) : (i += 1) { const arg = args[i]; if (std.mem.eql(u8, arg, "-h") or std.mem.eql(u8, arg, "--help")) { print("{s}", .{HELP_TEXT}); return error.HelpRequested; } else if (std.mem.eql(u8, arg, "--paths")) { show_paths = true; } else if (std.mem.eql(u8, arg, "-v") or std.mem.eql(u8, arg, "--verbose")) { verbose = true; } else if (std.mem.eql(u8, arg, "-s") or std.mem.eql(u8, arg, "--section")) { // Next argument should be the section number if (i + 1 >= args.len) { print("[X] Option {s} requires a section number (1-9)\n", .{arg}); return error.InvalidOption; } i += 1; const section_arg = args[i]; // Validate section number if (section_arg.len == 0 or section_arg.len > 4) { print("[X] Invalid section '{s}'. Use 1-9 or subsections like 3ssl\n", .{section_arg}); return error.InvalidSection; } // Basic validation - should start with a digit 1-9 if (section_arg[0] < '1' or section_arg[0] > '9') { print("[X] Invalid section '{s}'. Section must be 1-9\n", .{section_arg}); return error.InvalidSection; } // Add section to list try sections_list.append(allocator, section_arg); } else if (std.mem.startsWith(u8, arg, "-")) { print("[X] Unknown option: {s}\nUse -h or --help for usage information\n", .{arg}); return error.InvalidOption; } else if (keyword.len == 0) { keyword = arg; } else { print("[X] Multiple keywords not supported\n", .{}); return error.MultipleKeywords; } } if (keyword.len == 0) { print("[X] No keyword provided\n{s}", .{HELP_TEXT}); return error.NoKeyword; } // Convert ArrayList to owned slice, or null if empty const target_sections = if (sections_list.items.len > 0) try sections_list.toOwnedSlice(allocator) else null; return types.SearchConfig{ .keyword = keyword, .target_sections = target_sections, .show_paths = show_paths, .verbose = verbose, }; } // ============================================================================ // TESTS // ============================================================================ test "parseArgs - basic keyword" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = &[_][:0]u8{ "manwhere", "sleep" }; const config = try parseArgs(allocator, args); try std.testing.expectEqualStrings("sleep", config.keyword); try std.testing.expect(config.target_sections == null); try std.testing.expect(!config.show_paths); try std.testing.expect(!config.verbose); } test "parseArgs - with verbose flag" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = &[_][:0]u8{ "manwhere", "-v", "ls" }; const config = try parseArgs(allocator, args); try std.testing.expectEqualStrings("ls", config.keyword); try std.testing.expect(config.verbose); } test "parseArgs - with paths flag" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = &[_][:0]u8{ "manwhere", "--paths", "printf" }; const config = try parseArgs(allocator, args); try std.testing.expectEqualStrings("printf", config.keyword); try std.testing.expect(config.show_paths); } test "parseArgs - with section flag" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = &[_][:0]u8{ "manwhere", "-s", "1", "ls" }; const config = try parseArgs(allocator, args); try std.testing.expectEqualStrings("ls", config.keyword); try std.testing.expect(config.target_sections != null); try std.testing.expectEqual(@as(usize, 1), config.target_sections.?.len); try std.testing.expectEqualStrings("1", config.target_sections.?[0]); } test "parseArgs - with multiple sections" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = &[_][:0]u8{ "manwhere", "-s", "1", "-s", "3", "test" }; const config = try parseArgs(allocator, args); try std.testing.expectEqualStrings("test", config.keyword); try std.testing.expect(config.target_sections != null); try std.testing.expectEqual(@as(usize, 2), config.target_sections.?.len); try std.testing.expectEqualStrings("1", config.target_sections.?[0]); try std.testing.expectEqualStrings("3", config.target_sections.?[1]); } test "parseArgs - error no keyword" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = &[_][:0]u8{"manwhere"}; const result = parseArgs(allocator, args); try std.testing.expectError(error.NoKeyword, result); } test "parseArgs - error invalid option" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = &[_][:0]u8{ "manwhere", "--invalid", "ls" }; const result = parseArgs(allocator, args); try std.testing.expectError(error.InvalidOption, result); } test "parseArgs - error multiple keywords" { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); const args = &[_][:0]u8{ "manwhere", "ls", "cat" }; const result = parseArgs(allocator, args); try std.testing.expectError(error.MultipleKeywords, result); }