Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions collagraph/sfc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from collagraph import Component

from .compiler import construct_ast
from .compiler import DEBUG, construct_ast, format_code


def load(path, namespace=None):
Expand Down Expand Up @@ -43,7 +43,19 @@ def load_from_string(template, path=None, namespace=None):
tree, name = construct_ast(path=path, template=template)

# Compile the tree into a code object (module)
code = compile(tree, filename=str(path), mode="exec")
# When CGX_DEBUG is set, write the AST to a temporary Python file
# and reparse it so that debuggers can step through the generated
# source with correct line numbers.
filename = str(path)
if DEBUG: # pragma: no cover
debug_file, source = _write_debug_file(tree, path)
if debug_file:
import ast

filename = str(debug_file)
tree = ast.parse(source, filename=filename)

code = compile(tree, filename=filename, mode="exec")
# Execute the code as module and pass a dictionary that will capture
# the global and local scope of the module
if namespace is None:
Expand All @@ -60,3 +72,45 @@ def load_from_string(template, path=None, namespace=None):
namespace["__component_class"] = component_class
namespace["__component_name"] = name
return component_class, namespace


def _write_debug_file(tree, path): # pragma: no cover
"""Write the compiled AST to a temporary Python file for debugging.

Returns a tuple of (path, formatted_source), or (None, None) if writing fails.
"""
import ast
import logging
import tempfile
from pathlib import Path

logger = logging.getLogger(__name__)

try:
plain_result = ast.unparse(tree)
formatted = format_code(plain_result)

# Create a meaningful filename based on the source .cgx file
source_name = Path(path).stem if path else "template"
debug_file = Path(tempfile.mktemp(prefix=f"cgx_{source_name}_", suffix=".py"))
debug_file.write_text(formatted, encoding="utf-8")
logger.debug("CGX debug file written to: %s", debug_file)

try:
from rich.console import Console
from rich.syntax import Syntax

console = Console()
syntax = Syntax(formatted, "python")
console.print(f"#---{path}---")
console.print(syntax)
console.print(f"[dim]Debug file: {debug_file}[/dim]")
except ImportError:
print(f"#---{path}---") # noqa: T201
print(formatted) # noqa: T201
print(f"Debug file: {debug_file}") # noqa: T201

return debug_file, formatted
except Exception as e:
logger.warning("Could not write AST debug file", exc_info=e)
return None, None
18 changes: 0 additions & 18 deletions collagraph/sfc/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,6 @@ class definition as `render` function.
# method to fix any `lineno` and `col_offset` attributes of the nodes
ast.fix_missing_locations(script_tree)

if DEBUG: # pragma: no cover
try:
_print_ast_tree_as_code(script_tree, path)
except Exception as e:
logger.warning("Could not unparse AST", exc_info=e)
return script_tree, component_def.name


Expand Down Expand Up @@ -1008,19 +1003,6 @@ def check_parsed_tree(node: Element):
)


def _print_ast_tree_as_code(tree, path): # pragma: no cover
"""Handy function for debugging an ast tree"""
from rich.console import Console
from rich.syntax import Syntax

plain_result = ast.unparse(tree)
formatted = format_code(plain_result)
console = Console()
syntax = Syntax(formatted, "python")
console.print(f"#---{path}---")
console.print(syntax)


def format_code(code): # pragma: no cover
"""
Format the given code string with ruff
Expand Down
Loading