Better arg parsing

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-22 02:24:44 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-22 02:24:44 +0100
Commit 069301a603ad07338179bafed0c35b6fe3b372f2 (patch)
-rw-r--r-- list.c 6
-rw-r--r-- list.h 2
-rw-r--r-- main.c 54
-rwxr-xr-x tests.sh 28
-rw-r--r-- tests/depth_test/level0.c 1
-rw-r--r-- tests/depth_test/sub/level1.c 1
-rw-r--r-- tests/test.c 5
7 files changed, 81 insertions, 16 deletions
diff --git a/list.c b/list.c
...
22
	*head = new;
22
	*head = new;
23
}
23
}
24
  
24
  
25
void list_files_recursively(char *base_path, Node **head) {
25
void list_files_recursively(char *base_path, Node **head, int max_depth, int current_depth) {
26
	struct stat statbuf;
26
	struct stat statbuf;
27
	if (stat(base_path, &statbuf) == -1) {
27
	if (stat(base_path, &statbuf) == -1) {
28
		perror("stat");
28
		perror("stat");
...
52
  
52
  
53
			if (stat(path, &statbuf) != -1) {
53
			if (stat(path, &statbuf) != -1) {
54
				if (S_ISDIR(statbuf.st_mode)) {
54
				if (S_ISDIR(statbuf.st_mode)) {
55
					list_files_recursively(path, head);
55
					if (max_depth == -1 || current_depth < max_depth) {
  
56
						list_files_recursively(path, head, max_depth, current_depth + 1);
  
57
					}
56
				} else if (S_ISREG(statbuf.st_mode)) {
58
				} else if (S_ISREG(statbuf.st_mode)) {
57
					add_file_path(head, path);
59
					add_file_path(head, path);
58
				}
60
				}
...
diff --git a/list.h b/list.h
...
7
} Node;
7
} Node;
8
  
8
  
9
void add_file_path(Node **head, char *file_path);
9
void add_file_path(Node **head, char *file_path);
10
void list_files_recursively(char *base_path, Node **head);
10
void list_files_recursively(char *base_path, Node **head, int max_depth, int current_depth);
11
void free_file_list(Node *head);
11
void free_file_list(Node *head);
12
int size_of_file_list(Node *head);
12
int size_of_file_list(Node *head);
13
  
13
  
...
diff --git a/main.c b/main.c
1
// TODO:
1
// TODO:
2
//  - Add language specific filter (by default all but it can also passed
  
3
//    with -tpy -tc -trb) which would only parse python, c and ruby files.
  
4
//  - By default its case insensitive but with passing -cs it tells that
  
5
//    all matching should be done in case sensitive way.
  
6
//  - Add Levenshtein distance for matching and expose distance as arg with
2
//  - Add Levenshtein distance for matching and expose distance as arg with
7
//    something like -d5 which would allow distance of 5 on a match.
3
//    something like -d5 which would allow distance of 5 on a match.
8
//  - Allow DEBUG to be provided as environmental variable.
  
9
//  - Added depth flag (-r means recursive, -l2 means 2 levels deep).
  
10
  
4
  
11
// FIXME:
5
// FIXME:
12
//  - Truncate longer argument list.
6
//  - Truncate longer argument list.
13
  
7
  
  
8
#define _GNU_SOURCE
14
#include <assert.h>
9
#include <assert.h>
  
10
#include <getopt.h>
15
#include <pthread.h>
11
#include <pthread.h>
  
12
#include <stdarg.h>
16
#include <stdio.h>
13
#include <stdio.h>
17
#include <stdlib.h>
14
#include <stdlib.h>
18
#include <string.h>
15
#include <string.h>
...
26
#include "queries/c.h"
23
#include "queries/c.h"
27
#include "queries/cpp.h"
24
#include "queries/cpp.h"
28
#include "queries/go.h"
25
#include "queries/go.h"
  
26
#include "queries/javascript.h"
29
#include "queries/php.h"
27
#include "queries/php.h"
30
#include "queries/python.h"
28
#include "queries/python.h"
31
#include "queries/rust.h"
29
#include "queries/rust.h"
32
#include "queries/javascript.h"
  
33
  
30
  
34
int debug_enabled = 0;
31
int debug_enabled = 0;
35
  
32
  
...
93
	const char *query_string;
90
	const char *query_string;
94
	uint32_t query_len;
91
	uint32_t query_len;
95
	const char *cfname;
92
	const char *cfname;
  
93
	int case_sensitive;
96
};
94
};
97
  
95
  
98
// void parse_source_file(const char *file_path, const char *source_code,
96
// void parse_source_file(const char *file_path, const char *source_code,
...
104
	const char *source_code = args->source_code;
102
	const char *source_code = args->source_code;
105
	TSLanguage *language = args->language;
103
	TSLanguage *language = args->language;
106
	const char *cfname = args->cfname;
104
	const char *cfname = args->cfname;
  
105
	int case_sensitive = args->case_sensitive;
107
  
106
  
108
	TSParser *parser = ts_parser_new();
107
	TSParser *parser = ts_parser_new();
109
	ts_parser_set_language(parser, language);
108
	ts_parser_set_language(parser, language);
...
172
		// Substring matching.
171
		// Substring matching.
173
		// FIXME: Add Levenshtein distance.
172
		// FIXME: Add Levenshtein distance.
174
		if (fn.fname != NULL) {
173
		if (fn.fname != NULL) {
175
			char *result = strstr(fn.fname, cfname);
174
			char *result;
  
175
			if (case_sensitive) {
  
176
				result = strstr(fn.fname, cfname);
  
177
			} else {
  
178
				result = strcasestr(fn.fname, cfname);
  
179
			}
  
180
  
176
			if (result != NULL) {
181
			if (result != NULL) {
177
				char *fparams_formatted = remove_newlines(fn.fparams);
182
				char *fparams_formatted = remove_newlines(fn.fparams);
178
				printf("%s:%zu: %s %s %s\n", file_path, fn.lineno, fn.ftype ? fn.ftype : "", fn.fname, fparams_formatted ? fparams_formatted : "");
183
				printf("%s:%zu: %s %s %s\n", file_path, fn.lineno, fn.ftype ? fn.ftype : "", fn.fname, fparams_formatted ? fparams_formatted : "");
...
205
}
210
}
206
  
211
  
207
int main(int argc, char *argv[]) {
212
int main(int argc, char *argv[]) {
208
	if (argc < 2) {
213
	int case_sensitive = 0;
209
		printf("Usage: %s <search term> [directory|file]\n", argv[0]);
214
	int max_depth = -1;
  
215
	int opt;
  
216
	struct option long_options[] = {
  
217
		{"case-sensitive", no_argument, 0, 'c'},
  
218
		{"depth", required_argument, 0, 'd'},
  
219
		{0, 0, 0, 0}};
  
220
  
  
221
	while ((opt = getopt_long(argc, argv, "cd:", long_options, NULL)) != -1) {
  
222
		switch (opt) {
  
223
		case 'c':
  
224
			case_sensitive = 1;
  
225
			break;
  
226
		case 'd':
  
227
			max_depth = atoi(optarg);
  
228
			break;
  
229
		default:
  
230
			fprintf(stderr, "Usage: %s [-c|--case-sensitive] [-d|--depth <level>] <search term> [directory|file]\n", argv[0]);
  
231
			return 1;
  
232
		}
  
233
	}
  
234
  
  
235
	if (optind >= argc) {
  
236
		fprintf(stderr, "Usage: %s [-c|--case-sensitive] [-d|--depth <level>] <search term> [directory|file]\n", argv[0]);
210
		return 1;
237
		return 1;
211
	}
238
	}
212
  
239
  
213
	const char *cfname = argv[1];
240
	const char *cfname = argv[optind];
214
	char *directory = (argc > 2) ? argv[2] : ".";
241
	char *directory = (optind + 1 < argc) ? argv[optind + 1] : ".";
215
  
242
  
216
	Node *head = NULL;
243
	Node *head = NULL;
217
	list_files_recursively(directory, &head);
244
	list_files_recursively(directory, &head, max_depth, 0);
218
	int list_size = size_of_file_list(head);
245
	int list_size = size_of_file_list(head);
219
  
246
  
220
	const char *debug_env = getenv("DEBUG");
247
	const char *debug_env = getenv("DEBUG");
...
289
				thread_args->query_string = query_string;
316
				thread_args->query_string = query_string;
290
				thread_args->query_len = query_len;
317
				thread_args->query_len = query_len;
291
				thread_args->cfname = cfname;
318
				thread_args->cfname = cfname;
  
319
				thread_args->case_sensitive = case_sensitive;
292
  
320
  
293
				tp_add_job(pool, (thread_func_t)parse_source_file, thread_args);
321
				tp_add_job(pool, (thread_func_t)parse_source_file, thread_args);
294
			} else {
322
			} else {
...
diff --git a/tests.sh b/tests.sh
...
29
    fi
29
    fi
30
}
30
}
31
  
31
  
  
32
run_test_with_flags() {
  
33
    local label=$1
  
34
    local flags=$2
  
35
    local search_term=$3
  
36
    local file=$4
  
37
    local expected_pattern=$5
  
38
  
  
39
    printf "Testing %-50s " "$label ($flags $search_term)"
  
40
    output=$($CREP $flags "$search_term" "$file")
  
41
  
  
42
    if echo "$output" | grep -q "$expected_pattern"; then
  
43
        echo "PASSED"
  
44
    else
  
45
        echo "FAILED"
  
46
        echo "  Expected pattern: $expected_pattern"
  
47
        echo "  Actual output: $output"
  
48
        failed=$((failed + 1))
  
49
    fi
  
50
}
  
51
  
32
echo "Starting tests..."
52
echo "Starting tests..."
33
echo "----------------"
53
echo "----------------"
34
  
54
  
...
76
run_test "C++ Class" "MyClass" "$TEST_DIR/test.cpp" "class MyClass"
96
run_test "C++ Class" "MyClass" "$TEST_DIR/test.cpp" "class MyClass"
77
run_test "C++ Struct" "MyStruct" "$TEST_DIR/test.cpp" "struct MyStruct"
97
run_test "C++ Struct" "MyStruct" "$TEST_DIR/test.cpp" "struct MyStruct"
78
run_test "C++ Method" "myMethod" "$TEST_DIR/test.cpp" "void myMethod ()"
98
run_test "C++ Method" "myMethod" "$TEST_DIR/test.cpp" "void myMethod ()"
  
99
  
  
100
# Case Sensitivity Tests
  
101
run_test "Default Case Insensitive" "foo" "tests/test.c" "void FooBar"
  
102
run_test_with_flags "Case Sensitive -c" "-c" "foobar" "tests/test.c" "void foobar"
  
103
  
  
104
# Depth Tests
  
105
run_test_with_flags "Depth 0 (root)" "-d 0" "level" "tests/depth_test" "void level0"
  
106
run_test_with_flags "Depth 1 (recursive)" "-d 1" "level" "tests/depth_test" "void level1"
79
  
107
  
80
echo "----------------"
108
echo "----------------"
81
if [ $failed -eq 0 ]; then
109
if [ $failed -eq 0 ]; then
...
diff --git a/tests/depth_test/level0.c b/tests/depth_test/level0.c
  
1
void level0() {}
diff --git a/tests/depth_test/sub/level1.c b/tests/depth_test/sub/level1.c
  
1
void level1() {}
diff --git a/tests/test.c b/tests/test.c
...
30
    hello();
30
    hello();
31
    return 0;
31
    return 0;
32
}
32
}
  
33
  
  
34
// Case sensitivity tests
  
35
void FooBar() {}
  
36
void foobar() {}
  
37
void FOOBAR() {}