Skip to content
Merged
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
14 changes: 10 additions & 4 deletions code2llm/parsers/toon_parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
#!/usr/bin/env python3
"""TOON format parser - extracted from validate_toon.py"""

from pathlib import Path
"""TOON format parser - re-exported from code2toon if installed."""

try:
from code2toon import (
parse_toon_content,
is_toon_file,
load_toon,
)
except ImportError:
from pathlib import Path


def _parse_header_line(line, data):
Expand Down
4 changes: 2 additions & 2 deletions docs/refactoring/modularization-and-rust-engine-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,6 @@ graph TD
- Implemented native call graph resolution with $O(1)$ candidate lookups and module affinity matching.

4. **Phase 4 (In Progress - Ecosystem Modularization)**:
- **`code2graph` (Complete)**: Extracted graph data models (`AnalysisResult`, `FunctionInfo`, `FlowNode`, `FlowEdge`, `GraphQuery`) into standalone package at `packages/code2graph` and integrated into `code2llm/core/models.py`.
- **`code2toon` (Next)**: Extract TOON format specification, serializer, and validator into standalone package.
- **`code2graph` (Complete)**: Extracted graph data models (`AnalysisResult`, `FunctionInfo`, `FlowNode`, `FlowEdge`, `GraphQuery`) into standalone package at `packages/code2graph` and published to `https://github.com/semcod/code2graph`.
- **`code2toon` (Complete)**: Extracted TOON format specification, parser, validator, and diff engine into standalone package at `packages/code2toon`.
- **`code2flow`**: Extract visual diagrams and heavy rendering dependencies into standalone package.
14 changes: 14 additions & 0 deletions packages/code2toon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# code2toon

TOON (Topology Oriented Object Notation) specification, parser, validator, and diff engine for static code analysis.

## Features
- **Fast TOON parser**: Parses plain-text and YAML variants of TOON (`analysis.toon.yaml`, `map.toon.yaml`, `evolution.toon.yaml`).
- **Strict schema validation**: Ensures conformance to structural metrics, health markers, and module topology.
- **Architectural diff**: Compares two TOON outputs to detect architectural drift, new cycles, complexity regressions, and duplicate types.
- **Native Acceleration Ready**: Integrates with `code2llm-rust` for sub-millisecond header formatting and metric computation.

## Installation
```bash
pip install code2toon
```
28 changes: 28 additions & 0 deletions packages/code2toon/code2toon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""code2toon: TOON (Topology Oriented Object Notation) specification and tooling."""

from .models import ToonDocument, ToonHeader, FunctionEntry
from .parser import (
is_toon_file,
parse_toon_content,
load_toon,
load_toon_document,
)
from .validator import ToonValidator, validate_toon
from .diff import ToonDiffResult, diff_toon

__version__ = "0.1.0"

__all__ = [
"ToonDocument",
"ToonHeader",
"FunctionEntry",
"is_toon_file",
"parse_toon_content",
"load_toon",
"load_toon_document",
"ToonValidator",
"validate_toon",
"ToonDiffResult",
"diff_toon",
"__version__",
]
60 changes: 60 additions & 0 deletions packages/code2toon/code2toon/diff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Diffing engine for TOON documents to detect architectural drift."""
from dataclasses import dataclass, field
from typing import List, Dict, Any, Union
from pathlib import Path

from .parser import load_toon_document
from .models import ToonDocument


@dataclass
class ToonDiffResult:
"""Result of diffing two TOON documents."""
cc_delta: float = 0.0
added_functions: List[str] = field(default_factory=list)
removed_functions: List[str] = field(default_factory=list)
added_classes: List[str] = field(default_factory=list)
removed_classes: List[str] = field(default_factory=list)
added_health_issues: List[str] = field(default_factory=list)
resolved_health_issues: List[str] = field(default_factory=list)

@property
def has_drift(self) -> bool:
"""Check if any architectural drift occurred."""
return (
abs(self.cc_delta) > 0.01
or bool(self.added_functions)
or bool(self.removed_functions)
or bool(self.added_classes)
or bool(self.removed_classes)
or bool(self.added_health_issues)
)


def diff_toon(
base: Union[str, Path, ToonDocument],
head: Union[str, Path, ToonDocument],
) -> ToonDiffResult:
"""Compare base and head TOON documents to detect architectural drift."""
doc_base = load_toon_document(base) if not isinstance(base, ToonDocument) else base
doc_head = load_toon_document(head) if not isinstance(head, ToonDocument) else head

diff = ToonDiffResult()
diff.cc_delta = round(doc_head.header.mean_cc - doc_base.header.mean_cc, 2)

base_funcs = {f.name for f in doc_base.functions}
head_funcs = {f.name for f in doc_head.functions}
diff.added_functions = sorted(list(head_funcs - base_funcs))
diff.removed_functions = sorted(list(base_funcs - head_funcs))

base_classes = set(doc_base.classes)
head_classes = set(doc_head.classes)
diff.added_classes = sorted(list(head_classes - base_classes))
diff.removed_classes = sorted(list(base_classes - head_classes))

base_health = set(doc_base.health)
head_health = set(doc_head.health)
diff.added_health_issues = sorted(list(head_health - base_health))
diff.resolved_health_issues = sorted(list(base_health - head_health))

return diff
34 changes: 34 additions & 0 deletions packages/code2toon/code2toon/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Data models for TOON documents."""
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any


@dataclass
class ToonHeader:
"""Header information for a TOON document."""
project: str = ""
generated_at: str = ""
mean_cc: float = 0.0
critical_count: int = 0
duplicate_count: int = 0
cycle_count: int = 0


@dataclass
class FunctionEntry:
"""Function metrics entry in TOON."""
name: str
cc: float
file: Optional[str] = None
line: Optional[int] = None


@dataclass
class ToonDocument:
"""Structured representation of a parsed TOON document."""
header: ToonHeader = field(default_factory=ToonHeader)
health: List[str] = field(default_factory=list)
functions: List[FunctionEntry] = field(default_factory=list)
classes: List[str] = field(default_factory=list)
hotspots: List[str] = field(default_factory=list)
raw_data: Dict[str, Any] = field(default_factory=dict)
153 changes: 153 additions & 0 deletions packages/code2toon/code2toon/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"""TOON format parser for plain-text and YAML variants."""
import re
from pathlib import Path
from typing import Dict, Any, Union, List
import yaml

from .models import ToonDocument, ToonHeader, FunctionEntry


def is_toon_file(filepath: Union[str, Path]) -> bool:
"""Check if file is TOON format based on extension or content."""
path = Path(filepath)
if path.suffix in [".toon", ".toon.yaml"] or ".toon." in path.name:
return True
try:
with open(path, "r", encoding="utf-8") as f:
first_line = f.readline()
return first_line.startswith("# ") and (
"func" in first_line or "CC" in first_line or "code2llm" in first_line
)
except Exception:
return False


def parse_toon_content(content: str) -> Dict[str, Any]:
"""Parse TOON plain-text content into a structured dictionary."""
data: Dict[str, Any] = {
"meta": {},
"stats": {},
"health": [],
"functions": [],
"classes": [],
"hotspots": [],
}

current_section = None
lines = content.splitlines()

for line in lines:
stripped = line.strip()
if not stripped:
continue

if line.startswith("# "):
if "CC̄" in line or "CC=" in line or "critical:" in line:
_parse_stats_line(line, data)
elif not data["meta"].get("project"):
_parse_header_line(line, data)
continue

if re.match(r"^[A-Z_]+(\[\d*\])?:", stripped):
sec_name = stripped.split("[")[0].split(":")[0].lower()
current_section = sec_name
continue

if current_section == "health":
if any(marker in stripped for marker in ("🔴", "🟡", "!", "critical", "warning")):
data["health"].append(stripped)
elif current_section == "functions":
if not stripped.startswith("summary:"):
parts = stripped.split()
if len(parts) >= 2:
try:
cc = float(parts[0])
data["functions"].append({"name": parts[1], "cc": cc})
except ValueError:
pass
elif current_section == "classes":
parts = stripped.split()
if parts and not parts[0].startswith("█"):
data["classes"].append({"name": parts[0]})
elif current_section == "hotspots":
if stripped.startswith("#"):
parts = stripped.split()
if len(parts) >= 2:
data["hotspots"].append({"name": parts[1]})

return data


def _parse_header_line(line: str, data: Dict[str, Any]) -> None:
parts = line[2:].strip().split("|")
if parts:
data["meta"]["project"] = parts[0].strip()
if len(parts) > 1:
data["meta"]["generated"] = parts[-1].strip()


def _parse_stats_line(line: str, data: Dict[str, Any]) -> None:
parts = line[2:].strip().split("|")
for part in parts:
part = part.strip()
if "critical:" in part:
data["stats"]["critical"] = part.split(":")[-1].strip()
elif "dups:" in part:
data["stats"]["duplicates"] = part.split(":")[-1].strip()
elif "cycles:" in part:
data["stats"]["cycles"] = part.split(":")[-1].strip()
elif "CC̄=" in part or "CC=" in part:
data["stats"]["mean_cc"] = part.split("=")[-1].strip()


def load_toon(filepath: Union[str, Path]) -> Dict[str, Any]:
"""Load and parse a TOON file (auto-detecting YAML vs plain-text format)."""
path = Path(filepath)
content = path.read_text(encoding="utf-8")

if path.name.endswith(".yaml") or path.name.endswith(".yml"):
try:
yaml_data = yaml.safe_load(content)
if isinstance(yaml_data, dict):
return yaml_data
except Exception:
pass

return parse_toon_content(content)


def load_toon_document(filepath: Union[str, Path]) -> ToonDocument:
"""Load a TOON file and return a strongly-typed ToonDocument."""
raw = load_toon(filepath)
doc = ToonDocument(raw_data=raw)

stats = raw.get("stats", {})
doc.header.project = raw.get("meta", {}).get("project", "")
try:
doc.header.mean_cc = float(stats.get("mean_cc", 0.0))
doc.header.critical_count = int(stats.get("critical", 0))
doc.header.duplicate_count = int(stats.get("duplicates", 0))
doc.header.cycle_count = int(stats.get("cycles", 0))
except (ValueError, TypeError):
pass

doc.health = list(raw.get("health", []))
for f in raw.get("functions", []):
if isinstance(f, dict):
doc.functions.append(FunctionEntry(name=f.get("name", ""), cc=float(f.get("cc", 0.0))))
elif isinstance(f, str):
doc.functions.append(FunctionEntry(name=f, cc=0.0))

for c in raw.get("classes", []):
if isinstance(c, dict):
doc.classes.append(c.get("name", ""))
elif isinstance(c, str):
doc.classes.append(c)

for h in raw.get("hotspots", []):
if isinstance(h, dict):
doc.hotspots.append(h.get("name", ""))
elif isinstance(h, str):
doc.hotspots.append(h)

return doc
37 changes: 37 additions & 0 deletions packages/code2toon/code2toon/validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Validation logic for TOON documents."""
from typing import Dict, Any, List, Tuple


class ToonValidator:
"""Validates structure and completeness of TOON data."""

REQUIRED_METRICS = ["critical", "duplicates", "cycles"]

def validate(self, toon_data: Dict[str, Any]) -> Tuple[bool, List[str]]:
"""Validate TOON dictionary structure.

Returns:
Tuple of (is_valid, list_of_errors)
"""
errors = []

if not isinstance(toon_data, dict):
return False, ["TOON data must be a dictionary"]

# Check meta/project
meta = toon_data.get("meta", {})
if not meta.get("project") and not toon_data.get("project"):
errors.append("Missing project name in TOON header/metadata")

# Validate sections if present
for section in ("functions", "classes", "health", "hotspots"):
val = toon_data.get(section)
if val is not None and not isinstance(val, list):
errors.append(f"Section '{section}' must be a list")

return len(errors) == 0, errors


def validate_toon(toon_data: Dict[str, Any]) -> Tuple[bool, List[str]]:
"""Convenience function to validate TOON dictionary."""
return ToonValidator().validate(toon_data)
28 changes: 28 additions & 0 deletions packages/code2toon/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "code2toon"
version = "0.1.0"
description = "TOON (Topology Oriented Object Notation) parser, validator, and schema engine"
readme = "README.md"
requires-python = ">=3.9"
license = {text = "Apache-2.0"}
authors = [
{name = "Tom Sapletta", email = "tom@sapletta.com"}
]
keywords = ["toon", "code-analysis", "static-analysis", "architecture", "topology"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"pyyaml>=5.4",
]

[tool.setuptools.packages.find]
where = ["."]
include = ["code2toon*"]
Loading
Loading