Skip to content

Instantly share code, notes, and snippets.

@marvhus
Last active October 21, 2025 15:34
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;
MAX_THREAD_COUNT :: 16;
Options :: enum_flags {
NONE :: 0;
OUTPUT_WHOLE_LINE :: 1; // Output the entire line, starting at @
}
Input :: struct {
arguments: [] string;
lane: Lane_Context;
}
main :: () {
args := get_command_line_arguments();
barrier: Barrier;
init(*barrier, MAX_THREAD_COUNT);
defer destroy(*barrier);
broadcast_value: int;
threads: [MAX_THREAD_COUNT] Thread;
for *threads {
input := New(Input,, temp);
input.arguments = args;
input.lane = .{
index = it_index,
count = MAX_THREAD_COUNT,
barrier = *barrier,
broadcast_memory = *broadcast_value
};
it.data = cast(*void) input;
thread_init(it, thread_main);
thread_start(it);
}
for *threads {
thread_deinit(it);
}
}
// @entry
thread_main :: (thread: *Thread) -> int {
using input := cast(*Input) thread.data;
context.lane = lane;
if arguments.count < 2 {
if context.lane.index == 0 then print(USAGE);
return 0;
}
// @options
options := Options.NONE;
for 2..arguments.count-1 if arguments[it] == {
case "--whole-line";
options |= .OUTPUT_WHOLE_LINE;
case;
if context.lane.index == 0 {
print("Unknown argument: %\n", arguments[it]);
print(USAGE);
}
return 0;
}
// @path @files
path := arguments[1];
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: [..] string;
file_counter: *int;
if context.lane.index == 0 {
for file_list(path, recursive = true) {
if ends_with(it, ".jai") then array_add(*files, it);
}
file_counter = New(int,, temp);
}
lane_broadcast(cast(*s64) *files.count, 0);
lane_broadcast(cast(*s64) *files.data, 0);
lane_broadcast(cast(*s64) *file_counter, 0);
while true {
index := atomic_add(file_counter, 1);
if index >= files.count break;
path := files[index];
handle_file(path, options);
}
return 0;
}
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 :: (path: string, options: Options) {
file, success := file_open(path);
if !success return;
defer file_close(*file);
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", path, line, bytes);
free(bytes);
}
}
#import "Lane";
#import "Pool";
#import "File";
#import "Basic";
#import "String";
#import "Thread";
#import "System";
#import "Atomics";
#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