summaryrefslogtreecommitdiff
path: root/examples/tools/mini_grep.zc
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-11 22:51:29 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-11 22:51:29 +0000
commit45feb20bf55c16ee415ef31a356ada67e1df18d6 (patch)
treeadfd5b4c5641dc5bdfac96f330553140ee90319b /examples/tools/mini_grep.zc
parent36938b584ea2d096d97a124b70da51f685850ff7 (diff)
Some more, and some fixes
Diffstat (limited to 'examples/tools/mini_grep.zc')
-rw-r--r--examples/tools/mini_grep.zc311
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;
+}