openai/openai-python

Public

mirrored fromhttps://github.com/openai/openai-pythonAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.0.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

bin/blacken-docs.py

251lines · modecode

1# fork of https://github.com/asottile/blacken-docs implementing https://github.com/asottile/blacken-docs/issues/170
2from __future__ import annotations
3
4import re
5import argparse
6import textwrap
7import contextlib
8from typing import Match, Optional, Sequence, Generator, NamedTuple, cast
9
10import black
11from black.mode import TargetVersion
12from black.const import DEFAULT_LINE_LENGTH
13
14MD_RE = re.compile(
15 r"(?P<before>^(?P<indent> *)```\s*python\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```\s*$)",
16 re.DOTALL | re.MULTILINE,
17)
18MD_PYCON_RE = re.compile(
19 r"(?P<before>^(?P<indent> *)```\s*pycon\n)" r"(?P<code>.*?)" r"(?P<after>^(?P=indent)```.*$)",
20 re.DOTALL | re.MULTILINE,
21)
22RST_PY_LANGS = frozenset(("python", "py", "sage", "python3", "py3", "numpy"))
23BLOCK_TYPES = "(code|code-block|sourcecode|ipython)"
24DOCTEST_TYPES = "(testsetup|testcleanup|testcode)"
25RST_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)
38RST_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)
47PYCON_PREFIX = ">>> "
48PYCON_CONTINUATION_PREFIX = "..."
49PYCON_CONTINUATION_RE = re.compile(
50 rf"^{re.escape(PYCON_CONTINUATION_PREFIX)}( |$)",
51)
52LATEX_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)
58LATEX_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)
62PYTHONTEX_LANG = r"(?P<lang>pyblock|pycode|pyconsole|pyverbatim)"
63PYTHONTEX_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)
69INDENT_RE = re.compile("^ +(?=[^ ])", re.MULTILINE)
70TRAILING_NL_RE = re.compile(r"\n+\Z", re.MULTILINE)
71
72
73class CodeBlockError(NamedTuple):
74 offset: int
75 exc: Exception
76
77
78def format_str(
79 src: str,
80 black_mode: black.FileMode,
81) -> tuple[str, Sequence[CodeBlockError]]:
82 errors: list[CodeBlockError] = []
83
84 @contextlib.contextmanager
85 def _collect_error(match: Match[str]) -> Generator[None, None, None]:
86 try:
87 yield
88 except Exception as e:
89 errors.append(CodeBlockError(match.start(), e))
90
91 def _md_match(match: Match[str]) -> str:
92 code = textwrap.dedent(match["code"])
93 with _collect_error(match):
94 code = black.format_str(code, mode=black_mode)
95 code = textwrap.indent(code, match["indent"])
96 return f'{match["before"]}{code}{match["after"]}'
97
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 def _pycon_match(match: Match[str]) -> str:
113 code = ""
114 fragment = cast(Optional[str], None)
115
116 def finish_fragment() -> None:
117 nonlocal code
118 nonlocal fragment
119
120 if fragment is not None:
121 with _collect_error(match):
122 fragment = black.format_str(fragment, mode=black_mode)
123 fragment_lines = fragment.splitlines()
124 code += f"{PYCON_PREFIX}{fragment_lines[0]}\n"
125 for line in fragment_lines[1:]:
126 # Skip blank lines to handle Black adding a blank above
127 # functions within blocks. A blank line would end the REPL
128 # continuation prompt.
129 #
130 # >>> if True:
131 # ... def f():
132 # ... pass
133 # ...
134 if line:
135 code += f"{PYCON_CONTINUATION_PREFIX} {line}\n"
136 if fragment_lines[-1].startswith(" "):
137 code += f"{PYCON_CONTINUATION_PREFIX}\n"
138 fragment = None
139
140 indentation = None
141 for line in match["code"].splitlines():
142 orig_line, line = line, line.lstrip()
143 if indentation is None and line:
144 indentation = len(orig_line) - len(line)
145 continuation_match = PYCON_CONTINUATION_RE.match(line)
146 if continuation_match and fragment is not None:
147 fragment += line[continuation_match.end() :] + "\n"
148 else:
149 finish_fragment()
150 if line.startswith(PYCON_PREFIX):
151 fragment = line[len(PYCON_PREFIX) :] + "\n"
152 else:
153 code += orig_line[indentation:] + "\n"
154 finish_fragment()
155 return code
156
157 def _md_pycon_match(match: Match[str]) -> str:
158 code = _pycon_match(match)
159 code = textwrap.indent(code, match["indent"])
160 return f'{match["before"]}{code}{match["after"]}'
161
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 src = MD_RE.sub(_md_match, src)
181 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 return src, errors
188
189
190def format_file(
191 filename: str,
192 black_mode: black.FileMode,
193 skip_errors: bool,
194) -> int:
195 with open(filename, encoding="UTF-8") as f:
196 contents = f.read()
197 new_contents, errors = format_str(contents, black_mode)
198 for error in errors:
199 lineno = contents[: error.offset].count("\n") + 1
200 print(f"{filename}:{lineno}: code block parse error {error.exc}")
201 if errors and not skip_errors:
202 return 1
203 if contents != new_contents:
204 print(f"{filename}: Rewriting...")
205 with open(filename, "w", encoding="UTF-8") as f:
206 f.write(new_contents)
207 return 0
208 else:
209 return 0
210
211
212def main(argv: Sequence[str] | None = None) -> int:
213 parser = argparse.ArgumentParser()
214 parser.add_argument(
215 "-l",
216 "--line-length",
217 type=int,
218 default=DEFAULT_LINE_LENGTH,
219 )
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 parser.add_argument(
230 "-S",
231 "--skip-string-normalization",
232 action="store_true",
233 )
234 parser.add_argument("-E", "--skip-errors", action="store_true")
235 parser.add_argument("filenames", nargs="*")
236 args = parser.parse_args(argv)
237
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 retv = 0
245 for filename in args.filenames:
246 retv |= format_file(filename, black_mode, skip_errors=args.skip_errors)
247 return retv
248
249
250if __name__ == "__main__":
251 raise SystemExit(main())
252