from __future__ import annotations
import os
import shlex
from fs_common import ORG, SKIP_DIRS, repo_dir
from fs_storage import walk_tree
LANG_EXTS = {
"rust": ["rs"], "python": ["py", "pyi"], "javascript": ["js", "jsx", "mjs"],
"typescript": ["ts", "tsx"], "go": ["go"], "c": ["c", "h"],
"c++": ["cpp", "cc", "hpp"], "java": ["java"], "ruby": ["rb"],
"shell": ["sh", "bash"], "bash": ["sh", "bash"], "toml": ["toml"],
"yaml": ["yaml", "yml"], "json": ["json"], "markdown": ["md"],
"html": ["html"], "css": ["css"],
}
def parse_query(q: str) -> dict:
try:
tokens = shlex.split(q)
except ValueError: tokens = shlex.split(q + '"')
parsed: dict = {
"terms": [], "negated": [], "repo": None, "org": None,
"ext": None, "lang": None, "neglang": None,
}
i = 0
while i < len(tokens):
tok = tokens[i]
neg = False
if tok == "NOT" and i + 1 < len(tokens):
neg = True
i += 1
tok = tokens[i]
elif tok.startswith("-"):
neg = True
tok = tok[1:]
if not tok:
pass
elif tok.startswith("repo:"):
parsed["repo"] = tok[5:]
elif tok.startswith("org:"):
parsed["org"] = tok[4:]
elif tok.startswith("extension:"):
parsed["ext"] = tok[10:].lstrip(".")
elif tok.startswith("language:"):
parsed["neglang" if neg else "lang"] = tok[9:].lower()
elif tok.startswith("path:"):
(parsed["negated"] if neg else parsed["terms"]).append(tok[5:])
else:
(parsed["negated"] if neg else parsed["terms"]).append(tok)
i += 1
return parsed
def file_in_scope(path: str, text: str, parsed: dict) -> list[str] | None:
low = text.lower()
needles = [t.lower() for t in parsed["terms"]]
matched = [n for n in needles if n in low]
if needles and not matched:
return None
path_low = path.lower()
for n in parsed["negated"]:
if n.lower() in low or n.lower() in path_low:
return None
ext = path.rsplit(".", 1)[-1].lower() if "." in path else ""
if parsed["lang"]:
exts = LANG_EXTS.get(parsed["lang"], [parsed["lang"]])
if ext not in exts:
return None
if parsed["neglang"]:
exts = LANG_EXTS.get(parsed["neglang"], [parsed["neglang"]])
if ext in exts:
return None
return matched
def search_code(root: str, q: str, limit: int | None) -> tuple[list[dict], bool]:
parsed = parse_query(q)
repo_scope = parsed["repo"]
ext = parsed["ext"]
repos = [f"{ORG}/{repo_scope.split('/', 1)[1]}"] if repo_scope else [
f"{ORG}/{d}" for d in sorted(os.listdir(root))
if os.path.isdir(os.path.join(root, d)) and d not in SKIP_DIRS
]
items = []
truncated = False
for repo in repos:
if truncated:
break
if not os.path.isdir(os.path.join(root, repo.split("/", 1)[1])):
continue
for entry in walk_tree(root, repo):
if entry["type"] != "blob":
continue
if ext and not entry["path"].lower().endswith("." + ext.lstrip(".")):
continue
full = os.path.join(repo_dir(root, repo), entry["path"])
try:
text = open(full, encoding="utf-8", errors="replace").read()
except OSError:
continue
if text.startswith("\x00") or "\x00" in text[:8192]:
continue matched = file_in_scope(entry["path"], text, parsed)
if matched is None:
continue
items.append(
{
"repo": repo,
"path": entry["path"],
"sha": entry["sha"],
"branch": "main",
"matches": matched,
}
)
if limit is not None and len(items) >= limit:
truncated = True
break
return items, truncated
def search_code_batches(
root: str, q: str, limit: int | None
) -> tuple[list[list[dict]], bool]:
parsed = parse_query(q)
repo_scope = parsed["repo"]
ext = parsed["ext"]
repos = [f"{ORG}/{repo_scope.split('/', 1)[1]}"] if repo_scope else [
f"{ORG}/{d}" for d in sorted(os.listdir(root))
if os.path.isdir(os.path.join(root, d)) and d not in SKIP_DIRS
]
batches: list[list[dict]] = []
sent = 0
truncated = False
for repo in repos:
if truncated:
break
if not os.path.isdir(os.path.join(root, repo.split("/", 1)[1])):
continue
batch = []
for entry in walk_tree(root, repo):
if entry["type"] != "blob":
continue
if ext and not entry["path"].lower().endswith("." + ext.lstrip(".")):
continue
full = os.path.join(repo_dir(root, repo), entry["path"])
try:
text = open(full, encoding="utf-8", errors="replace").read()
except OSError:
continue
if text.startswith("\x00") or "\x00" in text[:8192]:
continue matched = file_in_scope(entry["path"], text, parsed)
if matched is None:
continue
line = 1
if matched:
lowered = text.lower()
for n, ln in enumerate(lowered.splitlines(), start=1):
if matched[0] in ln:
line = n
break
batch.append(
{
"repo": repo,
"path": entry["path"],
"sha": entry["sha"],
"branch": "main",
"matches": matched,
"line": line,
}
)
if limit is not None and sent + len(batch) >= limit:
truncated = True
break
if batch:
batches.append(batch)
sent += len(batch)
return batches, truncated