1- # fork of https://github.com/asottile/blacken-docs implementing https://github.com/asottile/blacken-docs/issues/170
1+ # fork of https://github.com/asottile/blacken-docs adapted for ruff
22from __future__ import annotations
33
44import re
5+ import sys
56import argparse
67import textwrap
78import contextlib
9+ import subprocess
810from typing import Match , Optional , Sequence , Generator , NamedTuple , cast
911
10- import black
11- from black .mode import TargetVersion
12- from black .const import DEFAULT_LINE_LENGTH
13-
1412MD_RE = re .compile (
1513 r"(?P<before>^(?P<indent> *)```\s*python\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```\s*$)" ,
1614 re .DOTALL | re .MULTILINE ,
1917 r"(?P<before>^(?P<indent> *)```\s*pycon\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```.*$)" ,
2018 re .DOTALL | re .MULTILINE ,
2119)
22- RST_PY_LANGS = frozenset (("python" , "py" , "sage" , "python3" , "py3" , "numpy" ))
23- BLOCK_TYPES = "(code|code-block|sourcecode|ipython)"
24- DOCTEST_TYPES = "(testsetup|testcleanup|testcode)"
25- RST_RE = re .compile (
26- rf"(?P<before>"
27- rf"^(?P<indent> *)\.\. ("
28- rf"jupyter-execute::|"
29- rf"{ BLOCK_TYPES } :: (?P<lang>\w+)|"
30- rf"{ DOCTEST_TYPES } ::.*"
31- rf")\n"
32- rf"((?P=indent) +:.*\n)*"
33- rf"\n*"
34- rf")"
35- rf"(?P<code>(^((?P=indent) +.*)?\n)+)" ,
36- re .MULTILINE ,
37- )
38- RST_PYCON_RE = re .compile (
39- r"(?P<before>"
40- r"(?P<indent> *)\.\. ((code|code-block):: pycon|doctest::.*)\n"
41- r"((?P=indent) +:.*\n)*"
42- r"\n*"
43- r")"
44- r"(?P<code>(^((?P=indent) +.*)?(\n|$))+)" ,
45- re .MULTILINE ,
46- )
4720PYCON_PREFIX = ">>> "
4821PYCON_CONTINUATION_PREFIX = "..."
4922PYCON_CONTINUATION_RE = re .compile (
5023 rf"^{ re .escape (PYCON_CONTINUATION_PREFIX )} ( |$)" ,
5124)
52- LATEX_RE = re .compile (
53- r"(?P<before>^(?P<indent> *)\\begin{minted}{python}\n)"
54- r"(?P<code>.*?)"
55- r"(?P<after>^(?P=indent)\\end{minted}\s*$)" ,
56- re .DOTALL | re .MULTILINE ,
57- )
58- LATEX_PYCON_RE = re .compile (
59- r"(?P<before>^(?P<indent> *)\\begin{minted}{pycon}\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)\\end{minted}\s*$)" ,
60- re .DOTALL | re .MULTILINE ,
61- )
62- PYTHONTEX_LANG = r"(?P<lang>pyblock|pycode|pyconsole|pyverbatim)"
63- PYTHONTEX_RE = re .compile (
64- rf"(?P<before>^(?P<indent> *)\\begin{{{ PYTHONTEX_LANG } }}\n)"
65- rf"(?P<code>.*?)"
66- rf"(?P<after>^(?P=indent)\\end{{(?P=lang)}}\s*$)" ,
67- re .DOTALL | re .MULTILINE ,
68- )
69- INDENT_RE = re .compile ("^ +(?=[^ ])" , re .MULTILINE )
70- TRAILING_NL_RE = re .compile (r"\n+\Z" , re .MULTILINE )
25+ DEFAULT_LINE_LENGTH = 100
7126
7227
7328class CodeBlockError (NamedTuple ):
@@ -77,7 +32,6 @@ class CodeBlockError(NamedTuple):
7732
7833def format_str (
7934 src : str ,
80- black_mode : black .FileMode ,
8135) -> tuple [str , Sequence [CodeBlockError ]]:
8236 errors : list [CodeBlockError ] = []
8337
@@ -91,24 +45,10 @@ def _collect_error(match: Match[str]) -> Generator[None, None, None]:
9145 def _md_match (match : Match [str ]) -> str :
9246 code = textwrap .dedent (match ["code" ])
9347 with _collect_error (match ):
94- code = black . format_str (code , mode = black_mode )
48+ code = format_code_block (code )
9549 code = textwrap .indent (code , match ["indent" ])
9650 return f'{ match ["before" ]} { code } { match ["after" ]} '
9751
98- def _rst_match (match : Match [str ]) -> str :
99- lang = match ["lang" ]
100- if lang is not None and lang not in RST_PY_LANGS :
101- return match [0 ]
102- min_indent = min (INDENT_RE .findall (match ["code" ]))
103- trailing_ws_match = TRAILING_NL_RE .search (match ["code" ])
104- assert trailing_ws_match
105- trailing_ws = trailing_ws_match .group ()
106- code = textwrap .dedent (match ["code" ])
107- with _collect_error (match ):
108- code = black .format_str (code , mode = black_mode )
109- code = textwrap .indent (code , min_indent )
110- return f'{ match ["before" ]} { code .rstrip ()} { trailing_ws } '
111-
11252 def _pycon_match (match : Match [str ]) -> str :
11353 code = ""
11454 fragment = cast (Optional [str ], None )
@@ -119,7 +59,7 @@ def finish_fragment() -> None:
11959
12060 if fragment is not None :
12161 with _collect_error (match ):
122- fragment = black . format_str (fragment , mode = black_mode )
62+ fragment = format_code_block (fragment )
12363 fragment_lines = fragment .splitlines ()
12464 code += f"{ PYCON_PREFIX } { fragment_lines [0 ]} \n "
12565 for line in fragment_lines [1 :]:
@@ -159,42 +99,33 @@ def _md_pycon_match(match: Match[str]) -> str:
15999 code = textwrap .indent (code , match ["indent" ])
160100 return f'{ match ["before" ]} { code } { match ["after" ]} '
161101
162- def _rst_pycon_match (match : Match [str ]) -> str :
163- code = _pycon_match (match )
164- min_indent = min (INDENT_RE .findall (match ["code" ]))
165- code = textwrap .indent (code , min_indent )
166- return f'{ match ["before" ]} { code } '
167-
168- def _latex_match (match : Match [str ]) -> str :
169- code = textwrap .dedent (match ["code" ])
170- with _collect_error (match ):
171- code = black .format_str (code , mode = black_mode )
172- code = textwrap .indent (code , match ["indent" ])
173- return f'{ match ["before" ]} { code } { match ["after" ]} '
174-
175- def _latex_pycon_match (match : Match [str ]) -> str :
176- code = _pycon_match (match )
177- code = textwrap .indent (code , match ["indent" ])
178- return f'{ match ["before" ]} { code } { match ["after" ]} '
179-
180102 src = MD_RE .sub (_md_match , src )
181103 src = MD_PYCON_RE .sub (_md_pycon_match , src )
182- src = RST_RE .sub (_rst_match , src )
183- src = RST_PYCON_RE .sub (_rst_pycon_match , src )
184- src = LATEX_RE .sub (_latex_match , src )
185- src = LATEX_PYCON_RE .sub (_latex_pycon_match , src )
186- src = PYTHONTEX_RE .sub (_latex_match , src )
187104 return src , errors
188105
189106
107+ def format_code_block (code : str ) -> str :
108+ return subprocess .check_output (
109+ [
110+ sys .executable ,
111+ "-m" ,
112+ "ruff" ,
113+ "format" ,
114+ "--stdin-filename=script.py" ,
115+ f"--line-length={ DEFAULT_LINE_LENGTH } " ,
116+ ],
117+ encoding = "utf-8" ,
118+ input = code ,
119+ )
120+
121+
190122def format_file (
191123 filename : str ,
192- black_mode : black .FileMode ,
193124 skip_errors : bool ,
194125) -> int :
195126 with open (filename , encoding = "UTF-8" ) as f :
196127 contents = f .read ()
197- new_contents , errors = format_str (contents , black_mode )
128+ new_contents , errors = format_str (contents )
198129 for error in errors :
199130 lineno = contents [: error .offset ].count ("\n " ) + 1
200131 print (f"{ filename } :{ lineno } : code block parse error { error .exc } " )
@@ -217,15 +148,6 @@ def main(argv: Sequence[str] | None = None) -> int:
217148 type = int ,
218149 default = DEFAULT_LINE_LENGTH ,
219150 )
220- parser .add_argument (
221- "-t" ,
222- "--target-version" ,
223- action = "append" ,
224- type = lambda v : TargetVersion [v .upper ()],
225- default = [],
226- help = f"choices: { [v .name .lower () for v in TargetVersion ]} " ,
227- dest = "target_versions" ,
228- )
229151 parser .add_argument (
230152 "-S" ,
231153 "--skip-string-normalization" ,
@@ -235,15 +157,9 @@ def main(argv: Sequence[str] | None = None) -> int:
235157 parser .add_argument ("filenames" , nargs = "*" )
236158 args = parser .parse_args (argv )
237159
238- black_mode = black .FileMode (
239- target_versions = set (args .target_versions ),
240- line_length = args .line_length ,
241- string_normalization = not args .skip_string_normalization ,
242- )
243-
244160 retv = 0
245161 for filename in args .filenames :
246- retv |= format_file (filename , black_mode , skip_errors = args .skip_errors )
162+ retv |= format_file (filename , skip_errors = args .skip_errors )
247163 return retv
248164
249165
0 commit comments