mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 18:48:33 -04:00
wip: "full" finder
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package findr
|
package findr
|
||||||
|
|
||||||
import "core:os"
|
import "core:os"
|
||||||
|
import "core:strings"
|
||||||
|
import "core:sys/linux"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -364,3 +366,64 @@ test_no_hidden_skips_dotfiles :: proc(t: ^testing.T) {
|
|||||||
{"repo/secrets.env"},
|
{"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:strings"
|
||||||
import "core:text/regex"
|
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 {
|
is_regex_meta :: proc(c: u8) -> bool {
|
||||||
switch c {
|
switch c {
|
||||||
case '.', '+', '(', ')', '{', '}', '^', '$', '|':
|
case '.', '+', '(', ')', '{', '}', '^', '$', '|', '#':
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
glob_to_regex :: proc(pattern: string, anchored: bool) -> string {
|
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
|
sb: strings.Builder
|
||||||
strings.builder_init(&sb)
|
strings.builder_init(&sb)
|
||||||
defer strings.builder_destroy(&sb)
|
defer strings.builder_destroy(&sb)
|
||||||
@@ -98,8 +100,8 @@ glob_to_regex :: proc(pattern: string, anchored: bool) -> string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rule :: struct {
|
Rule :: struct {
|
||||||
regex: regex.Regular_Expression,
|
regex: regex.Regular_Expression,
|
||||||
negated: bool,
|
negated: bool,
|
||||||
dir_only: bool,
|
dir_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,17 +153,17 @@ parse :: proc(content: string) -> Gitignore {
|
|||||||
delete(regex_str)
|
delete(regex_str)
|
||||||
if err != nil do continue
|
if err != nil do continue
|
||||||
|
|
||||||
append(&gi.rules, Rule{
|
append(&gi.rules, Rule{regex = re, negated = negated, dir_only = dir_only})
|
||||||
regex = re,
|
|
||||||
negated = negated,
|
|
||||||
dir_only = dir_only,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gi
|
return gi
|
||||||
}
|
}
|
||||||
|
|
||||||
Match :: enum { None, Ignored, Unignored }
|
Match :: enum {
|
||||||
|
None,
|
||||||
|
Ignored,
|
||||||
|
Unignored,
|
||||||
|
}
|
||||||
|
|
||||||
check_match :: proc(gi: ^Gitignore, path: string, is_dir: bool) -> Match {
|
check_match :: proc(gi: ^Gitignore, path: string, is_dir: bool) -> Match {
|
||||||
result := Match.None
|
result := Match.None
|
||||||
@@ -186,3 +188,4 @@ destroy :: proc(gi: ^Gitignore) {
|
|||||||
}
|
}
|
||||||
delete(gi.rules)
|
delete(gi.rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,20 @@ test_glob_backslash_escape :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, result, "(^|/)!foo(/.*)?$")
|
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)
|
||||||
test_glob_empty :: proc(t: ^testing.T) {
|
test_glob_empty :: proc(t: ^testing.T) {
|
||||||
result := glob_to_regex("", false)
|
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)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -181,22 +181,20 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
|||||||
rel = ""
|
rel = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_git || gi_ctx != nil {
|
gi := load_ignore_patterns(dir_path, has_git || gi_ctx != nil)
|
||||||
gi := load_gitignore(dir_path)
|
if gi != nil {
|
||||||
if gi != nil {
|
new_ctx := new(GIContext)
|
||||||
new_ctx := new(GIContext)
|
new_ctx.gi = gi
|
||||||
new_ctx.gi = gi
|
if len(rel) > 0 {
|
||||||
if len(rel) > 0 {
|
new_ctx.base_rel, _ = strings.clone(rel)
|
||||||
new_ctx.base_rel, _ = strings.clone(rel)
|
|
||||||
}
|
|
||||||
new_ctx.parent = gi_ctx
|
|
||||||
|
|
||||||
sync.mutex_lock(&pool.contexts_lock)
|
|
||||||
append(&pool.all_contexts, new_ctx)
|
|
||||||
sync.mutex_unlock(&pool.contexts_lock)
|
|
||||||
|
|
||||||
gi_ctx = new_ctx
|
|
||||||
}
|
}
|
||||||
|
new_ctx.parent = gi_ctx
|
||||||
|
|
||||||
|
sync.mutex_lock(&pool.contexts_lock)
|
||||||
|
append(&pool.all_contexts, new_ctx)
|
||||||
|
sync.mutex_unlock(&pool.contexts_lock)
|
||||||
|
|
||||||
|
gi_ctx = new_ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
rel_buf: [4096]u8
|
rel_buf: [4096]u8
|
||||||
@@ -205,7 +203,7 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
|||||||
if entry.name == ".git" do continue
|
if entry.name == ".git" do continue
|
||||||
|
|
||||||
is_dir := entry.type == .DIR
|
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) {
|
if pool.exclude_gi != nil && is_ignored(pool.exclude_gi, entry.name, is_dir) {
|
||||||
continue
|
continue
|
||||||
@@ -241,7 +239,7 @@ process_dir :: proc(pool: ^WalkerPool, item: WorkItem) {
|
|||||||
child_path := join_path(dir_path, entry.name)
|
child_path := join_path(dir_path, entry.name)
|
||||||
push_work(pool, WorkItem{path = child_path, rel = child_rel, gi_ctx = gi_ctx})
|
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) {
|
if should_emit && matches_pattern(pool, entry.name) {
|
||||||
full_path := join_path(dir_path, entry.name)
|
full_path := join_path(dir_path, entry.name)
|
||||||
sync.mutex_lock(&pool.results_mutex)
|
sync.mutex_lock(&pool.results_mutex)
|
||||||
@@ -345,16 +343,37 @@ free_entries :: proc(entries: ^[dynamic]RawEntry) {
|
|||||||
delete(entries^)
|
delete(entries^)
|
||||||
}
|
}
|
||||||
|
|
||||||
load_gitignore :: proc(dir_path: string) -> ^Gitignore {
|
load_ignore_patterns :: proc(dir_path: string, in_repo: bool) -> ^Gitignore {
|
||||||
gi_path := join_path(dir_path, ".gitignore")
|
has_patterns := false
|
||||||
defer delete(gi_path)
|
sb: strings.Builder
|
||||||
|
strings.builder_init(&sb)
|
||||||
|
defer strings.builder_destroy(&sb)
|
||||||
|
|
||||||
data, err := os.read_entire_file_from_path(gi_path, context.allocator)
|
if in_repo {
|
||||||
if err != .NONE do return nil
|
gi_path := join_path(dir_path, ".gitignore")
|
||||||
|
data, err := os.read_entire_file_from_path(gi_path, context.allocator)
|
||||||
|
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 := new(Gitignore)
|
||||||
gi^ = parse(string(data))
|
gi^ = parse(content)
|
||||||
delete(data)
|
|
||||||
return gi
|
return gi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user