wip: "full" finder
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package findr
|
||||
|
||||
import "core:os"
|
||||
import "core:strings"
|
||||
import "core:sys/linux"
|
||||
import "core:testing"
|
||||
|
||||
// ============================================================================
|
||||
@@ -364,3 +366,64 @@ test_no_hidden_skips_dotfiles :: proc(t: ^testing.T) {
|
||||
{"repo/secrets.env"},
|
||||
)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Special file type tests (SOCK, FIFO, CHR, BLK parity with fd)
|
||||
// ============================================================================
|
||||
|
||||
@(test)
|
||||
test_fifo_emitted :: proc(t: ^testing.T) {
|
||||
env := create_test_env()
|
||||
defer destroy_test_env(&env)
|
||||
|
||||
create_git_repo(env, "repo")
|
||||
create_file(env, "repo/.gitignore", "*.env\n")
|
||||
|
||||
fifo_path := join_path(env.temp_dir, "repo/test.fifo")
|
||||
defer delete(fifo_path)
|
||||
cpath := strings.clone_to_cstring(fifo_path)
|
||||
defer delete(cpath)
|
||||
linux.mknod(cpath, linux.S_IFIFO | linux.Mode{.IRUSR, .IWUSR}, 0)
|
||||
|
||||
assert_output(t, env, nil,
|
||||
{include_hidden = true, ignore_mode = .All},
|
||||
{"repo/", "repo/.gitignore", "repo/test.fifo"},
|
||||
)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// .ignore file support tests (fd respects .ignore in addition to .gitignore)
|
||||
// ============================================================================
|
||||
|
||||
@(test)
|
||||
test_ignore_file_respected :: proc(t: ^testing.T) {
|
||||
env := create_test_env()
|
||||
defer destroy_test_env(&env)
|
||||
|
||||
create_git_repo(env, "repo")
|
||||
create_file(env, "repo/.ignore", "*.tmp\n")
|
||||
create_file(env, "repo/file.tmp")
|
||||
create_file(env, "repo/file.txt")
|
||||
|
||||
assert_output(t, env, nil,
|
||||
{include_hidden = true, ignore_mode = .Respected},
|
||||
{"repo/", "repo/.ignore", "repo/file.txt"},
|
||||
)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_ignore_overrides_gitignore :: proc(t: ^testing.T) {
|
||||
env := create_test_env()
|
||||
defer destroy_test_env(&env)
|
||||
|
||||
create_git_repo(env, "repo")
|
||||
create_file(env, "repo/.gitignore", "*.log\n")
|
||||
create_file(env, "repo/.ignore", "important.log\n")
|
||||
create_file(env, "repo/debug.log")
|
||||
create_file(env, "repo/important.log")
|
||||
|
||||
assert_output(t, env, nil,
|
||||
{include_hidden = true, ignore_mode = .Respected},
|
||||
{"repo/", "repo/.gitignore", "repo/.ignore"},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,15 +4,17 @@ import "core:fmt"
|
||||
import "core:strings"
|
||||
import "core:text/regex"
|
||||
|
||||
// FIXME: Use a const bit_set[0..<128; u128] here when we start doing optimizations
|
||||
is_regex_meta :: proc(c: u8) -> bool {
|
||||
switch c {
|
||||
case '.', '+', '(', ')', '{', '}', '^', '$', '|':
|
||||
case '.', '+', '(', ')', '{', '}', '^', '$', '|', '#':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
glob_to_regex :: proc(pattern: string, anchored: bool) -> string {
|
||||
// TODO: Attempt to pre-allocate the string builder when we start doing optimizations
|
||||
sb: strings.Builder
|
||||
strings.builder_init(&sb)
|
||||
defer strings.builder_destroy(&sb)
|
||||
@@ -151,17 +153,17 @@ parse :: proc(content: string) -> Gitignore {
|
||||
delete(regex_str)
|
||||
if err != nil do continue
|
||||
|
||||
append(&gi.rules, Rule{
|
||||
regex = re,
|
||||
negated = negated,
|
||||
dir_only = dir_only,
|
||||
})
|
||||
append(&gi.rules, Rule{regex = re, negated = negated, dir_only = dir_only})
|
||||
}
|
||||
|
||||
return gi
|
||||
}
|
||||
|
||||
Match :: enum { None, Ignored, Unignored }
|
||||
Match :: enum {
|
||||
None,
|
||||
Ignored,
|
||||
Unignored,
|
||||
}
|
||||
|
||||
check_match :: proc(gi: ^Gitignore, path: string, is_dir: bool) -> Match {
|
||||
result := Match.None
|
||||
@@ -186,3 +188,4 @@ destroy :: proc(gi: ^Gitignore) {
|
||||
}
|
||||
delete(gi.rules)
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,20 @@ test_glob_backslash_escape :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, result, "(^|/)!foo(/.*)?$")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_glob_hash_escaped :: proc(t: ^testing.T) {
|
||||
result := glob_to_regex("#foo", false)
|
||||
defer delete(result)
|
||||
testing.expect_value(t, result, "(^|/)\\#foo(/.*)?$")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_glob_hash_in_pattern :: proc(t: ^testing.T) {
|
||||
result := glob_to_regex("#*#", false)
|
||||
defer delete(result)
|
||||
testing.expect_value(t, result, "(^|/)\\#[^/]*\\#(/.*)?$")
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_glob_empty :: proc(t: ^testing.T) {
|
||||
result := glob_to_regex("", false)
|
||||
@@ -176,3 +190,15 @@ test_is_ignored_globstar :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, is_ignored(&gi, "foo/bar/cache", false), true)
|
||||
}
|
||||
|
||||
@(test)
|
||||
test_is_ignored_hash_pattern :: proc(t: ^testing.T) {
|
||||
gi := parse("\\#*\\#\n")
|
||||
defer destroy(&gi)
|
||||
|
||||
testing.expect_value(t, is_ignored(&gi, "#foo#", false), true)
|
||||
testing.expect_value(t, is_ignored(&gi, "#test#", false), true)
|
||||
testing.expect_value(t, is_ignored(&gi, "AUTHORS", false), false)
|
||||
testing.expect_value(t, is_ignored(&gi, "build.zig", false), false)
|
||||
testing.expect_value(t, is_ignored(&gi, "ChangeLog", false), false)
|
||||
}
|
||||
|
||||
|
||||
43
walker.odin
43
walker.odin
@@ -181,8 +181,7 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
||||
rel = ""
|
||||
}
|
||||
|
||||
if has_git || gi_ctx != nil {
|
||||
gi := load_gitignore(dir_path)
|
||||
gi := load_ignore_patterns(dir_path, has_git || gi_ctx != nil)
|
||||
if gi != nil {
|
||||
new_ctx := new(GIContext)
|
||||
new_ctx.gi = gi
|
||||
@@ -197,7 +196,6 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
||||
|
||||
gi_ctx = new_ctx
|
||||
}
|
||||
}
|
||||
|
||||
rel_buf: [4096]u8
|
||||
|
||||
@@ -205,7 +203,7 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
||||
if entry.name == ".git" do continue
|
||||
|
||||
is_dir := entry.type == .DIR
|
||||
is_regular := entry.type == .REG || entry.type == .UNKNOWN || entry.type == .LNK
|
||||
is_nondir := entry.type != .DIR
|
||||
|
||||
if pool.exclude_gi != nil && is_ignored(pool.exclude_gi, entry.name, is_dir) {
|
||||
continue
|
||||
@@ -241,7 +239,7 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
||||
child_path := join_path(dir_path, entry.name)
|
||||
push_work(pool, WorkItem{path = child_path, rel = child_rel, gi_ctx = gi_ctx})
|
||||
}
|
||||
} else if is_regular {
|
||||
} else if is_nondir {
|
||||
if should_emit && matches_pattern(pool, entry.name) {
|
||||
full_path := join_path(dir_path, entry.name)
|
||||
sync.mutex_lock(&pool.results_mutex)
|
||||
@@ -345,16 +343,37 @@ free_entries :: proc(entries: ^[dynamic]RawEntry) {
|
||||
delete(entries^)
|
||||
}
|
||||
|
||||
load_gitignore :: proc(dir_path: string) -> ^Gitignore {
|
||||
load_ignore_patterns :: proc(dir_path: string, in_repo: bool) -> ^Gitignore {
|
||||
has_patterns := false
|
||||
sb: strings.Builder
|
||||
strings.builder_init(&sb)
|
||||
defer strings.builder_destroy(&sb)
|
||||
|
||||
if in_repo {
|
||||
gi_path := join_path(dir_path, ".gitignore")
|
||||
defer delete(gi_path)
|
||||
|
||||
data, err := os.read_entire_file_from_path(gi_path, context.allocator)
|
||||
if err != .NONE do return nil
|
||||
|
||||
gi := new(Gitignore)
|
||||
gi^ = parse(string(data))
|
||||
delete(gi_path)
|
||||
if err == .NONE {
|
||||
fmt.sbprintf(&sb, "%s", string(data))
|
||||
delete(data)
|
||||
has_patterns = true
|
||||
}
|
||||
}
|
||||
|
||||
ig_path := join_path(dir_path, ".ignore")
|
||||
idata, ierr := os.read_entire_file_from_path(ig_path, context.allocator)
|
||||
delete(ig_path)
|
||||
if ierr == .NONE {
|
||||
fmt.sbprintf(&sb, "%s", string(idata))
|
||||
delete(idata)
|
||||
has_patterns = true
|
||||
}
|
||||
|
||||
if !has_patterns do return nil
|
||||
|
||||
content := strings.to_string(sb)
|
||||
gi := new(Gitignore)
|
||||
gi^ = parse(content)
|
||||
return gi
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user