Skip to content

Commit 81daee4

Browse files
authored
Merge pull request #15 from finecode-dev/feature/ruff-format
Use ruff in format preset instead of black and isort
2 parents 18acbb6 + ef8833e commit 81daee4

File tree

17 files changed

+263
-481
lines changed

17 files changed

+263
-481
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# fine_python_ruff
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from .format_handler import RuffFormatHandler, RuffFormatHandlerConfig
2+
3+
__all__ = [
4+
"RuffFormatHandler",
5+
"RuffFormatHandlerConfig"
6+
]
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
from __future__ import annotations
2+
3+
import dataclasses
4+
import sys
5+
from pathlib import Path
6+
7+
if sys.version_info < (3, 12):
8+
from typing_extensions import override
9+
else:
10+
from typing import override
11+
12+
from finecode_extension_api import code_action
13+
from finecode_extension_api.actions import format as format_action
14+
from finecode_extension_api.interfaces import icache, icommandrunner, ilogger
15+
16+
17+
@dataclasses.dataclass
18+
class RuffFormatHandlerConfig(code_action.ActionHandlerConfig):
19+
line_length: int = 88
20+
indent_width: int = 4
21+
quote_style: str = "double" # "double" or "single"
22+
target_version: str = "py38" # minimum Python version
23+
preview: bool = False
24+
25+
26+
class RuffFormatHandler(
27+
code_action.ActionHandler[format_action.FormatAction, RuffFormatHandlerConfig]
28+
):
29+
CACHE_KEY = "RuffFormatter"
30+
31+
def __init__(
32+
self,
33+
config: RuffFormatHandlerConfig,
34+
context: code_action.ActionContext,
35+
logger: ilogger.ILogger,
36+
cache: icache.ICache,
37+
command_runner: icommandrunner.ICommandRunner,
38+
) -> None:
39+
self.config = config
40+
self.context = context
41+
self.logger = logger
42+
self.cache = cache
43+
self.command_runner = command_runner
44+
45+
self.ruff_bin_path = Path(sys.executable).parent / "ruff"
46+
47+
@override
48+
async def run(
49+
self,
50+
payload: format_action.FormatRunPayload,
51+
run_context: format_action.FormatRunContext,
52+
) -> format_action.FormatRunResult:
53+
result_by_file_path: dict[Path, format_action.FormatRunFileResult] = {}
54+
for file_path in payload.file_paths:
55+
file_content, file_version = run_context.file_info_by_path[file_path]
56+
try:
57+
new_file_content = await self.cache.get_file_cache(
58+
file_path, self.CACHE_KEY
59+
)
60+
result_by_file_path[file_path] = format_action.FormatRunFileResult(
61+
changed=False, code=new_file_content
62+
)
63+
continue
64+
except icache.CacheMissException:
65+
pass
66+
67+
new_file_content, file_changed = await self.format_one(
68+
file_path, file_content
69+
)
70+
71+
# save for next handlers
72+
run_context.file_info_by_path[file_path] = format_action.FileInfo(
73+
new_file_content, file_version
74+
)
75+
76+
await self.cache.save_file_cache(
77+
file_path, file_version, self.CACHE_KEY, new_file_content
78+
)
79+
result_by_file_path[file_path] = format_action.FormatRunFileResult(
80+
changed=file_changed, code=new_file_content
81+
)
82+
83+
return format_action.FormatRunResult(result_by_file_path=result_by_file_path)
84+
85+
async def format_one(self, file_path: Path, file_content: str) -> tuple[str, bool]:
86+
"""Format a single file using ruff format"""
87+
# Build ruff format command
88+
cmd = [
89+
str(self.ruff_bin_path),
90+
"format",
91+
"--cache-dir",
92+
str(self.context.cache_dir / ".ruff_cache"),
93+
"--line-length",
94+
str(self.config.line_length),
95+
f'--config="indent-width={str(self.config.indent_width)}"',
96+
f"--config=\"format.quote-style='{self.config.quote_style}'\"",
97+
"--target-version",
98+
self.config.target_version,
99+
"--stdin-filename",
100+
str(file_path),
101+
]
102+
103+
if self.config.preview:
104+
cmd.append("--preview")
105+
106+
cmd_str = " ".join(cmd)
107+
ruff_process = await self.command_runner.run(cmd_str)
108+
109+
ruff_process.write_to_stdin(file_content)
110+
ruff_process.close_stdin() # Signal EOF
111+
112+
await ruff_process.wait_for_end()
113+
114+
if ruff_process.get_exit_code() == 0:
115+
new_file_content = ruff_process.get_output()
116+
file_changed = new_file_content != file_content
117+
return new_file_content, file_changed
118+
else:
119+
raise code_action.ActionFailedException(
120+
f"ruff failed with code {ruff_process.get_exit_code()}: {ruff_process.get_error_output()} || {ruff_process.get_output()}"
121+
)

extensions/fine_python_ruff/fine_python_ruff/py.typed

Whitespace-only changes.

extensions/fine_python_ruff/poetry.lock

Lines changed: 0 additions & 449 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[project]
2+
name = "fine_python_ruff"
3+
version = "0.2.0"
4+
description = ""
5+
authors = [{ name = "Vladyslav Hnatiuk", email = "[email protected]" }]
6+
readme = "README.md"
7+
requires-python = ">=3.11, < 3.14"
8+
dependencies = ["finecode_extension_api==0.3.*", "ruff (>=0.8.0,<1.0.0)"]
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import atexit
2+
import shutil
3+
import sys
4+
import tempfile
5+
6+
from setuptools import setup
7+
from setuptools.command.build import build
8+
from setuptools.command.build_ext import build_ext
9+
from setuptools.command.build_py import build_py
10+
from setuptools.command.egg_info import egg_info
11+
12+
# Create a single temp directory for all build operations
13+
_TEMP_BUILD_DIR = None
14+
15+
16+
def get_temp_build_dir(pkg_name):
17+
global _TEMP_BUILD_DIR
18+
if _TEMP_BUILD_DIR is None:
19+
_TEMP_BUILD_DIR = tempfile.mkdtemp(prefix=f"{pkg_name}_build_")
20+
atexit.register(lambda: shutil.rmtree(_TEMP_BUILD_DIR, ignore_errors=True))
21+
return _TEMP_BUILD_DIR
22+
23+
24+
class TempDirBuildMixin:
25+
def initialize_options(self):
26+
super().initialize_options()
27+
temp_dir = get_temp_build_dir(self.distribution.get_name())
28+
self.build_base = temp_dir
29+
30+
31+
class TempDirEggInfoMixin:
32+
def initialize_options(self):
33+
super().initialize_options()
34+
temp_dir = get_temp_build_dir(self.distribution.get_name())
35+
self.egg_base = temp_dir
36+
37+
38+
class CustomBuild(TempDirBuildMixin, build):
39+
pass
40+
41+
42+
class CustomBuildPy(TempDirBuildMixin, build_py):
43+
pass
44+
45+
46+
class CustomBuildExt(TempDirBuildMixin, build_ext):
47+
pass
48+
49+
50+
class CustomEggInfo(TempDirEggInfoMixin, egg_info):
51+
def initialize_options(self):
52+
# Don't use temp dir for editable installs
53+
if "--editable" in sys.argv or "-e" in sys.argv:
54+
egg_info.initialize_options(self)
55+
else:
56+
super().initialize_options()
57+
58+
59+
setup(
60+
name="fine_python_ruff",
61+
cmdclass={
62+
"build": CustomBuild,
63+
"build_py": CustomBuildPy,
64+
"build_ext": CustomBuildExt,
65+
"egg_info": CustomEggInfo,
66+
},
67+
)

extensions/fine_python_ruff/tests/__init__.py

Whitespace-only changes.

finecode_extension_api/src/finecode_extension_api/interfaces/icommandrunner.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ def get_output(self) -> str: ...
99

1010
def get_error_output(self) -> str: ...
1111

12+
def write_to_stdin(self, value: str) -> None: ...
13+
14+
def close_stdin(self) -> None: ...
15+
1216

1317
class ISyncProcess(IProcess):
1418
def wait_for_end(self, timeout: float | None = None) -> None: ...

finecode_extension_runner/src/finecode_extension_runner/global_state.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
# the runner, not from updating the config
99
project_dir_path: Path | None = None
1010
log_level: Literal["TRACE", "INFO"] = "INFO"
11-
env_name: str = ""
11+
env_name: str = ""

0 commit comments

Comments
 (0)