import ast from pathlib import Path class CodeOutliner: def __init__(self): pass def outline(self, code: str) -> str: code = code.lstrip(chr(0xFEFF)) try: tree = ast.parse(code) except SyntaxError as e: return f"ERROR parsing code: {e}" output = [] def get_docstring(node): doc = ast.get_docstring(node) if doc: return doc.splitlines()[0] return None def walk(node, indent=0): if isinstance(node, ast.ClassDef): start_line = node.lineno end_line = getattr(node, "end_lineno", start_line) output.append(f"{' ' * indent}[Class] {node.name} (Lines {start_line}-{end_line})") doc = get_docstring(node) if doc: output.append(f"{' ' * (indent + 1)}\"\"\"{doc}\"\"\"") for item in node.body: walk(item, indent + 1) elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): start_line = node.lineno end_line = getattr(node, "end_lineno", start_line) prefix = "[Async Func]" if isinstance(node, ast.AsyncFunctionDef) else "[Func]" # Check if it's a method # We can check the indent or the parent, but in AST walk we know if we are inside a ClassDef # Let's use a simpler heuristic for the outline: if indent > 0, it's likely a method. if indent > 0: prefix = "[Method]" output.append(f"{' ' * indent}{prefix} {node.name} (Lines {start_line}-{end_line})") doc = get_docstring(node) if doc: output.append(f"{' ' * (indent + 1)}\"\"\"{doc}\"\"\"") for node in tree.body: walk(node) return "\n".join(output) def get_outline(path: Path, code: str) -> str: suffix = path.suffix.lower() if suffix == ".py": outliner = CodeOutliner() return outliner.outline(code) else: return f"Outlining not supported for {suffix} files yet."