Add CLI and services modules
Some checks failed
CI / test (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled

This commit is contained in:
2026-01-30 18:58:57 +00:00
parent fb141a4cb8
commit aec6f61927

View 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