{"name":"find_clone.nu","source":"// find_clone.nu — grep-like recursive search over files / directories\n//\n// Demonstrates:\n//   - CLI flag parsing (nurl_argv_get / nurl_argv_count)\n//   - File I/O via stdlib/std/fs.nu (read_file + dir_list + IoErr)\n//   - Recursive directory walk\n//   - Regex engine via stdlib/ext/regex.nu (regex_compile / _test)\n//   - Owned-resource auto-drop across function boundaries\n//\n// Usage:\n//   find_clone [MODE] PATTERN [PATH ...]\n//\n// Modes:\n//   (default)              literal substring match\n//   --list PAT[,PAT...]    comma-separated alternatives (any match)\n//   --regex PAT            POSIX-extended regex\n//\n// PATH is one or more files or directories. Directories recurse;\n// hidden entries (leading \".\") are skipped. With no PATH the tool\n// reads stdin until EOF. Lines are NUL-byte-safe up to but not\n// including the first NUL (NURL strings are NUL-terminated).\n//\n// Output (per match):\n//   path:line:contents\n//\n// Exit codes:\n//   0  one or more matches were printed\n//   1  no matches found\n//   2  usage / I/O error\n//\n// Build & run:\n//   ./build/nurlc examples/find_clone.nu > /tmp/find_clone.ll\n//   clang /tmp/find_clone.ll stdlib/runtime.o -lm -lpthread -o /tmp/find_clone\n//   /tmp/find_clone error stdlib/std/log.nu\n//   /tmp/find_clone --list error,warn stdlib/std/\n//   /tmp/find_clone --regex 'log_[a-z]+f[0-9]' stdlib/std/log.nu\n\n$ `stdlib/core/string.nu`\n$ `stdlib/std/fs.nu`\n$ `stdlib/ext/regex.nu`\n\n// ── Line scanning ──────────────────────────────────────────────────\n//\n// Walk `text` line-by-line, calling the matcher closure for each.\n// Returns the number of matches (which we also print as a side effect\n// from the closure — the i-return is purely for \"any-match\" testing\n// in main's exit-code decision).\n\n@ scan_lines s path s text ( @ b s ) test → i {\n    : i n ( nurl_str_len text )\n    : *u p # *u text\n    : ~ i hits 0\n    : ~ i lineno 1\n    : ~ i start 0\n    : ~ i k 0\n    ~ < k n {\n        : i c & # i . p k 255\n        ? == c 10 {\n            : i ll - k start\n            : s line ( nurl_str_slice text start ll )\n            ? ( test line ) {\n                ( nurl_print path )\n                ( nurl_print `:` )\n                ( nurl_print ( nurl_str_int lineno ) )\n                ( nurl_print `:` )\n                ( nurl_print line )\n                ( nurl_print `\\n` )\n                = hits + hits 1\n            } {}\n            = lineno + lineno 1\n            = start + k 1\n        } {}\n        = k + k 1\n    }\n    // Trailing line without a closing newline.\n    ? > n start {\n        : s line ( nurl_str_slice text start - n start )\n        ? ( test line ) {\n            ( nurl_print path )\n            ( nurl_print `:` )\n            ( nurl_print ( nurl_str_int lineno ) )\n            ( nurl_print `:` )\n            ( nurl_print line )\n            ( nurl_print `\\n` )\n            = hits + hits 1\n        } {}\n    } {}\n    ^ hits\n}\n\n// ── Walk a single file ─────────────────────────────────────────────\n//\n// Reads the file's contents and delegates to scan_lines. Errors on\n// the read path print to stderr and contribute zero hits — find_clone\n// keeps walking the rest of the inputs.\n\n@ scan_file s path ( @ b s ) test → i {\n    : !String IoErr r ( read_file path )\n    ?? r {\n        T content → {\n            : i h ( scan_lines path ( string_data content ) test )\n            ^ h\n        }\n        F err → {\n            ( nurl_eprint `find_clone: ` )\n            ( nurl_eprint path )\n            ( nurl_eprint `: read failed\\n` )\n            ^ 0\n        }\n    }\n}\n\n// ── Recursive directory walk ───────────────────────────────────────\n//\n// Treats `path` as a directory: lists entries, skips dotfiles, and\n// for each non-dotfile entry computes the child path and recurses\n// via `walk` so a nested directory descends correctly. Returns the\n// total number of hits printed under this subtree. If `dir_list`\n// errors out (the path is a regular file, missing, or otherwise\n// inaccessible), returns -1 so the caller can fall back to the file\n// path.\n\n@ walk_dir s path ( @ b s ) test → i {\n    : !( Vec String ) IoErr r ( dir_list path )\n    ?? r {\n        T entries → {\n            : i n ( vec_len [String] entries )\n            : ~ i hits 0\n            : ~ i k 0\n            ~ < k n {\n                : ?String e ( vec_get [String] entries k )\n                ?? e {\n                    T se → {\n                        : s name ( string_data se )\n                        // Skip \".\" / \"..\" and dotfiles\n                        ? == 46 & # i . # *u name 0 255 {} {\n                            : String sub ( string_from path )\n                            ( string_push_str sub `/` )\n                            ( string_push_str sub name )\n                            = hits + hits ( walk ( string_data sub ) test )\n                        }\n                    }\n                    F → {}\n                }\n                = k + k 1\n            }\n            ^ hits\n        }\n        F err → ^ -1\n    }\n}\n\n// Dispatch: try directory walk first; if that errors out (it's not a\n// directory), treat `path` as a regular file.\n\n@ walk s path ( @ b s ) test → i {\n    : i d ( walk_dir path test )\n    ? >= d 0 ^ d {}\n    ^ ( scan_file path test )\n}\n\n// ── stdin path ─────────────────────────────────────────────────────\n\n@ scan_stdin ( @ b s ) test → i {\n    : ~ i hits 0\n    : ~ i lineno 1\n    ~ T {\n        : s line ( nurl_read_line )\n        ? == 0 # i line { = lineno -1 } {}\n        ? < lineno 0 {} {\n            ? ( test line ) {\n                ( nurl_print `<stdin>:` )\n                ( nurl_print ( nurl_str_int lineno ) )\n                ( nurl_print `:` )\n                ( nurl_print line )\n                ( nurl_print `\\n` )\n                = hits + hits 1\n            } {}\n            = lineno + lineno 1\n        }\n        ? < lineno 0 { ^ hits } {}\n    }\n    ^ hits\n}\n\n// ── Pattern matchers (closure-shaped so scan_lines is mode-agnostic) ─\n\n@ make_literal_test s needle → ( @ b s ) {\n    ^ \\ s line → b { ^ >= ( nurl_str_find line needle ) 0 }\n}\n\n@ make_list_test ( Vec String ) needles → ( @ b s ) {\n    ^ \\ s line → b {\n        : i n ( vec_len [String] needles )\n        : ~ i k 0\n        ~ < k n {\n            : ?String e ( vec_get [String] needles k )\n            ?? e {\n                T se → {\n                    ? >= ( nurl_str_find line ( string_data se ) ) 0 { ^ T } {}\n                }\n                F → {}\n            }\n            = k + k 1\n        }\n        ^ F\n    }\n}\n\n@ make_regex_test Regex rx → ( @ b s ) {\n    ^ \\ s line → b { ^ ( regex_test rx line ) }\n}\n\n// ── CLI parsing helpers ────────────────────────────────────────────\n\n@ split_csv s pats → ( Vec String ) {\n    : String holder ( string_from pats )\n    : ( Vec String ) parts ( string_split holder `,` )\n    ( string_free holder )\n    ^ parts\n}\n\n@ usage → v {\n    ( nurl_eprint `Usage: find_clone [--list PAT[,PAT...] | --regex PAT | PATTERN] [PATH ...]\\n` )\n    ( nurl_eprint `Reads stdin when no PATH is given.\\n` )\n}\n\n// Drive a matcher closure over either stdin (no path args) or every\n// PATH on argv starting at `first_path`. Returns the total match count.\n@ run_with ( @ b s ) test i first_path i argc → i {\n    ? <= argc first_path { ^ ( scan_stdin test ) } {}\n    : ~ i total 0\n    : ~ i i first_path\n    ~ < i argc {\n        = total + total ( walk ( nurl_argv_get i ) test )\n        = i + i 1\n    }\n    ^ total\n}\n\n@ exit_for_hits i hits → i { ^ ? > hits 0 0 1 }\n\n@ main → i {\n    : i argc ( nurl_argv_count )\n    ? < argc 2 { ( usage ) ^ 2 } {}\n\n    : s flag ( nurl_argv_get 1 )\n\n    // --regex PATTERN [PATH...]\n    ? == ( nurl_str_eq flag `--regex` ) 1 {\n        ? < argc 3 { ( usage ) ^ 2 } {}\n        : !Regex ParseErr c ( regex_compile ( nurl_argv_get 2 ) )\n        ?? c {\n            T r → {\n                : i hits ( run_with ( make_regex_test r ) 3 argc )\n                ( regex_free r )\n                ^ ( exit_for_hits hits )\n            }\n            F e → {\n                ( nurl_eprint `find_clone: regex_compile failed\\n` )\n                ^ 2\n            }\n        }\n    } {}\n\n    // --list PAT,PAT,... [PATH...]\n    ? == ( nurl_str_eq flag `--list` ) 1 {\n        ? < argc 3 { ( usage ) ^ 2 } {}\n        : ( Vec String ) pats ( split_csv ( nurl_argv_get 2 ) )\n        ^ ( exit_for_hits ( run_with ( make_list_test pats ) 3 argc ) )\n    } {}\n\n    // Default: literal substring. `flag` IS the pattern.\n    ^ ( exit_for_hits ( run_with ( make_literal_test flag ) 2 argc ) )\n}\n","bytes":9016}