import "std.zc" struct GrepConfig { query: string; path: string; ignore_case: bool; line_numbers: bool; recursive: bool; invert: bool; color: bool; } fn to_lower(c: char) -> char { if (c >= 'A' and c <= 'Z') return c + 32; return c; } fn str_find_case(haystack: string, needle: string, ignore_case: bool) -> Option { if (!ignore_case) { let res = (string)strstr(haystack, needle); if (res != NULL) return Option::Some(res); return Option::None(); } let h = haystack; let n = needle; let n_len = strlen(needle); while (*h != '\0') { let is_match: bool = true; for i in 0..n_len { let hc = to_lower(*(h + i)); let nc = to_lower(*(n + i)); if (hc != nc) { is_match = false; break; } } if (is_match) { return Option::Some(h); } h++; } return Option::None(); } fn print_highlight(line: string, match_ptr: string, match_len: usize, config: GrepConfig*) { if (!config.color) { "{line}"; return; } let current = line; while (current < match_ptr) { "{*current}"..; current++; } if (config.color) { "\x1b[31m"..; } for i in 0..match_len { "{*(match_ptr + i)}"..; } if (config.color) { "\x1b[0m"..; } current = match_ptr + match_len; "{current}"; } fn grep_file(path: string, config: GrepConfig*) -> Result { let content_str: String = File::read_all(path)?; defer content_str.destroy(); let content = content_str.c_str(); let line_num = 1; let ptr = content; let line_start = content; let q_len = strlen(config.query); while (*ptr != '\0') { if (*ptr == '\n') { *ptr = 0; let match_opt = str_find_case(line_start, config.query, config.ignore_case); let found_str = ""; if (match_opt.is_some()) found_str = match_opt.unwrap(); if ((match_opt.is_some() and !config.invert) || (!match_opt.is_some() and config.invert)) { if (config.path) { if (config.color) "\x1b[35m{path}\x1b[0m:"..; else "{path}:"..; } if (config.line_numbers) { if (config.color) "\x1b[32m{line_num}\x1b[0m:"..; else "{line_num}:"..; } if (match_opt.is_some()) { print_highlight(line_start, found_str, q_len, config); } else { "{line_start}"; } } *ptr = '\n'; line_start = ptr + 1; line_num = line_num + 1; } ptr = ptr + 1; } if (ptr > line_start) { let match_opt = str_find_case(line_start, config.query, config.ignore_case); let found_str = ""; if (match_opt.is_some()) found_str = match_opt.unwrap(); if ((match_opt.is_some() and !config.invert) || (!match_opt.is_some() and config.invert)) { if (config.path) "{path}:"..; if (config.line_numbers) "{line_num}:"..; if (match_opt.is_some()) { print_highlight(line_start, found_str, q_len, config); } else { "{line_start}"; } } } return Result::Ok(0); } fn visit_dir(path: string, config: GrepConfig*) { let entries_res = File::read_dir(path); guard entries_res.is_ok() else return; let entries = entries_res.unwrap(); let p_base = Path::new(path); for i in 0..entries.length() { let entry: DirEntry = entries.get(i); let full_path_obj = p_base.join(entry.name.c_str()); let full_path = full_path_obj.c_str(); if (entry.is_dir) { if (config.recursive) { visit_dir(full_path, config); } } else { grep_file(full_path, config); } } } fn print_usage() { "Usage: mini_grep [OPTIONS] "; "Options:"; " -i Ignore case"; " -n Show line numbers"; " -v Invert match"; " -r Recursive search"; " --color Enable color output"; } fn main(argc: int, argv: string*) { if (argc < 3) { print_usage(); return 0; } let config = GrepConfig { query: NULL, path: NULL, ignore_case: false, line_numbers: false, recursive: false, invert: false, color: false }; let arg_idx = 1; while (arg_idx < argc) { let arg = argv[arg_idx]; if (arg[0] == '-') { if (arg[1] == '-') { if (strcmp(arg, "--color") == 0) { config.color = true; } else { "Unknown option: {arg}"; print_usage(); return 0; } } else { let len = strlen(arg); for i in 1..len { let c = arg[i]; match c { 'i' => config.ignore_case = true, 'n' => config.line_numbers = true, 'v' => config.invert = true, 'r' => config.recursive = true, _ => { !"Unknown option flag: '{c}' in {arg}"; print_usage(); exit(1); } } } } } else { if (config.query == NULL) config.query = arg; else if (config.path == NULL) config.path = arg; } arg_idx = arg_idx + 1; } if (config.query == NULL or config.path == NULL) { print_usage(); return 0; } if (File::exists(config.path)) { let meta_res = File::metadata(config.path); if (meta_res.is_err()) { !"grep: {config.path}: Error reading metadata"; return 1; } let meta = meta_res.unwrap(); if (meta.is_dir) { if (config.recursive) { visit_dir(config.path, &config); } else { "grep: {config.path}: Is a directory"; } } else { let res = grep_file(config.path, &config); if (res.is_err()) { !"Error: {res.err}"; return 1; } } } else { "grep: {config.path}: No such file or directory"; } return 0; }