66import random
77import re
88import string
9+ import subprocess
910import sys
1011import warnings
1112from functools import lru_cache
@@ -71,19 +72,19 @@ def _sort_key_source(item: CollectorItem) -> Any:
7172
7273
7374def do_format_code (code : str , line_length : int ) -> str :
74- """Format code using Black .
75+ """Format code.
7576
7677 Parameters:
7778 code: The code to format.
78- line_length: The line length to give to Black .
79+ line_length: The line length.
7980
8081 Returns:
8182 The same code, formatted.
8283 """
8384 code = code .strip ()
8485 if len (code ) < line_length :
8586 return code
86- formatter = _get_black_formatter ()
87+ formatter = _get_formatter ()
8788 return formatter (code , line_length )
8889
8990
@@ -118,7 +119,7 @@ def _format_signature(name: Markup, signature: str, line_length: int) -> str:
118119 # Black cannot format names with dots, so we replace
119120 # the whole name with a string of equal length
120121 name_length = len (name )
121- formatter = _get_black_formatter ()
122+ formatter = _get_formatter ()
122123 formatable = f"def { 'x' * name_length } { signature } : pass"
123124 formatted = formatter (formatable , line_length )
124125
@@ -137,13 +138,13 @@ def do_format_signature(
137138 annotations : bool | None = None ,
138139 crossrefs : bool = False , # noqa: ARG001
139140) -> str :
140- """Format a signature using Black .
141+ """Format a signature.
141142
142143 Parameters:
143144 context: Jinja context, passed automatically.
144145 callable_path: The path of the callable we render the signature of.
145146 function: The function we render the signature of.
146- line_length: The line length to give to Black .
147+ line_length: The line length.
147148 annotations: Whether to show type annotations.
148149 crossrefs: Whether to cross-reference types in the signature.
149150
@@ -199,13 +200,13 @@ def do_format_attribute(
199200 * ,
200201 crossrefs : bool = False , # noqa: ARG001
201202) -> str :
202- """Format an attribute using Black .
203+ """Format an attribute.
203204
204205 Parameters:
205206 context: Jinja context, passed automatically.
206207 attribute_path: The path of the callable we render the signature of.
207208 attribute: The attribute we render the signature of.
208- line_length: The line length to give to Black .
209+ line_length: The line length.
209210 crossrefs: Whether to cross-reference types in the signature.
210211
211212 Returns:
@@ -434,12 +435,59 @@ def do_filter_objects(
434435
435436
436437@lru_cache (maxsize = 1 )
437- def _get_black_formatter () -> Callable [[str , int ], str ]:
438+ def _get_formatter () -> Callable [[str , int ], str ]:
439+ for formatter_function in [
440+ _get_black_formatter ,
441+ _get_ruff_formatter ,
442+ ]:
443+ if (formatter := formatter_function ()) is not None :
444+ return formatter
445+
446+ logger .info ("Formatting signatures requires either Black or Ruff to be installed." )
447+ return lambda text , _ : text
448+
449+
450+ def _get_ruff_formatter () -> Callable [[str , int ], str ] | None :
451+ try :
452+ from ruff .__main__ import find_ruff_bin
453+ except ImportError :
454+ return None
455+
456+ try :
457+ ruff_bin = find_ruff_bin ()
458+ except FileNotFoundError :
459+ ruff_bin = "ruff"
460+
461+ def formatter (code : str , line_length : int ) -> str :
462+ try :
463+ completed_process = subprocess .run ( # noqa: S603
464+ [
465+ ruff_bin ,
466+ "format" ,
467+ "--config" ,
468+ f"line-length={ line_length } " ,
469+ "--stdin-filename" ,
470+ "file.py" ,
471+ "-" ,
472+ ],
473+ check = True ,
474+ capture_output = True ,
475+ text = True ,
476+ input = code ,
477+ )
478+ except subprocess .CalledProcessError :
479+ return code
480+ else :
481+ return completed_process .stdout
482+
483+ return formatter
484+
485+
486+ def _get_black_formatter () -> Callable [[str , int ], str ] | None :
438487 try :
439488 from black import InvalidInput , Mode , format_str
440489 except ModuleNotFoundError :
441- logger .info ("Formatting signatures requires Black to be installed." )
442- return lambda text , _ : text
490+ return None
443491
444492 def formatter (code : str , line_length : int ) -> str :
445493 mode = Mode (line_length = line_length )
0 commit comments