kfind — fast, tiny file finder written in V
Find a file
2025-09-03 21:29:55 +00:00
.gitignore Initial commit: kfind 0.3.1 2025-08-13 18:17:39 +02:00
kfind.v feat: --json (array) and --ndjson (one JSON object per line). Sorted, deterministic, ready for jq/pi 2025-09-03 21:23:51 +00:00
LICENSE added LICENCE 2025-08-13 18:27:35 +02:00
project.v Initial commit: kfind 0.3.1 2025-08-13 18:18:44 +02:00
README.md added "name filtering, and JSON/NDJSON output" to the description 2025-09-03 21:29:55 +00:00

README.md

kfind — fast, tiny file finder in V

kfind is a tiny, dependency-free file finder written in V with extension, name filtering, and JSON/NDJSON output. It walks a directory tree quickly, filters by extension and name, and prints a stable, sorted list of matches — perfect for scripting and sysadmin workflows.

Why admins love it

  • Single static binary drop it on any box and go.
  • Deterministic output sorted paths make diffs and pipelines reliable.
  • Targeted scans extension set + name query + optional depth cap = fast, focused searches.
  • No surprises doesnt follow hidden files/dirs unless you ask it to; clear, readable code.

Install / Run

# Run directly
v run kfind.v [path] [flags]

# Build an optimized binary
v -prod -o kfind kfind.v
./kfind [path] [flags]

CLI

kfind <version>
-----------------------------------------------
Usage: kfind [options] [path]

Description: Fast file finder with extension and name filtering.

Options:
  -e, --ext <string>        Comma-separated list of extensions to include (e.g. "rs,zig,v")
  -n, --name <string>       Substring to match in file path/name
  -i, --ignore-case         Case-insensitive name matching
  -H, --hidden              Include hidden files and directories
  -d, --max-depth <int>     Maximum recursion depth (-1 = unlimited)
  -J, --json                Output JSON array (["/a","/b",...])
  -N, --ndjson              Output line-delimited JSON objects ({"path":"..."} per line)
  -h, --help                display this help and exit
  --version                 output version information and exit

Examples

# Everything under current directory
kfind

# Only code-ish files
kfind . --ext=rs,zig,v,go,py

# Match by name (case-insensitive)
kfind /var/log --name error --ignore-case

# Limit recursion for speed
kfind /srv --ext=png,jpg --max-depth=2

# Include hidden entries
kfind --hidden --name .env

# JSON array for programmatic use
kfind . --ext=rs,zig,v -J | jq length

# NDJSON, perfect for big streams
kfind /var/log -n error -N | jq -r '.path' | xargs tail -n1

What makes kfind fast — the “k-find” algorithm

kfind uses a pruned depth-first search (DFS) with constant-time extension checks and optional query filtering:

  1. Normalize and pre-index extensions
    • --ext=jpg,png{'jpg': true, 'png': true}
    • Membership test is O(1) per file.
  2. Pruned DFS with an optional depth cap
    • max-depth = -1 → unlimited.
    • If max-depth >= 0, recursion stops once depth > max-depth.
    • Hidden files/dirs are skipped early unless --hidden is set, reducing work.
  3. Cheap, streaming filters
    • For each file, only compute file_ext() and a substring match for --name.
    • If --ignore-case, both strings are lower-cased once; no regex backtracking.
  4. Stable, minimal memory usage
    • DFS keeps one directorys listing at a time; memory ~ O(matches + depth).
    • Final output is sorted once at the end for reproducibility in scripts.

Complexity:

  • Let N be total directory entries visited. Time is O(N) with small constants.
  • Filtering cost is linear in filename lengths; extension checks are constant-time.
  • Memory: O(M + D) where M is number of matches, D is max recursion depth.

Pseudocode

ext_set = hashset(normalize(--ext))
dfs(path, depth):
  if max_depth >= 0 and depth > max_depth: return
  for entry in ls(path):
    if !hidden && is_hidden(entry): continue
    full = join(path, entry)
    if is_dir(full): dfs(full, depth + 1)
    else if (ext_set empty || file_ext(full) in ext_set) and name_matches(full):
      emit(full)
sort(results); print(results)

Design choices

  • No nested functions (V style) → helpers live at top level.
  • Explicit closure captures for safety: fn [query, case_insensitive] (f string) bool { ... }.
  • Immutability-first with copy-on-write where needed: mut matches := files.clone().
  • Clear failure modes: unreadable directories print a warning and the walk continues.
  • No symlink following by default (can be added later as a flag).

Scripting tips

  • Count matches: kfind --ext=log | wc -l
  • Feed into other tools: kfind /var/log -n error | xargs tail -n1
  • Save snapshots for diffing: kfind /srv/www --ext=html,css,js > listing.txt

Roadmap (nice-to-haves)

  • --follow-symlinks
  • Regex mode: --name-regex
  • Parallel walk (worker pool) for massive trees
  • JSON/NDJSON output for machine users (added)

License

This project is licensed under the European Union Public Licence v. 1.2. See the LICENSE file for details.