Skip to content

Commit ff390df

Browse files
committed
Breakup interfaces, remove unnecessary defensiveness
1 parent 2a91d9a commit ff390df

13 files changed

Lines changed: 158 additions & 142 deletions

sync_ai_rules/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
from pathlib import Path
1010
from typing import Dict, List
1111

12-
from sync_ai_rules.core.interfaces import RuleMetadata
1312
from sync_ai_rules.core.plugin_manager import PluginManager
13+
from sync_ai_rules.core.rule_metadata import RuleMetadata
1414
from sync_ai_rules.file_updater import update_documentation_file
1515

1616

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Output generator interface.
4+
"""
5+
6+
from abc import ABC, abstractmethod
7+
from typing import Any, Dict, List
8+
9+
from sync_ai_rules.core.rule_metadata import RuleMetadata
10+
11+
12+
class OutputGenerator(ABC):
13+
"""Abstract base class for all output generators."""
14+
15+
@property
16+
@abstractmethod
17+
def name(self) -> str:
18+
"""Unique name for this generator."""
19+
20+
@property
21+
@abstractmethod
22+
def default_filenames(self) -> List[str]:
23+
"""Default output filenames."""
24+
25+
@abstractmethod
26+
def generate(self, rules: Dict[str, List[RuleMetadata]], config: Dict[str, Any]) -> str:
27+
"""Generate output content from grouped rules."""
28+
29+
@abstractmethod
30+
def get_section_markers(self) -> tuple[str, str]:
31+
"""Return start and end markers for auto-generated section."""

sync_ai_rules/core/interfaces.py

Lines changed: 0 additions & 94 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Input parser interface.
4+
"""
5+
6+
from abc import ABC, abstractmethod
7+
from typing import Any, Dict, List, Optional
8+
9+
from sync_ai_rules.core.rule_metadata import RuleMetadata
10+
11+
12+
class InputParser(ABC):
13+
"""Abstract base class for all input parsers."""
14+
15+
@property
16+
@abstractmethod
17+
def name(self) -> str:
18+
"""Unique name for this parser."""
19+
20+
@property
21+
@abstractmethod
22+
def supported_extensions(self) -> List[str]:
23+
"""File extensions this parser can handle."""
24+
25+
@property
26+
def source_directories(self) -> List[str]:
27+
"""
28+
Relative paths to directories this parser should scan.
29+
Override in subclass if parser is specific to certain directories.
30+
Returns empty list by default (scans all compatible files).
31+
"""
32+
return []
33+
34+
@abstractmethod
35+
def can_parse(self, file_path: str) -> bool:
36+
"""Check if this parser can handle the given file."""
37+
38+
@abstractmethod
39+
def parse(self, file_path: str, context: Dict[str, Any]) -> Optional[RuleMetadata]:
40+
"""Parse a file and return standardized metadata."""

sync_ai_rules/core/pipeline.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Pipeline data model for parser-generator pairs.
4+
"""
5+
6+
from dataclasses import dataclass
7+
from typing import TYPE_CHECKING
8+
9+
if TYPE_CHECKING:
10+
from sync_ai_rules.core.generator_interface import OutputGenerator
11+
from sync_ai_rules.core.parser_interface import InputParser
12+
13+
14+
@dataclass
15+
class Pipeline:
16+
"""Represents a parser-generator pipeline."""
17+
18+
name: str
19+
description: str
20+
parser: "InputParser"
21+
generator: "OutputGenerator"

sync_ai_rules/core/plugin_manager.py

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
import yaml
1111

12-
from .interfaces import InputParser, OutputGenerator, Pipeline
12+
from sync_ai_rules.core.generator_interface import OutputGenerator
13+
from sync_ai_rules.core.parser_interface import InputParser
14+
from sync_ai_rules.core.pipeline import Pipeline
1315

1416

1517
class PluginManager:
@@ -22,46 +24,33 @@ def load_plugins(self, base_path: str):
2224
"""Load all pipelines from plugins.yaml configuration file."""
2325
config_path = Path(base_path) / "plugins.yaml"
2426

25-
if not config_path.exists():
26-
print(f"✗ Plugin configuration not found: {config_path}")
27-
return
27+
with open(config_path) as f:
28+
config = yaml.safe_load(f)
2829

29-
try:
30-
with open(config_path) as f:
31-
config = yaml.safe_load(f)
32-
33-
# Load pipelines
34-
for pipeline_config in config.get("pipelines", []):
35-
pipeline = self._load_pipeline(base_path, pipeline_config)
36-
if pipeline:
37-
self.pipelines.append(pipeline)
38-
print(f"✓ Loaded pipeline: {pipeline.name} - {pipeline.description}")
39-
40-
except Exception as e:
41-
print(f"✗ Failed to load plugin configuration: {e}")
30+
# Load pipelines
31+
for pipeline_config in config.get("pipelines", []):
32+
pipeline = self._load_pipeline(base_path, pipeline_config)
33+
if pipeline:
34+
self.pipelines.append(pipeline)
35+
print(f"✓ Loaded pipeline: {pipeline.name} - {pipeline.description}")
4236

4337
def _load_pipeline(self, base_path: str, config: dict) -> Pipeline:
4438
"""Load a single parser-generator pipeline."""
45-
try:
46-
# Load parser
47-
parser_config = config["parser"]
48-
parser = self._load_parser(base_path, parser_config)
49-
50-
# Load generator
51-
generator_config = config["generator"]
52-
generator = self._load_generator(base_path, generator_config)
53-
54-
# Create pipeline
55-
return Pipeline(
56-
name=config["name"],
57-
description=config["description"],
58-
parser=parser,
59-
generator=generator,
60-
)
61-
62-
except Exception as e:
63-
print(f"✗ Failed to load pipeline {config.get('name', 'unknown')}: {e}")
64-
return None
39+
# Load parser
40+
parser_config = config["parser"]
41+
parser = self._load_parser(base_path, parser_config)
42+
43+
# Load generator
44+
generator_config = config["generator"]
45+
generator = self._load_generator(base_path, generator_config)
46+
47+
# Create pipeline
48+
return Pipeline(
49+
name=config["name"],
50+
description=config["description"],
51+
parser=parser,
52+
generator=generator,
53+
)
6554

6655
def _load_parser(self, base_path: str, config: dict) -> InputParser:
6756
"""Load a parser from configuration."""
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Rule metadata data model.
4+
"""
5+
6+
from dataclasses import dataclass
7+
from typing import Any, Dict, List
8+
9+
10+
@dataclass
11+
class RuleMetadata:
12+
"""Universal rule representation, format-agnostic."""
13+
14+
file_path: str
15+
relative_path: str
16+
title: str
17+
description: str
18+
scope_patterns: List[str]
19+
always_apply: bool
20+
category: str
21+
raw_content: str
22+
metadata: Dict[str, Any] = None
23+
24+
def __post_init__(self):
25+
if self.metadata is None:
26+
self.metadata = {}

sync_ai_rules/file_updater.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ def update_documentation_file(
7171
else:
7272
# No existing section, append to end
7373
if content and not content.endswith("\n"):
74-
content += "\n"
75-
if content:
74+
content += "\n\n"
75+
elif content:
7676
content += "\n"
7777
updated_content = content + new_section
7878
operation = "added"
@@ -90,5 +90,5 @@ def update_documentation_file(
9090

9191
return True, f"Successfully {operation} rules section in {file_path}"
9292

93-
except Exception as e:
93+
except (FileNotFoundError, PermissionError, UnicodeDecodeError, OSError) as e:
9494
return False, f"Failed to update {file_path}: {e}"

sync_ai_rules/generators/base_generator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from abc import abstractmethod
77
from typing import List
88

9-
from sync_ai_rules.core.interfaces import OutputGenerator, RuleMetadata
9+
from sync_ai_rules.core.generator_interface import OutputGenerator
10+
from sync_ai_rules.core.rule_metadata import RuleMetadata
1011

1112

1213
class BaseGenerator(OutputGenerator):

sync_ai_rules/generators/code_review_guidelines_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from typing import Any, Dict, List
77

8-
from sync_ai_rules.core.interfaces import RuleMetadata
8+
from sync_ai_rules.core.rule_metadata import RuleMetadata
99
from sync_ai_rules.generators.base_generator import BaseGenerator
1010

1111

0 commit comments

Comments
 (0)