<<
path:
root/public/track.git/html/src/main.jai
blob: 96ae34b3d70f988c95dba7c859583873be7be7d0
[raw]
[clear marker]
3 * Copyright (c) 2026 dev@ptrace.dev
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in all
13 * copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 MEMORY_DEBUGGER = MEMORY_DEBUGGER_ENABLED
31#import "File_Utilities";
39Re :: #import "uniform";
46/** Limitations: This pattern won't catch keywords inside multiline
47 comments, that have other paragraphs in front on them. Like this:
50 My multiline comment that explains something.
52 TODO(me, 10): Check if this `TODO` is reachable!
55 Spoiler: This todo comment is not reachable.
57 It wouldn't be a problem to catch this, but this would also catch
58 stuff, that probably isn't inside a comment and set a higher risk
63Filter :: enum_flags u8 {
73 #if MEMORY_DEBUGGER_ENABLED defer report_memory_leaks();
75 cmd_args := get_command_line_arguments();
76 defer this_allocation_is_not_a_leak(cmd_args.data);
78 user_paths: [..]string;
79 defer this_allocation_is_not_a_leak(user_paths.data);
82 defer this_allocation_is_not_a_leak(visitor.paths.data);
85 termcolors_init_allocator_and_context();
87 success := args_read(cmd_args, *user_paths);
96 files_collect(".", *visitor);
98 for user_paths files_collect(it, *visitor);
102 print("No results\n");
106 had_updates := false;
109 is_updated, todo, note, idea, cont, other := update(it);
110 had_updates |= is_updated;
113 display(todo, note, idea, cont, other, path_filename(it));
116 termcolors_reset_pool(true);
117 reset_temporary_storage();
120 if !had_updates print("No results\n");
123set_filter :: () -> Filter {
126 if args.filter_todo then filter |= .TODO;
127 if args.filter_note then filter |= .NOTE;
128 if args.filter_continue then filter |= .CONTINUE;
129 if args.filter_idea then filter |= .IDEA;
148HEEEEEELP :: #string STR_END
149Displays TODO, NOTE, IDEA or CONTINUE in files.
152 track [dir or files] [options]
155 -h Show (this) help menu
157 -o Omit 'other' results
158 -i Stops ignoring dotfiles dotdirs
160 -sa Sorts by author instead by priority
168 -err-io (Debug): shows IO errors
170 -microslop-cringe Normalizes lines endings
180 follow_symlinks := false;
181 microslop_cringe := false;
182 sort_by_author := false;
183 show_io_errors := false;
185 // TODO: This is garbage
186 filter_todo := false;
187 filter_note := false;
188 filter_continue := false;
189 filter_idea := false;
192// Letting this struct, in case I want to extend it
198args_read :: (cmd_args: []string, user_paths: *[..]string) -> success: bool {
200 find :: (arg: *bool, patterns: ..string) #expand {
202 if array_find(`cmd_args, it) {
204 array_unordered_remove_by_value(*`cmd_args, it);
209 find(*args.help, "-h", "--help", "-help", "help");
210 find(*args.recursive, "-r");
211 find(*args.omit_other, "-o");
212 find(*args.ignore_dot, "-i");
213 find(*args.follow_symlinks, "-sl");
214 find(*args.microslop_cringe, "-microslop-cringe");
215 find(*args.sort_by_author, "-sa");
216 find(*args.show_io_errors, "-err-io");
217 find(*args.filter_todo, "-ft");
218 find(*args.filter_note, "-fn");
219 find(*args.filter_continue, "-fc");
220 find(*args.filter_idea, "-fi");
222 if cmd_args.count < 2 return true;
223 for i: 1..cmd_args.count-1 array_add(user_paths, cmd_args[i]);
228update :: (path: string) -> (
236 does_array_has_data :: (arr: []$T) #expand {
237 `is_updated |= cast(bool, arr);
242 dummy.allocator = temp;
244 source, f_success := read_entire_file(path, log_errors = args.show_io_errors);
248 // TODO: Kinda cringe, I have to come up with a way better idea
249 return false, dummy, dummy, dummy, dummy, dummy;
252 if args.microslop_cringe then normalize_line_endings(*source);
253 has_match, matches := matches_collect(source,, temp);
256 return false, dummy, dummy, dummy, dummy, dummy;
259 tokens := matches_process(source, matches,, temp);
260 tokens_sorted := sort_tokens(tokens);
261 todo, note, idea, cont, other := filter_tokens(tokens_sorted,, temp);
264 does_array_has_data(todo);
265 does_array_has_data(note);
266 does_array_has_data(idea);
267 does_array_has_data(cont);
268 does_array_has_data(other);
270 // TODO: This is cringe too
271 return is_updated, todo, note, idea, cont, other;
274filter_tokens :: (tokens: []Token)
275 -> (todo: [..]Token, note: [..]Token, idea: [..]Token, cont: [..]Token, other: [..]Token)
277 should_add :: (expected: Filter, arr: *[..]$T) #expand {
278 if `filter & expected then array_add(arr, `it);
281 filter := set_filter();
282 todo, note, idea, cont, other: [..]Token;
285 if #complete it.identifier == {
286 case .TODO; should_add(.TODO, *todo);
287 case .NOTE; should_add(.NOTE, *note);
288 case .IDEA; should_add(.IDEA, *idea);
289 case .CONTINUE; should_add(.CONTINUE, *cont);
291 if !args.omit_other then array_add(*other, it);
295 return todo, note, idea, cont, other;
298sort_tokens :: (tokens: [..]Token) -> []Token {
299 if args.sort_by_author {
300 return quick_sort(tokens, compare_author);
302 return quick_sort(tokens, compare_priority);
306compare_author :: (a: Token, b: Token) -> s64 {
307 return compare_strings(xx a.author, xx b.author);
310compare_priority :: (a: Token, b: Token) -> s64 {
311 return compare_floats(xx b.priority, xx a.priority);
314files_collect :: (path: string, visitor: *Visitor) {
316 add_if_file :: (path: string) #expand {
317 is_a_file, success := is_file(path);
320 log_error("Not a valid path: %", path);
325 array_add(*`visitor.paths, path);
330 collect_files :: (using info: *File_Visit_Info, visitor: *Visitor) {
331 if args.ignore_dot && starts_with(short_name, ".") {
332 descend_into_directory = false;
336 if had_error then return;
337 add_if_file(full_name);
340 path_trimmed := trim_right(path, "/");
343 add_if_file(path_trimmed);
346 complete := visit_files(
348 recursive = args.recursive,
352 visit_directories = true,
353 visit_symlinks = args.follow_symlinks
357 log_error("[Warning]: Some error occured while traversing the file tree");
361is_file :: (path: string) -> bool, success: bool {
364 p := to_c_string(path);
365 ret := stat(p, *stats);
366 this_allocation_is_not_a_leak(p);
368 if ret != 0 return false, false;
369 return S_ISREG(stats.st_mode), true;