Add CLI and services modules
This commit is contained in:
207
src/codexchange/services/conversion_service.py
Normal file
207
src/codexchange/services/conversion_service.py
Normal file
@@ -0,0 +1,207 @@
|
||||
"""Conversion service for CodeXchange CLI."""
|
||||
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from codexchange.models import (
|
||||
ConversionRequest,
|
||||
ConversionResult,
|
||||
Language,
|
||||
)
|
||||
from codexchange.services.ollama_service import OllamaService
|
||||
|
||||
|
||||
class ConversionService:
|
||||
"""Service for converting code between programming languages."""
|
||||
|
||||
MAX_RETRIES = 3
|
||||
|
||||
def __init__(self, ollama_service: Optional[OllamaService] = None):
|
||||
"""Initialize the conversion service.
|
||||
|
||||
Args:
|
||||
ollama_service: Optional OllamaService instance.
|
||||
"""
|
||||
self.ollama_service = ollama_service or OllamaService()
|
||||
|
||||
def build_conversion_prompt(
|
||||
self,
|
||||
source_code: str,
|
||||
source_language: Language,
|
||||
target_language: Language
|
||||
) -> str:
|
||||
"""Build a prompt for code conversion.
|
||||
|
||||
Args:
|
||||
source_code: Source code to convert.
|
||||
source_language: Source programming language.
|
||||
target_language: Target programming language.
|
||||
|
||||
Returns:
|
||||
Formatted prompt for the LLM.
|
||||
"""
|
||||
prompt = f"""You are an expert code converter. Convert the following code from {source_language.value} to {target_language.value}.
|
||||
|
||||
IMPORTANT REQUIREMENTS:
|
||||
1. Preserve ALL comments, docstrings, and inline documentation exactly as they are
|
||||
2. Maintain the original code style and formatting as much as possible
|
||||
3. Keep the same logic and functionality
|
||||
4. Use idiomatic patterns for the target language
|
||||
5. Do not add any explanations or comments beyond what was in the original code
|
||||
6. Return ONLY the converted code, no markdown formatting, no explanations
|
||||
|
||||
Source code:
|
||||
```{source_language.value}
|
||||
{source_code}
|
||||
```
|
||||
|
||||
Converted code:
|
||||
```{target_language.value}
|
||||
"""
|
||||
return prompt
|
||||
|
||||
def convert(self, request: ConversionRequest) -> ConversionResult:
|
||||
"""Convert code from one language to another.
|
||||
|
||||
Args:
|
||||
request: ConversionRequest with source code and language info.
|
||||
|
||||
Returns:
|
||||
ConversionResult with converted code or error.
|
||||
"""
|
||||
for attempt in range(self.MAX_RETRIES):
|
||||
try:
|
||||
prompt = self.build_conversion_prompt(
|
||||
request.source_code,
|
||||
request.source_language,
|
||||
request.target_language
|
||||
)
|
||||
|
||||
converted_code = self.ollama_service.generate(
|
||||
prompt=prompt,
|
||||
model=request.model
|
||||
)
|
||||
|
||||
converted_code = self._clean_conversion_output(converted_code)
|
||||
|
||||
return ConversionResult(
|
||||
success=True,
|
||||
converted_code=converted_code,
|
||||
original_code=request.source_code,
|
||||
source_language=request.source_language,
|
||||
target_language=request.target_language,
|
||||
model=request.model
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
if attempt == self.MAX_RETRIES - 1:
|
||||
return ConversionResult(
|
||||
success=False,
|
||||
original_code=request.source_code,
|
||||
source_language=request.source_language,
|
||||
target_language=request.target_language,
|
||||
model=request.model,
|
||||
error_message=f"Conversion failed after {self.MAX_RETRIES} attempts: {e}"
|
||||
)
|
||||
|
||||
return ConversionResult(
|
||||
success=False,
|
||||
original_code=request.source_code,
|
||||
source_language=request.source_language,
|
||||
target_language=request.target_language,
|
||||
model=request.model,
|
||||
error_message="Conversion failed: unknown error"
|
||||
)
|
||||
|
||||
def _clean_conversion_output(self, output: str) -> str:
|
||||
"""Clean up the conversion output.
|
||||
|
||||
Args:
|
||||
output: Raw output from LLM.
|
||||
|
||||
Returns:
|
||||
Cleaned code string.
|
||||
"""
|
||||
output = output.strip()
|
||||
|
||||
code_block_pattern = r"```(?:\w+)?\n([\s\S]*?)\n```"
|
||||
matches = re.findall(code_block_pattern, output)
|
||||
|
||||
if matches:
|
||||
output = matches[0]
|
||||
|
||||
output = output.strip()
|
||||
|
||||
return output
|
||||
|
||||
def count_comments(self, code: str) -> int:
|
||||
"""Count the number of comment lines in code.
|
||||
|
||||
Args:
|
||||
code: Source code.
|
||||
|
||||
Returns:
|
||||
Number of comment lines.
|
||||
"""
|
||||
lines = code.split("\n")
|
||||
comment_count = 0
|
||||
|
||||
in_multiline_comment = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
if in_multiline_comment:
|
||||
comment_count += 1
|
||||
if "*/" in stripped:
|
||||
in_multiline_comment = False
|
||||
stripped_after = stripped.split("*/", 1)[1].strip()
|
||||
if stripped_after and not stripped_after.startswith("//"):
|
||||
comment_count -= 1
|
||||
continue
|
||||
|
||||
if stripped.startswith("//"):
|
||||
comment_count += 1
|
||||
elif stripped.startswith("/*"):
|
||||
comment_count += 1
|
||||
if "*/" not in stripped:
|
||||
in_multiline_comment = True
|
||||
else:
|
||||
comment_content = stripped.split("/*")[1].split("*/")[0]
|
||||
if comment_content.strip():
|
||||
comment_count += 1
|
||||
elif stripped.startswith("#"):
|
||||
comment_count += 1
|
||||
elif stripped.startswith('"""') or stripped.startswith("'''"):
|
||||
if stripped.count('"""') == 2 or stripped.count("'''") == 2:
|
||||
comment_count += 1
|
||||
|
||||
return comment_count
|
||||
|
||||
def verify_comment_preservation(
|
||||
self,
|
||||
original_code: str,
|
||||
converted_code: str,
|
||||
tolerance: float = 0.1
|
||||
) -> bool:
|
||||
"""Verify that comments were preserved in conversion.
|
||||
|
||||
Args:
|
||||
original_code: Original source code.
|
||||
converted_code: Converted source code.
|
||||
tolerance: Allowed difference in comment count (10% by default).
|
||||
|
||||
Returns:
|
||||
True if comments appear to be preserved.
|
||||
"""
|
||||
original_comments = self.count_comments(original_code)
|
||||
converted_comments = self.count_comments(converted_code)
|
||||
|
||||
if original_comments == 0:
|
||||
return True
|
||||
|
||||
if converted_comments == 0:
|
||||
return False
|
||||
|
||||
ratio = abs(original_comments - converted_comments) / original_comments
|
||||
return ratio <= tolerance
|
||||
Reference in New Issue
Block a user