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
111 changes: 57 additions & 54 deletions src/plugins/cpp_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re
from collections import defaultdict
import subprocess
import tempfile
import yaml
from typing import List, Tuple, Dict, Set, Any
from base_interfaces import BasePreprocessor, BaseMinifier
Expand Down Expand Up @@ -77,61 +78,64 @@ def preprocess(self, file_path: str, include_paths: List[str], defines: Dict[str
source_content = include_regex.sub("", source_content)
combined_content += source_content

temp_file_path: str = "temp_combined.cpp"
with open(temp_file_path, "w") as f:
f.write(combined_content)

# First clang-format pass for initial formatting
_run_clang_format(['clang-format', '-i', temp_file_path])

with open(temp_file_path, "r") as f:
processed_content: str = f.read()

# Inject tunable parameters (before removing comments, as we need // @tune markers)
if defines:
processed_content = self._inject_tunable_params(processed_content, defines)

# Remove comments using regex
processed_content = re.sub(r'//.*\n', '\n', processed_content) # Single-line comments
processed_content = re.sub(r'/\*.*?\*/', '', processed_content, flags=re.DOTALL) # Multi-line comments

# Check for prepkit_config.yaml for minification setting
config_file_path: str = os.path.join(os.getcwd(), "prepkit_config.yaml")
minify_output: bool = False
if os.path.exists(config_file_path):
try:
with open(config_file_path, 'r') as f:
config: Dict[str, Any] = yaml.safe_load(f)
minify_output = config.get("cpp_preprocess", {}).get("minify_output", False)
except yaml.YAMLError as e:
click.echo(f"Warning: Error reading prepkit_config.yaml: {e}. Using default settings.", err=True)

if minify_output:
# Aggressive clang-format style for minification
minify_style: str = "{IndentWidth: 0, BreakBeforeBraces: Attach, SpaceAfterCStyleCast: false, SpacesInParentheses: false, CompactNamespaces: true, AllowShortBlocksOnASingleLine: Always, AllowShortFunctionsOnASingleLine: All}"

# Final clang-format pass with minification style
with open(temp_file_path, "w") as f:
f.write(processed_content)

_run_clang_format(['clang-format', '-i', '-style=' + minify_style, temp_file_path])

with open(temp_file_path, "r") as f:
minified_output: str = f.read()
minified_output = re.sub(r'\s+', '', minified_output) # Remove all whitespace
minified_output = re.sub(r'\n', '', minified_output) # Remove all newlines
final_output: str = minified_output
else:
# Final clang-format pass with default style
with open(temp_file_path, "w") as f:
f.write(processed_content)
with tempfile.NamedTemporaryFile(mode="w", suffix=".cpp", delete=False) as temp_file:
temp_file.write(combined_content)
temp_file_path: str = temp_file.name

try:
# First clang-format pass for initial formatting
_run_clang_format(['clang-format', '-i', temp_file_path])

with open(temp_file_path, "r") as f:
final_output: str = f.read()
processed_content: str = f.read()

# Inject tunable parameters (before removing comments, as we need // @tune markers)
if defines:
processed_content = self._inject_tunable_params(processed_content, defines)

# Remove comments using regex
processed_content = re.sub(r'//.*\n', '\n', processed_content) # Single-line comments
processed_content = re.sub(r'/\*.*?\*/', '', processed_content, flags=re.DOTALL) # Multi-line comments

# Check for prepkit_config.yaml for minification setting
config_file_path: str = os.path.join(os.getcwd(), "prepkit_config.yaml")
minify_output: bool = False
if os.path.exists(config_file_path):
try:
with open(config_file_path, 'r') as f:
config: Dict[str, Any] = yaml.safe_load(f)
minify_output = config.get("cpp_preprocess", {}).get("minify_output", False)
except yaml.YAMLError as e:
click.echo(f"Warning: Error reading prepkit_config.yaml: {e}. Using default settings.", err=True)

if minify_output:
# Aggressive clang-format style for minification
minify_style: str = "{IndentWidth: 0, BreakBeforeBraces: Attach, SpaceAfterCStyleCast: false, SpacesInParentheses: false, CompactNamespaces: true, AllowShortBlocksOnASingleLine: Always, AllowShortFunctionsOnASingleLine: All}"

# Final clang-format pass with minification style
with open(temp_file_path, "w") as f:
f.write(processed_content)

_run_clang_format(['clang-format', '-i', '-style=' + minify_style, temp_file_path])

with open(temp_file_path, "r") as f:
minified_output: str = f.read()
minified_output = re.sub(r'\s+', '', minified_output) # Remove all whitespace
minified_output = re.sub(r'\n', '', minified_output) # Remove all newlines
final_output: str = minified_output
else:
# Final clang-format pass with default style
with open(temp_file_path, "w") as f:
f.write(processed_content)

_run_clang_format(['clang-format', '-i', temp_file_path])

with open(temp_file_path, "r") as f:
final_output: str = f.read()
finally:
if os.path.exists(temp_file_path):
os.remove(temp_file_path)

os.remove(temp_file_path)
return final_output
else:
# Report circular dependency error
Expand Down Expand Up @@ -214,10 +218,9 @@ def minify(self, file_path: str) -> str:
with open(file_path, 'r') as f:
content = f.read()

# Create temporary file
temp_file_path = "temp_minify.cpp"
with open(temp_file_path, "w") as f:
f.write(content)
with tempfile.NamedTemporaryFile(mode="w", suffix=".cpp", delete=False) as temp_file:
temp_file.write(content)
temp_file_path = temp_file.name

try:
# Use clang-format with aggressive minification style
Expand Down
12 changes: 8 additions & 4 deletions src/plugins/rust_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import os
import re
import subprocess
import tempfile
from typing import List, Dict, Optional
from base_interfaces import BasePreprocessor, BaseMinifier
from preprocessing_utils import StringLiteralProtector, report_circular_dependency_error
Expand Down Expand Up @@ -169,10 +170,11 @@ def _expand_mod_declarations(

def _format_with_rustfmt(self, content: str) -> str:
"""Format Rust source with rustfmt if it is available, else return as-is."""
temp_file_path: Optional[str] = None
try:
temp_file_path: str = "temp_combined.rs"
with open(temp_file_path, "w") as f:
f.write(content)
with tempfile.NamedTemporaryFile(mode="w", suffix=".rs", delete=False) as temp_file:
temp_file.write(content)
temp_file_path = temp_file.name

result = subprocess.run(
['rustfmt', temp_file_path],
Expand All @@ -190,12 +192,14 @@ def _format_with_rustfmt(self, content: str) -> str:
message = f"{message}\n{details}"
click.echo(message, err=True)

os.remove(temp_file_path)
except FileNotFoundError:
# rustfmt not available, use unformatted output
click.echo("Warning: rustfmt not found. Using unformatted output.", err=True)
except subprocess.SubprocessError as e:
click.echo(f"Warning: rustfmt failed: {e}. Using unformatted output.", err=True)
finally:
if temp_file_path and os.path.exists(temp_file_path):
os.remove(temp_file_path)

return content

Expand Down
16 changes: 16 additions & 0 deletions tests/test_cpp_preprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ def test_cpp_preprocess_includes(cpp_preprocessor, temp_files):
assert "#include \"header.hpp\"" not in output # Ensure local include is removed
assert "#include \"constants.hpp\"" not in output # Ensure local include is removed

def test_cpp_preprocess_does_not_create_fixed_temp_file(cpp_preprocessor, temp_files, monkeypatch):
monkeypatch.chdir(temp_files)

main_file = temp_files / "main.cpp"
cpp_preprocessor.preprocess(str(main_file), [str(temp_files)])

assert not (temp_files / "temp_combined.cpp").exists()

def test_cpp_preprocess_comments(cpp_preprocessor, temp_files):
comments_file = temp_files / "comments_test.cpp"
output = cpp_preprocessor.preprocess(str(comments_file), [])
Expand All @@ -63,6 +71,14 @@ def test_cpp_minify(cpp_minifier, temp_files):
assert "*/" not in output # Multi-line comments removed
# Note: Some whitespace and newlines are preserved for compilation compatibility

def test_cpp_minify_does_not_create_fixed_temp_file(cpp_minifier, temp_files, monkeypatch):
monkeypatch.chdir(temp_files)

minify_file = temp_files / "minify_test.cpp"
cpp_minifier.minify(str(minify_file))

assert not (temp_files / "temp_minify.cpp").exists()

def test_cpp_tunable_params_single(cpp_preprocessor, temp_files):
"""Test single tunable parameter injection."""
tune_file = temp_files / "tune_single.cpp"
Expand Down
2 changes: 2 additions & 0 deletions tests/test_error_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ def test_missing_include_hint_message(self, tmp_path, capsys):

def test_cpp_formatter_failure_is_reported(self, tmp_path, monkeypatch):
"""Test that clang-format failures produce a clear error."""
monkeypatch.chdir(tmp_path)
main_cpp = tmp_path / "main.cpp"
main_cpp.write_text("int main(){return 0;}\n")

Expand All @@ -184,6 +185,7 @@ def fake_run(*args, **kwargs):
message = str(exc_info.value)
assert "clang-format failed with exit code 1" in message
assert "bad style" in message
assert not (tmp_path / "temp_combined.cpp").exists()

def test_rustfmt_failure_warns_and_returns_unformatted_output(self, monkeypatch, capsys):
"""Test that optional rustfmt failures are visible but non-fatal."""
Expand Down
4 changes: 3 additions & 1 deletion tests/test_rust_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ def test_empty_file(self, tmp_path):
# Should not crash, just return empty content
assert result == "\n" or result == ""

def test_single_file_no_modules(self, tmp_path):
def test_single_file_no_modules(self, tmp_path, monkeypatch):
"""Test preprocessing a single file with no mod declarations."""
monkeypatch.chdir(tmp_path)
main_rs = tmp_path / "main.rs"
main_rs.write_text("""
fn main() {
Expand All @@ -87,6 +88,7 @@ def test_single_file_no_modules(self, tmp_path):

assert "fn main()" in result
assert 'println!("Hello, World!")' in result
assert not (tmp_path / "temp_combined.rs").exists()

def test_include_paths_resolution(self, tmp_path):
"""Test module resolution using include_paths parameter."""
Expand Down
Loading