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
2
2
from __future__ import annotations
3
3
4
4
import re
5
+ import sys
5
6
import argparse
6
7
import textwrap
7
8
import contextlib
9
+ import subprocess
8
10
from typing import Match , Optional , Sequence , Generator , NamedTuple , cast
9
11
10
- import black
11
- from black .mode import TargetVersion
12
- from black .const import DEFAULT_LINE_LENGTH
13
-
14
12
MD_RE = re .compile (
15
13
r"(?P<before>^(?P<indent> *)```\s*python\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```\s*$)" ,
16
14
re .DOTALL | re .MULTILINE ,
19
17
r"(?P<before>^(?P<indent> *)```\s*pycon\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```.*$)" ,
20
18
re .DOTALL | re .MULTILINE ,
21
19
)
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
- )
47
20
PYCON_PREFIX = ">>> "
48
21
PYCON_CONTINUATION_PREFIX = "..."
49
22
PYCON_CONTINUATION_RE = re .compile (
50
23
rf"^{ re .escape (PYCON_CONTINUATION_PREFIX )} ( |$)" ,
51
24
)
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
71
26
72
27
73
28
class CodeBlockError (NamedTuple ):
@@ -77,7 +32,6 @@ class CodeBlockError(NamedTuple):
77
32
78
33
def format_str (
79
34
src : str ,
80
- black_mode : black .FileMode ,
81
35
) -> tuple [str , Sequence [CodeBlockError ]]:
82
36
errors : list [CodeBlockError ] = []
83
37
@@ -91,24 +45,10 @@ def _collect_error(match: Match[str]) -> Generator[None, None, None]:
91
45
def _md_match (match : Match [str ]) -> str :
92
46
code = textwrap .dedent (match ["code" ])
93
47
with _collect_error (match ):
94
- code = black . format_str (code , mode = black_mode )
48
+ code = format_code_block (code )
95
49
code = textwrap .indent (code , match ["indent" ])
96
50
return f'{ match ["before" ]} { code } { match ["after" ]} '
97
51
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
-
112
52
def _pycon_match (match : Match [str ]) -> str :
113
53
code = ""
114
54
fragment = cast (Optional [str ], None )
@@ -119,7 +59,7 @@ def finish_fragment() -> None:
119
59
120
60
if fragment is not None :
121
61
with _collect_error (match ):
122
- fragment = black . format_str (fragment , mode = black_mode )
62
+ fragment = format_code_block (fragment )
123
63
fragment_lines = fragment .splitlines ()
124
64
code += f"{ PYCON_PREFIX } { fragment_lines [0 ]} \n "
125
65
for line in fragment_lines [1 :]:
@@ -159,42 +99,33 @@ def _md_pycon_match(match: Match[str]) -> str:
159
99
code = textwrap .indent (code , match ["indent" ])
160
100
return f'{ match ["before" ]} { code } { match ["after" ]} '
161
101
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
-
180
102
src = MD_RE .sub (_md_match , src )
181
103
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 )
187
104
return src , errors
188
105
189
106
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
+
190
122
def format_file (
191
123
filename : str ,
192
- black_mode : black .FileMode ,
193
124
skip_errors : bool ,
194
125
) -> int :
195
126
with open (filename , encoding = "UTF-8" ) as f :
196
127
contents = f .read ()
197
- new_contents , errors = format_str (contents , black_mode )
128
+ new_contents , errors = format_str (contents )
198
129
for error in errors :
199
130
lineno = contents [: error .offset ].count ("\n " ) + 1
200
131
print (f"{ filename } :{ lineno } : code block parse error { error .exc } " )
@@ -217,15 +148,6 @@ def main(argv: Sequence[str] | None = None) -> int:
217
148
type = int ,
218
149
default = DEFAULT_LINE_LENGTH ,
219
150
)
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
- )
229
151
parser .add_argument (
230
152
"-S" ,
231
153
"--skip-string-normalization" ,
@@ -235,15 +157,9 @@ def main(argv: Sequence[str] | None = None) -> int:
235
157
parser .add_argument ("filenames" , nargs = "*" )
236
158
args = parser .parse_args (argv )
237
159
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
-
244
160
retv = 0
245
161
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 )
247
163
return retv
248
164
249
165
0 commit comments