137 lines
4.6 KiB
Python
Executable File
137 lines
4.6 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# ollie:parallel read
|
|
# args_json: {"type":"object","required":["pattern"],"properties":{"pattern":{"type":"string","description":"Regex pattern to search"},"path":{"type":"string","description":"Search directory (default: cwd)"},"mode":{"type":"string","description":"content|files_with_matches|count (default: content)"},"glob":{"type":"string","description":"File glob filter"},"type":{"type":"string","description":"File type filter (rg --type)"},"case_insensitive":{"type":"boolean","description":"Case-insensitive search"},"after":{"type":"integer","description":"Lines after match (-A)"},"before":{"type":"integer","description":"Lines before match (-B)"},"context":{"type":"integer","description":"Lines around match (-C)"},"multiline":{"type":"boolean","description":"Enable multiline matching"},"head":{"type":"integer","description":"Max results to return (default: 50)"},"offset":{"type":"integer","description":"Skip first N results"}}}
|
|
# ollie:prompt
|
|
# ## file_grep
|
|
#
|
|
# Search file contents with ripgrep. Capped at 50 matches by default.
|
|
#
|
|
# **Args**: `pattern` (required), plus optional named params.
|
|
#
|
|
# ```
|
|
# file_grep(pattern="pattern", path="/abs/dir")
|
|
# file_grep(pattern="TODO", path="src/", glob="*.go", case_insensitive=true)
|
|
# file_grep(pattern="func main", path="/home/user/project")
|
|
# ```
|
|
#
|
|
# - `path`: search directory (not a positional arg)
|
|
# - `glob`: file glob filter
|
|
# - `case_insensitive`: boolean
|
|
#
|
|
# **Params**: `path`, `mode` (content|files_with_matches|count), `glob`, `type`, `case_insensitive`, `before`, `after`, `context`, `multiline`, `head`, `offset`.
|
|
# ollie:end
|
|
|
|
import sys
|
|
import os
|
|
import subprocess
|
|
import shutil
|
|
|
|
sys.path.insert(0, os.environ.get('OLLIE_TOOLS_PATH', os.path.dirname(os.path.abspath(__file__))))
|
|
from _lib.args import parse_args
|
|
|
|
args = parse_args()
|
|
pattern = args.require("pattern")
|
|
search_path = args.get("path", os.getcwd())
|
|
mode = args.get("mode", "content")
|
|
glob_pat = args.get("glob")
|
|
type_filter = args.get("type")
|
|
case_insensitive = args.get_bool("case_insensitive")
|
|
after = args.get_int("after")
|
|
before = args.get_int("before")
|
|
context_lines = args.get_int("context")
|
|
multiline = args.get_bool("multiline")
|
|
head = args.get_int("head", 0)
|
|
offset = args.get_int("offset", 0)
|
|
|
|
use_rg = shutil.which('rg') is not None
|
|
|
|
if use_rg:
|
|
cmd = ['rg', '--no-config', '--color=never']
|
|
|
|
if mode == 'files_with_matches':
|
|
cmd.append('-l')
|
|
elif mode == 'count':
|
|
cmd.append('--count')
|
|
else:
|
|
cmd.append('-n')
|
|
|
|
if case_insensitive:
|
|
cmd.append('-i')
|
|
if multiline:
|
|
cmd += ['-U', '--multiline-dotall']
|
|
if glob_pat:
|
|
cmd += ['-g', glob_pat]
|
|
if type_filter:
|
|
cmd += ['-t', type_filter]
|
|
if context_lines is not None:
|
|
cmd += ['-C', str(context_lines)]
|
|
if after is not None:
|
|
cmd += ['-A', str(after)]
|
|
if before is not None:
|
|
cmd += ['-B', str(before)]
|
|
|
|
cmd += ['-e', pattern, search_path]
|
|
else:
|
|
cmd = ['grep', '-r', '--color=never']
|
|
|
|
if mode == 'files_with_matches':
|
|
cmd.append('-l')
|
|
elif mode == 'count':
|
|
cmd.append('-c')
|
|
else:
|
|
cmd.append('-n')
|
|
|
|
if case_insensitive:
|
|
cmd.append('-i')
|
|
if glob_pat:
|
|
cmd += ['--include=' + glob_pat]
|
|
if context_lines is not None:
|
|
cmd += ['-C', str(context_lines)]
|
|
if after is not None:
|
|
cmd += ['-A', str(after)]
|
|
if before is not None:
|
|
cmd += ['-B', str(before)]
|
|
|
|
cmd += ['-e', pattern, search_path]
|
|
|
|
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
if result.returncode == 1:
|
|
print("STATUS=no_matches MATCH_COUNT=0")
|
|
print("no matches")
|
|
sys.exit(0)
|
|
if result.returncode > 1:
|
|
print(f"STATUS=error")
|
|
print(f"error: {result.stderr.strip() or f'search exited with {result.returncode}'}")
|
|
sys.exit(1)
|
|
|
|
lines = result.stdout.splitlines()
|
|
total = len(lines)
|
|
|
|
if offset:
|
|
lines = lines[offset:]
|
|
|
|
max_matches = head if head else 50
|
|
capped = len(lines) > max_matches
|
|
if capped:
|
|
lines = lines[:max_matches]
|
|
|
|
if mode == 'content':
|
|
formatted = []
|
|
for line in lines:
|
|
if line == '--':
|
|
formatted.append(line)
|
|
continue
|
|
for sep in (':', '-'):
|
|
parts = line.split(sep, 2)
|
|
if len(parts) == 3 and parts[1].isdigit():
|
|
formatted.append(f"{parts[0]}:{parts[1]:>5}| {parts[2]}")
|
|
break
|
|
else:
|
|
formatted.append(line)
|
|
lines = formatted
|
|
|
|
print(f"STATUS=ok MATCH_COUNT={total}")
|
|
print('\n'.join(lines))
|
|
if capped:
|
|
print(f"\n(showing {max_matches} of {total} matches; use --head=N or --offset=N for more)")
|