.pydev-tools / _ast / describe.py
.pydev-tools / _ast / describe.py
#!/usr/bin/env python3
"""Produce a high-level project overview (README, dependencies, top imports).
Emits JSON with an overview summary, dependency list and per-file top imports.
No full-file reading required — instant repo context for the LLM.
Usage: describe.py <workspace_root> [output_dir]
"""
import ast, json, os, re, sys
def read_text(path):
try:
return open(path, encoding="utf-8").read()
except OSError:
return ""
def count_lines(path):
try:
return len(read_text(path).split("\n"))
except OSError:
return 0
def top_imports(text, limit=10):
seen = {}
for raw in text.split("\n"):
m = re.match(r"^\s*import\s+([\w.]+)", raw)
if m:
mod = m.group(1).split(".")[0]
seen[mod] = seen.get(mod, 0) + 1
m = re.match(r"^\s*from\s+([\w.]+)\s+import", raw)
if m:
mod = m.group(1).split(".")[0]
seen[mod] = seen.get(mod, 0) + 1
return sorted(seen.items(), key=lambda kv: (-kv[1], kv[0]))[:limit]
def parse_deps():
deps = []
req = "requirements.txt"
text = read_text(req)
for line in text.split("\n"):
line = line.strip()
if not line or line.startswith("#") or line.startswith("-"):
continue
m = re.match(r"^([A-Za-z0-9_.\-]+)\s*==\s*(.+)", line)
if m:
deps.append({"name": m.group(1), "version": m.group(2)})
return deps
def main():
root = sys.argv[1].strip()
out_dir = sys.argv[2].strip() if len(sys.argv) > 2 else "."
readme = read_text(os.path.join(root, "README.md"))
overview = {
"fileCount": sum(1 for _, _, ns in os.walk(root) if ns),
"lineCount": sum(count_lines(os.path.join(r, fn)) for r, _d, fs in os.walk(root) for fn in fs if fn.endswith(".py")),
"readmePreview": readme.strip().split("\n")[:6],
}
deps = parse_deps()
import_map = {}
py_files = []
for dirpath, _dirs, files in os.walk(root):
if ".venv" in dirpath.split(os.sep) or "site-packages" in dirpath.split(os.sep):
continue
for fn in files:
if fn.endswith(".py"):
py_files.append(os.path.join(dirpath, fn))
for f in sorted(py_files)[:20]:
rel = os.path.relpath(f, root)
try:
imports = top_imports(read_text(f))
except OSError:
continue
if imports:
import_map[rel] = {i[0]: i[1] for i in imports}
out = {"overview": overview, "dependencies": deps, "topImports": import_map}
md_path = os.path.join(out_dir, "workspace_description.md")
try:
lines = ["# Workspace Description", "", "## Overview", ""]
lines.append(f"- Files: {overview['fileCount']}, Python LOC: {overview['lineCount']}")
for line in overview["readmePreview"]:
lines.append(f"- {line}")
lines += ["", "## Dependencies", ""]
if deps:
for d in deps:
lines.append(f"- {d['name']}=={d['version']}")
else:
lines.append("- None (requirements.txt not found or empty)")
lines += ["", "## Top Imports (by file)", ""]
if import_map:
for rel, imps in sorted(import_map.items()):
lines.append(f"- `{rel}`: {', '.join(f'{k}×{v}' for k, v in imps.items())}")
else:
lines.append("- No Python files found.")
with open(md_path, "w", encoding="utf-8") as fh:
fh.write("\n".join(lines))
out["descriptionPath"] = md_path
except OSError:
pass
json.dump(out, sys.stdout, indent=2)
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""Produce a high-level project overview (README, dependencies, top imports).
Emits JSON with an overview summary, dependency list and per-file top imports.
No full-file reading required — instant repo context for the LLM.
Usage: describe.py <workspace_root> [output_dir]
"""
import ast, json, os, re, sys
def read_text(path):
try:
return open(path, encoding="utf-8").read()
except OSError:
return ""
def count_lines(path):
try:
return len(read_text(path).split("\n"))
except OSError:
return 0
def top_imports(text, limit=10):
seen = {}
for raw in text.split("\n"):
m = re.match(r"^\s*import\s+([\w.]+)", raw)
if m:
mod = m.group(1).split(".")[0]
seen[mod] = seen.get(mod, 0) + 1
m = re.match(r"^\s*from\s+([\w.]+)\s+import", raw)
if m:
mod = m.group(1).split(".")[0]
seen[mod] = seen.get(mod, 0) + 1
return sorted(seen.items(), key=lambda kv: (-kv[1], kv[0]))[:limit]
def parse_deps():
deps = []
req = "requirements.txt"
text = read_text(req)
for line in text.split("\n"):
line = line.strip()
if not line or line.startswith("#") or line.startswith("-"):
continue
m = re.match(r"^([A-Za-z0-9_.\-]+)\s*==\s*(.+)", line)
if m:
deps.append({"name": m.group(1), "version": m.group(2)})
return deps
def main():
root = sys.argv[1].strip()
out_dir = sys.argv[2].strip() if len(sys.argv) > 2 else "."
readme = read_text(os.path.join(root, "README.md"))
overview = {
"fileCount": sum(1 for _, _, ns in os.walk(root) if ns),
"lineCount": sum(count_lines(os.path.join(r, fn)) for r, _d, fs in os.walk(root) for fn in fs if fn.endswith(".py")),
"readmePreview": readme.strip().split("\n")[:6],
}
deps = parse_deps()
import_map = {}
py_files = []
for dirpath, _dirs, files in os.walk(root):
if ".venv" in dirpath.split(os.sep) or "site-packages" in dirpath.split(os.sep):
continue
for fn in files:
if fn.endswith(".py"):
py_files.append(os.path.join(dirpath, fn))
for f in sorted(py_files)[:20]:
rel = os.path.relpath(f, root)
try:
imports = top_imports(read_text(f))
except OSError:
continue
if imports:
import_map[rel] = {i[0]: i[1] for i in imports}
out = {"overview": overview, "dependencies": deps, "topImports": import_map}
md_path = os.path.join(out_dir, "workspace_description.md")
try:
lines = ["# Workspace Description", "", "## Overview", ""]
lines.append(f"- Files: {overview['fileCount']}, Python LOC: {overview['lineCount']}")
for line in overview["readmePreview"]:
lines.append(f"- {line}")
lines += ["", "## Dependencies", ""]
if deps:
for d in deps:
lines.append(f"- {d['name']}=={d['version']}")
else:
lines.append("- None (requirements.txt not found or empty)")
lines += ["", "## Top Imports (by file)", ""]
if import_map:
for rel, imps in sorted(import_map.items()):
lines.append(f"- `{rel}`: {', '.join(f'{k}×{v}' for k, v in imps.items())}")
else:
lines.append("- No Python files found.")
with open(md_path, "w", encoding="utf-8") as fh:
fh.write("\n".join(lines))
out["descriptionPath"] = md_path
except OSError:
pass
json.dump(out, sys.stdout, indent=2)
if __name__ == "__main__":
main()