Skip to content

Instantly share code, notes, and snippets.

@marvhus
Last active September 2, 2025 17:22
Show Gist options
  • Save marvhus/47606b84ec75889e323bcc93ba50495b to your computer and use it in GitHub Desktop.
Save marvhus/47606b84ec75889e323bcc93ba50495b to your computer and use it in GitHub Desktop.
CLI application for finding notes left in your comments
// Copyright (C) 2025 marvhus <[email protected]>
// See end of file for extended copyright information.
USAGE :: #string DONE
Usage: notes <path> [options]
- path
The directory you want to search through.
- options
--whole-line
Output the whole line, (combining all notes on the same line into one), starting at @ and ending at newline or end of block-comment, whichever comes first.
DONE;
main :: () {
args := get_command_line_arguments();
if args.count < 2 {
print(USAGE);
return;
}
path := args[1];
options := Options.NONE;
for 2..args.count-1 if args[it] == {
case "--whole-line";
options |= .OUTPUT_WHOLE_LINE;
}
if path.count > 0 && path[path.count - 1] == #char "/" {
// A trailing slash will give the resulting path a //, as file_list() provides a starting slash.
path.count -= 1;
}
files := file_list(path, recursive = true);
if files.count < 1 return;
pool: Pool;
set_allocators(*pool);
group: Thread_Group;
{
num_cpus := get_number_of_processors();
assert(num_cpus >= 1);
if num_cpus > 200 then num_cpus = 200;
#if (OS == .WINDOWS) || (OS == .LINUX) { // Compensate for hyperthreads.
num_cpus /= 1;
}
num_threads := max(num_cpus -1, 2);
init(*group, num_threads, handle_file);
group.logging = false;
start(*group);
}
defer shutdown(*group);
allocator: Allocator;
allocator.proc = pool_allocator_proc;
allocator.data = *pool;
work_remaining := 0;
for file: files {
// Will break for non .jai files (because block comments work diffrently here)...
// soooo let's just ignore everything else. -mvh, 2025-09-01
if !ends_with(file, ".jai") continue;
input := New(Input,, allocator);
input.path = file;
input.options = options;
add_work(*group, input, tprint("File: %", path));
work_remaining += 1;
}
reset_temporary_storage();
// @NOTE(mvh): It would be nice if there was a wait_for_all() function... but this works I guess.
while work_remaining > 0 {
sleep_milliseconds(1);
results := get_completed_work(*group);
work_remaining -= results.count;
}
}
Options :: enum_flags {
NONE :: 0;
OUTPUT_WHOLE_LINE :: 1; // Output the entire line, starting at @
}
Input :: struct {
path: string;
options: Options;
}
advance :: #bake_arguments file_seek(type = .CURRENT);
peek :: (file: File, offset := 0) -> u8 {
success, start := file_tell(file);
if !success return 0;
char := get(file, offset);
if char == 0 return 0;
success = file_seek(file, start, .START);
if !success return 0;
return char;
}
get :: (file: File, offset := 0) -> u8 {
if offset > 0 {
success := advance(file, offset);
if !success return 0;
}
char: u8;
success, total := file_read(file, *char, 1);
if !success || total < 1 return 0;
return char;
}
handle_file :: (group: *Thread_Group, thread: *Thread, work_data: *void) -> Thread_Continue_Status {
input := cast(*Input) work_data;
file := file_open(input.path);
defer file_close(*file);
options := input.options;
line := 1;
in_comment: enum {NONE; LINE; BLOCK;} = .NONE;
block_stack := 0;
while true {
char := peek(file);
if char == {
case 0;
break;
case #char "\n";
line += 1;
if in_comment == .LINE then in_comment = .NONE;
advance(file, 1);
continue;
case #char "/"; // Comment start
char := peek(file, 1);
if char == {
case #char "/";
in_comment = .LINE;
advance(file, 2);
continue;
case #char "*";
in_comment = .BLOCK;
block_stack += 1;
advance(file, 2);
continue;
}
case #char "*"; // Block comment end
char := peek(file, 1);
if char == #char "/" {
block_stack -= 1;
if block_stack <= 0 {
block_stack = 0;
in_comment = .NONE;
}
advance(file, 2);
}
}
if in_comment == .NONE || char != #char "@" {
advance(file, 1);
continue;
}
if !advance(file, 1) break;
success, start := file_tell(file);
if !success break;
length := 0;
if options & .OUTPUT_WHOLE_LINE {
start -= 1;
length += 1;
while true {
char := get(file);
if char == #char "\n" break;
if char == #char "\r" && peek(file) == #char "\n" {
get(file);
break;
}
if in_comment == .BLOCK && char == #char "*" && peek(file) == #char "/" {
get(file);
break;
}
length += 1;
}
if length < 2 continue;
} else {
while true {
char := get(file);
if !is_alpha(char) break;
length += 1;
}
if length < 1 continue;
}
if !file_seek(file, start, .START) break;
bytes := NewArray(length, u8).(string);
for 0..length-1 {
char := get(file);
bytes[it] = char;
}
print("%:%: %\n", input.path, line, bytes);
free(bytes);
}
return .CONTINUE; // Keep using this thread for other stuff.
}
#import "Pool";
#import "File";
#import "Basic";
#import "String";
#import "Thread";
#import "System";
#import "File_Utilities";
// notes.jai - CLI application for finding notes left in your comments
// Copyright (C) 2025 marvhus <[email protected]>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment