diff options
| author | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-11 22:51:29 +0000 |
|---|---|---|
| committer | Zuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian> | 2026-01-11 22:51:29 +0000 |
| commit | 45feb20bf55c16ee415ef31a356ada67e1df18d6 (patch) | |
| tree | adfd5b4c5641dc5bdfac96f330553140ee90319b /examples/tools | |
| parent | 36938b584ea2d096d97a124b70da51f685850ff7 (diff) | |
Some more, and some fixes
Diffstat (limited to 'examples/tools')
| -rw-r--r-- | examples/tools/mini_grep.zc | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/examples/tools/mini_grep.zc b/examples/tools/mini_grep.zc new file mode 100644 index 0000000..827b73a --- /dev/null +++ b/examples/tools/mini_grep.zc @@ -0,0 +1,311 @@ + +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<string> +{ + if (!ignore_case) + { + var res = (string)strstr(haystack, needle); + if (res != NULL) return Option<string>::Some(res); + return Option<string>::None(); + } + + var h = haystack; + var n = needle; + var n_len = strlen(needle); + + while (*h != 0) + { + var is_match: bool = true; + for i in 0..n_len + { + var hc = to_lower(*(h + i)); + var nc = to_lower(*(n + i)); + if (hc != nc) + { + is_match = false; + break; + } + } + if (is_match) + { + return Option<string>::Some(h); + } + h++; + } + return Option<string>::None(); +} + +fn print_highlight(line: string, match_ptr: string, match_len: usize, config: GrepConfig) { + if (!config.color) + { + "{line}"; + return; + } + + var 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<int> { + var content_str: String = File::read_all(path)?; + defer content_str.destroy(); + var content = content_str.c_str(); + + var line_num = 1; + var ptr = content; + var line_start = content; + + var q_len = strlen(config.query); + + while (*ptr != 0) + { + if (*ptr == '\n') + { + *ptr = 0; + + var match_opt = str_find_case(line_start, config.query, config.ignore_case); + var 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) + { + var match_opt = str_find_case(line_start, config.query, config.ignore_case); + var 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<int>::Ok(0); +} + +fn visit_dir(path: string, config: GrepConfig) { + var entries_res = File::read_dir(path); + guard entries_res.is_ok() else return; + + var entries = entries_res.unwrap(); + var p_base = Path::new(path); + + for i in 0..entries.length() + { + var entry: DirEntry = entries.get(i); + var full_path_obj = p_base.join(entry.name.c_str()); + var 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] <QUERY> <PATH>"; + "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; + } + + var config = GrepConfig { + query: NULL, + path: NULL, + ignore_case: false, + line_numbers: false, + recursive: false, + invert: false, + color: false + }; + + var arg_idx = 1; + while (arg_idx < argc) + { + var 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 + { + var len = strlen(arg); + for i in 1..len + { + var 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)) + { + var meta_res = File::metadata(config.path); + if (meta_res.is_err()) + { + !"grep: {config.path}: Error reading metadata"; + return 1; + } + var meta = meta_res.unwrap(); + + if (meta.is_dir) + { + if (config.recursive) + { + visit_dir(config.path, config); + } + else + { + "grep: {config.path}: Is a directory"; + } + } + else + { + var 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; +} |
