prettycsharp 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
prettycsharp/doc.py ADDED
@@ -0,0 +1,537 @@
1
+ """The layout document model and the printer that fits it to a line width.
2
+
3
+ A document describes text with optional line breaks, in the style of
4
+ Wadler's "prettier printer" as Prettier implements it. A ``Group`` prints on
5
+ one line when it fits and breaks every ``Line`` directly inside it when it
6
+ does not. ``HARDLINE`` always breaks and forces every enclosing group to
7
+ break. The printer is iterative, so deeply nested documents do not exhaust
8
+ the Python stack.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Sequence
14
+ from dataclasses import dataclass, field
15
+
16
+ # A document is a str, a list of documents, or one of the node classes below.
17
+ Doc = object
18
+
19
+ _MODE_BREAK = 0
20
+ _MODE_FLAT = 1
21
+
22
+
23
+ @dataclass(eq=False, slots=True)
24
+ class Group:
25
+ contents: Doc
26
+ should_break: bool = False
27
+ id: object | None = None
28
+ # A conditional group: the printer uses the first state that fits, and
29
+ # the last one when none does.
30
+ expanded_states: Sequence[Doc] | None = None
31
+
32
+
33
+ @dataclass(eq=False, slots=True)
34
+ class Indent:
35
+ contents: Doc
36
+
37
+
38
+ @dataclass(eq=False, slots=True)
39
+ class Align:
40
+ """Indent the contents by ``spaces`` extra columns."""
41
+
42
+ spaces: int
43
+ contents: Doc
44
+
45
+
46
+ @dataclass(eq=False, slots=True)
47
+ class Line:
48
+ hard: bool = False
49
+ soft: bool = False
50
+ literal: bool = False
51
+
52
+
53
+ @dataclass(eq=False, slots=True)
54
+ class IfBreak:
55
+ break_contents: Doc
56
+ flat_contents: Doc = ""
57
+ group_id: object | None = None
58
+
59
+
60
+ @dataclass(eq=False, slots=True)
61
+ class IndentIfBreak:
62
+ contents: Doc
63
+ group_id: object
64
+
65
+
66
+ @dataclass(eq=False, slots=True)
67
+ class LineSuffix:
68
+ """Text deferred to the end of the line, such as a trailing comment."""
69
+
70
+ contents: Doc
71
+
72
+
73
+ @dataclass(eq=False, slots=True)
74
+ class BreakParent:
75
+ pass
76
+
77
+
78
+ @dataclass(eq=False, slots=True)
79
+ class EnsureLine:
80
+ """A line break, unless nothing but indentation is on the line yet.
81
+
82
+ Comments that stood on lines of their own come after one, so they never
83
+ end up behind code, where the next formatting would take them for
84
+ trailing comments.
85
+ """
86
+
87
+
88
+ @dataclass(eq=False, slots=True)
89
+ class Fill:
90
+ """Content, separator, content, ...: separators break only where needed."""
91
+
92
+ parts: list[Doc] = field(default_factory=list)
93
+
94
+
95
+ @dataclass(eq=False, slots=True)
96
+ class Wrapped:
97
+ """Words that wrap to the line width at print time, each line prefixed.
98
+
99
+ Used for comment paragraphs: ``prefix`` is ``"// "`` and the words are
100
+ laid out greedily, so the result depends on the indentation the printer
101
+ reaches, which is known only while printing.
102
+ """
103
+
104
+ prefix: str
105
+ words: list[str]
106
+ # The lines as written. When given, they are printed unchanged unless
107
+ # one of them would pass the line width; only then are the words wrapped.
108
+ lines: list[str] | None = None
109
+
110
+
111
+ @dataclass(eq=False, slots=True)
112
+ class TrimIndent:
113
+ """Remove the indentation printed since the last line break: go to column 0."""
114
+
115
+
116
+ LINE = Line()
117
+ SOFTLINE = Line(soft=True)
118
+ ENSURE_LINE = EnsureLine()
119
+ TRIM_INDENT = TrimIndent()
120
+ _HARD = Line(hard=True)
121
+ _BREAK_PARENT = BreakParent()
122
+ HARDLINE: Doc = [_HARD, _BREAK_PARENT]
123
+ LITERALLINE: Doc = [Line(hard=True, literal=True), _BREAK_PARENT]
124
+ BREAK_PARENT = _BREAK_PARENT
125
+
126
+
127
+ def group(contents: Doc, should_break: bool = False, id: object | None = None) -> Group:
128
+ return Group(contents, should_break, id)
129
+
130
+
131
+ def conditional_group(states: Sequence[Doc]) -> Group:
132
+ return Group(states[0], False, None, list(states))
133
+
134
+
135
+ def indent(contents: Doc) -> Indent:
136
+ return Indent(contents)
137
+
138
+
139
+ def join(separator: Doc, docs: Sequence[Doc]) -> list[Doc]:
140
+ out: list[Doc] = []
141
+ for i, doc in enumerate(docs):
142
+ if i:
143
+ out.append(separator)
144
+ out.append(doc)
145
+ return out
146
+
147
+
148
+ def propagate_breaks(doc: Doc) -> None:
149
+ """Mark every group that contains a hard break as broken.
150
+
151
+ A conditional group stops the propagation, as in Prettier: its states
152
+ decide for themselves how much of them may break.
153
+ """
154
+ # Each stack entry is (doc, exiting). Groups are tracked in group_stack.
155
+ group_stack: list[Group] = []
156
+ stack: list[tuple[Doc, bool]] = [(doc, False)]
157
+ seen: set[int] = set()
158
+ while stack:
159
+ node, exiting = stack.pop()
160
+ if exiting:
161
+ assert isinstance(node, Group)
162
+ group_stack.pop()
163
+ if node.should_break and group_stack:
164
+ _break_parent(group_stack)
165
+ continue
166
+ if isinstance(node, str):
167
+ continue
168
+ if isinstance(node, list):
169
+ for child in reversed(node):
170
+ stack.append((child, False))
171
+ elif isinstance(node, BreakParent):
172
+ if group_stack:
173
+ _break_parent(group_stack)
174
+ elif isinstance(node, Group):
175
+ if id(node) in seen:
176
+ # A group shared between conditional states: its own break
177
+ # flag is already settled; still tell the parent.
178
+ if node.should_break and group_stack:
179
+ _break_parent(group_stack)
180
+ continue
181
+ seen.add(id(node))
182
+ group_stack.append(node)
183
+ stack.append((node, True))
184
+ if node.expanded_states is not None:
185
+ for state in reversed(node.expanded_states):
186
+ stack.append((state, False))
187
+ else:
188
+ stack.append((node.contents, False))
189
+ elif isinstance(node, (Indent, Align, LineSuffix, IndentIfBreak)):
190
+ stack.append((node.contents, False))
191
+ elif isinstance(node, IfBreak):
192
+ stack.append((node.flat_contents, False))
193
+ stack.append((node.break_contents, False))
194
+ elif isinstance(node, Fill):
195
+ for child in reversed(node.parts):
196
+ stack.append((child, False))
197
+
198
+
199
+ def _break_parent(group_stack: list[Group]) -> None:
200
+ parent = group_stack[-1]
201
+ if parent.expanded_states is None and not parent.should_break:
202
+ parent.should_break = True
203
+
204
+
205
+ @dataclass(slots=True)
206
+ class _Indentation:
207
+ text: str
208
+ width: int
209
+
210
+
211
+ class Printer:
212
+ def __init__(self, width: int, indent_unit: str, indent_width: int, eol: str) -> None:
213
+ self.width = width
214
+ self.indent_unit = indent_unit
215
+ self.indent_width = indent_width
216
+ self.eol = eol
217
+ self._indent_cache: dict[tuple[int, int], _Indentation] = {}
218
+
219
+ def _indentation(self, base: _Indentation, *, levels: int = 0, spaces: int = 0) -> _Indentation:
220
+ return _Indentation(
221
+ base.text + self.indent_unit * levels + " " * spaces,
222
+ base.width + self.indent_width * levels + spaces,
223
+ )
224
+
225
+ def print(self, doc: Doc) -> str:
226
+ propagate_breaks(doc)
227
+ root = _Indentation("", 0)
228
+ out: list[str] = []
229
+ pos = 0
230
+ line_suffix: list[tuple[_Indentation, int, Doc]] = []
231
+ group_modes: dict[object, int] = {}
232
+ cmds: list[tuple[_Indentation, int, Doc]] = [(root, _MODE_BREAK, doc)]
233
+ # Whether anything but whitespace is on the current line yet.
234
+ content = False
235
+
236
+ while cmds:
237
+ ind, mode, node = cmds.pop()
238
+ if isinstance(node, str):
239
+ if line_suffix and node.strip():
240
+ # Code after an end-of-line comment goes on the next line,
241
+ # so the comment stays behind the code it followed.
242
+ cmds.append((ind, mode, node))
243
+ cmds.append((ind, mode, _HARD))
244
+ continue
245
+ out.append(node)
246
+ newline = node.rfind("\n")
247
+ if newline >= 0:
248
+ pos = len(node) - newline - 1
249
+ content = bool(node[newline + 1 :].strip())
250
+ else:
251
+ pos += len(node)
252
+ content = content or bool(node.strip())
253
+ elif isinstance(node, TrimIndent):
254
+ if not content:
255
+ _trim(out)
256
+ pos = 0
257
+ elif isinstance(node, EnsureLine):
258
+ if content:
259
+ cmds.append((ind, mode, _HARD))
260
+ elif line_suffix:
261
+ # End-of-line comments with no code left on their line:
262
+ # each takes a line of its own, at this indentation.
263
+ _trim(out)
264
+ if out and out[-1].endswith(("\n", "\r")):
265
+ out.append(ind.text)
266
+ pos = ind.width
267
+ for _, suffix_mode, suffix in reversed(line_suffix):
268
+ cmds.append((ind, suffix_mode, _HARD))
269
+ cmds.append((ind, suffix_mode, _without_leading_space(suffix)))
270
+ line_suffix = []
271
+ elif out:
272
+ # Only spaces since the line break: start at this indentation.
273
+ _trim(out)
274
+ if out and out[-1].endswith(("\n", "\r")):
275
+ out.append(ind.text)
276
+ pos = ind.width
277
+ elif isinstance(node, list):
278
+ for child in reversed(node):
279
+ cmds.append((ind, mode, child))
280
+ elif isinstance(node, Indent):
281
+ cmds.append((self._indentation(ind, levels=1), mode, node.contents))
282
+ elif isinstance(node, Align):
283
+ # A negative width aligns to the column the printer is at.
284
+ spaces = node.spaces if node.spaces >= 0 else max(pos - ind.width, 0)
285
+ cmds.append((self._indentation(ind, spaces=spaces), mode, node.contents))
286
+ elif isinstance(node, Group):
287
+ if mode == _MODE_FLAT and not node.should_break:
288
+ cmds.append((ind, _MODE_FLAT, node.contents))
289
+ if node.id is not None:
290
+ group_modes[node.id] = _MODE_FLAT
291
+ continue
292
+ flat = (ind, _MODE_FLAT, node.contents)
293
+ rem = self.width - pos
294
+ if not node.should_break and self._fits(flat, cmds, rem, group_modes):
295
+ cmds.append(flat)
296
+ chosen = _MODE_FLAT
297
+ elif node.expanded_states is not None:
298
+ most_expanded = node.expanded_states[-1]
299
+ if node.should_break:
300
+ cmds.append((ind, _MODE_BREAK, most_expanded))
301
+ chosen = _MODE_BREAK
302
+ else:
303
+ chosen = _MODE_BREAK
304
+ for state in node.expanded_states[1:]:
305
+ attempt = (ind, _MODE_FLAT, state)
306
+ if self._fits(attempt, cmds, rem, group_modes):
307
+ cmds.append(attempt)
308
+ chosen = _MODE_FLAT
309
+ break
310
+ else:
311
+ cmds.append((ind, _MODE_BREAK, most_expanded))
312
+ else:
313
+ cmds.append((ind, _MODE_BREAK, node.contents))
314
+ chosen = _MODE_BREAK
315
+ if node.id is not None:
316
+ group_modes[node.id] = chosen
317
+ elif isinstance(node, Fill):
318
+ self._print_fill(ind, mode, node, cmds, pos, line_suffix, group_modes)
319
+ elif isinstance(node, IfBreak):
320
+ group_mode = group_modes.get(node.group_id, _MODE_FLAT) if node.group_id is not None else mode
321
+ contents = node.break_contents if group_mode == _MODE_BREAK else node.flat_contents
322
+ cmds.append((ind, mode, contents))
323
+ elif isinstance(node, IndentIfBreak):
324
+ group_mode = group_modes.get(node.group_id, _MODE_FLAT)
325
+ if group_mode == _MODE_BREAK:
326
+ cmds.append((self._indentation(ind, levels=1), mode, node.contents))
327
+ else:
328
+ cmds.append((ind, mode, node.contents))
329
+ elif isinstance(node, LineSuffix):
330
+ if line_suffix:
331
+ # Two end-of-line comments cannot share a line: the first
332
+ # would swallow the second. Break the line between them.
333
+ cmds.append((ind, mode, node))
334
+ cmds.append((ind, mode, _HARD))
335
+ continue
336
+ line_suffix.append((ind, mode, node.contents))
337
+ elif isinstance(node, BreakParent):
338
+ pass
339
+ elif isinstance(node, Wrapped):
340
+ pos = self._print_wrapped(ind, node, out, pos)
341
+ content = True
342
+ elif isinstance(node, Line):
343
+ if mode == _MODE_FLAT and not node.hard:
344
+ if not node.soft:
345
+ out.append(" ")
346
+ pos += 1
347
+ continue
348
+ if line_suffix:
349
+ cmds.append((ind, mode, node))
350
+ for entry in reversed(line_suffix):
351
+ cmds.append(entry)
352
+ line_suffix = []
353
+ # The comment brings its own space.
354
+ _trim(out)
355
+ continue
356
+ _trim(out)
357
+ content = False
358
+ if node.literal:
359
+ out.append(self.eol)
360
+ pos = 0
361
+ else:
362
+ out.append(self.eol + ind.text)
363
+ pos = ind.width
364
+ else: # pragma: no cover - a programming error
365
+ raise TypeError(f"not a document: {node!r}")
366
+
367
+ if not cmds and line_suffix:
368
+ for entry in reversed(line_suffix):
369
+ cmds.append(entry)
370
+ line_suffix = []
371
+ _trim(out)
372
+ return "".join(out)
373
+
374
+ def _fits(
375
+ self,
376
+ next_cmd: tuple[_Indentation, int, Doc],
377
+ rest: list[tuple[_Indentation, int, Doc]],
378
+ width: int,
379
+ group_modes: dict[object, int],
380
+ must_be_flat: bool = False,
381
+ ) -> bool:
382
+ rest_index = len(rest)
383
+ cmds: list[tuple[int, Doc]] = [(next_cmd[1], next_cmd[2])]
384
+ while width >= 0:
385
+ if not cmds:
386
+ if rest_index == 0:
387
+ return True
388
+ rest_index -= 1
389
+ entry = rest[rest_index]
390
+ cmds.append((entry[1], entry[2]))
391
+ continue
392
+ mode, node = cmds.pop()
393
+ if isinstance(node, str):
394
+ newline = node.find("\n")
395
+ if newline >= 0:
396
+ # A multi-line token: only its first line has to fit.
397
+ return width - newline >= 0
398
+ width -= len(node)
399
+ elif isinstance(node, list):
400
+ for child in reversed(node):
401
+ cmds.append((mode, child))
402
+ elif isinstance(node, Fill):
403
+ for child in reversed(node.parts):
404
+ cmds.append((mode, child))
405
+ elif isinstance(node, LineSuffix):
406
+ # Printed at the end of the line, so it takes no width here.
407
+ continue
408
+ elif isinstance(node, (Indent, Align, IndentIfBreak)):
409
+ cmds.append((mode, node.contents))
410
+ elif isinstance(node, Group):
411
+ if must_be_flat and node.should_break:
412
+ return False
413
+ group_mode = _MODE_BREAK if node.should_break else mode
414
+ contents = node.contents
415
+ if node.expanded_states is not None and group_mode == _MODE_BREAK:
416
+ contents = node.expanded_states[-1]
417
+ cmds.append((group_mode, contents))
418
+ elif isinstance(node, IfBreak):
419
+ group_mode = group_modes.get(node.group_id, _MODE_FLAT) if node.group_id is not None else mode
420
+ contents = node.break_contents if group_mode == _MODE_BREAK else node.flat_contents
421
+ cmds.append((mode, contents))
422
+ elif isinstance(node, Line):
423
+ if mode == _MODE_BREAK or node.hard:
424
+ return True
425
+ if not node.soft:
426
+ width -= 1
427
+ elif isinstance(node, (Wrapped, EnsureLine)):
428
+ return True
429
+ return False
430
+
431
+ def _print_fill(
432
+ self,
433
+ ind: _Indentation,
434
+ mode: int,
435
+ node: Fill,
436
+ cmds: list[tuple[_Indentation, int, Doc]],
437
+ pos: int,
438
+ line_suffix: list[tuple[_Indentation, int, Doc]],
439
+ group_modes: dict[object, int],
440
+ ) -> None:
441
+ parts = node.parts
442
+ if not parts:
443
+ return
444
+ rem = self.width - pos
445
+ content = parts[0]
446
+ content_flat = (ind, _MODE_FLAT, content)
447
+ content_fits = self._fits(content_flat, [], rem, group_modes, True)
448
+ if len(parts) == 1:
449
+ cmds.append(content_flat if content_fits else (ind, _MODE_BREAK, content))
450
+ return
451
+ whitespace = parts[1]
452
+ if len(parts) == 2:
453
+ if content_fits:
454
+ cmds.append((ind, _MODE_FLAT, whitespace))
455
+ cmds.append(content_flat)
456
+ else:
457
+ cmds.append((ind, _MODE_BREAK, whitespace))
458
+ cmds.append((ind, _MODE_BREAK, content))
459
+ return
460
+ remaining = Fill(parts[2:])
461
+ second = parts[2]
462
+ pair_flat = (ind, _MODE_FLAT, [content, whitespace, second])
463
+ pair_fits = self._fits(pair_flat, [], rem, group_modes, True)
464
+ cmds.append((ind, mode, remaining))
465
+ if pair_fits:
466
+ cmds.append((ind, _MODE_FLAT, whitespace))
467
+ cmds.append(content_flat)
468
+ elif content_fits:
469
+ cmds.append((ind, _MODE_BREAK, whitespace))
470
+ cmds.append(content_flat)
471
+ else:
472
+ cmds.append((ind, _MODE_BREAK, whitespace))
473
+ cmds.append((ind, _MODE_BREAK, content))
474
+
475
+ def _print_wrapped(self, ind: _Indentation, node: Wrapped, out: list[str], pos: int) -> int:
476
+ texts: list[str]
477
+ if node.lines is not None and all(
478
+ (pos if i == 0 else ind.width) + len(line) <= self.width for i, line in enumerate(node.lines)
479
+ ):
480
+ texts = node.lines
481
+ else:
482
+ # Greedy word wrap: as many words per line as fit, at least one.
483
+ # The first line starts where the printer is; later ones at the indent.
484
+ texts = [
485
+ (node.prefix + " ".join(words)).rstrip()
486
+ for words in wrap_words(
487
+ node.words,
488
+ first=self.width - pos - len(node.prefix),
489
+ rest=self.width - ind.width - len(node.prefix),
490
+ )
491
+ ]
492
+ for i, text in enumerate(texts):
493
+ if i:
494
+ out.append(self.eol + ind.text)
495
+ pos = ind.width
496
+ out.append(text)
497
+ pos += len(text)
498
+ return pos
499
+
500
+
501
+ def wrap_words(words: Sequence[str], first: int, rest: int) -> list[list[str]]:
502
+ """Split words into lines no longer than the widths given, where possible."""
503
+ lines: list[list[str]] = [[]]
504
+ available = first
505
+ length = 0
506
+ for word in words:
507
+ current = lines[-1]
508
+ extra = len(word) + (1 if current else 0)
509
+ if current and length + extra > available:
510
+ lines.append([word])
511
+ length = len(word)
512
+ available = rest
513
+ else:
514
+ current.append(word)
515
+ length += extra
516
+ return lines
517
+
518
+
519
+ def _without_leading_space(doc: Doc) -> Doc:
520
+ """A trailing comment's document, ``[" ", text]``, without its separating space."""
521
+ if isinstance(doc, list) and doc and doc[0] == " ":
522
+ return doc[1:]
523
+ return doc
524
+
525
+
526
+ def _trim(out: list[str]) -> None:
527
+ """Remove trailing spaces and tabs from the end of the output."""
528
+ while out:
529
+ trimmed = out[-1].rstrip(" \t")
530
+ if trimmed:
531
+ out[-1] = trimmed
532
+ return
533
+ out.pop()
534
+
535
+
536
+ def print_doc(doc: Doc, width: int, indent_unit: str = " ", indent_width: int = 4, eol: str = "\n") -> str:
537
+ return Printer(width, indent_unit, indent_width, eol).print(doc)
@@ -0,0 +1,106 @@
1
+ """The formatting pipeline: parse, rewrite, print, then verify.
2
+
3
+ ``format_source`` refuses input that does not parse (``CSharpSyntaxError``)
4
+ and raises ``FormattingError`` rather than return output that the verifier
5
+ finds different in meaning from the input. Formatting runs in a worker
6
+ thread with a large stack, so a deeply nested file fails cleanly instead of
7
+ crashing the interpreter.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import sys
13
+ import threading
14
+ from collections.abc import Callable
15
+ from typing import TypeVar
16
+
17
+ from prettycsharp.parsing import CSharpSyntaxError
18
+ from prettycsharp.printer import print_file
19
+ from prettycsharp.rewrites import rewrite
20
+ from prettycsharp.settings import Settings
21
+ from prettycsharp.syntax import build
22
+ from prettycsharp.verify import VerificationError, canonical, comment_words
23
+
24
+ LATEST = 99.0
25
+ # 255 MiB: Windows refuses a thread stack of 256 MiB or more.
26
+ _STACK_SIZE = 255 * 1024 * 1024
27
+ _RECURSION_LIMIT = 50_000
28
+ _lock = threading.Lock()
29
+
30
+ T = TypeVar("T")
31
+
32
+
33
+ class FormattingError(Exception):
34
+ """The formatter could not produce output it can stand behind."""
35
+
36
+
37
+ def lang_version_of(settings: Settings, detected: float | None = None) -> float:
38
+ if settings.lang_version == "auto":
39
+ return detected if detected is not None else LATEST
40
+ if settings.lang_version == "latest":
41
+ return LATEST
42
+ return float(settings.lang_version)
43
+
44
+
45
+ def format_source(source: str, settings: Settings | None = None, *, lang_version: float | None = None) -> str:
46
+ """Return ``source`` formatted.
47
+
48
+ Raises CSharpSyntaxError when the source does not parse, and
49
+ FormattingError when the result fails the formatter's own check.
50
+ ``lang_version`` is the project's C# version, used when the settings say
51
+ ``auto``; without it the latest version is assumed.
52
+ """
53
+ chosen = settings or Settings()
54
+ version = lang_version_of(chosen, lang_version)
55
+ return _run_with_big_stack(lambda: _format(source, chosen, version))
56
+
57
+
58
+ def _format(source: str, settings: Settings, lang_version: float) -> str:
59
+ file = build(source)
60
+ expected_tree = canonical(file.root)
61
+ expected_comments = comment_words(file)
62
+ rewrite(file, settings, lang_version)
63
+ text = print_file(file, settings, lang_version)
64
+ try:
65
+ result = build(text)
66
+ except CSharpSyntaxError as error:
67
+ raise FormattingError(f"the formatted code does not parse ({error})") from None
68
+ if canonical(result.root) != expected_tree or comment_words(result) != expected_comments:
69
+ try:
70
+ from prettycsharp.verify import verify
71
+
72
+ original = build(source)
73
+ verify(original, result)
74
+ except VerificationError as error:
75
+ raise FormattingError(str(error)) from None
76
+ raise FormattingError("the formatted code differs from the source")
77
+ return text
78
+
79
+
80
+ def _run_with_big_stack(work: Callable[[], T]) -> T:
81
+ result: list[T] = []
82
+ failure: list[BaseException] = []
83
+
84
+ def target() -> None:
85
+ try:
86
+ result.append(work())
87
+ except RecursionError:
88
+ failure.append(FormattingError("the file is nested too deeply to format"))
89
+ except BaseException as error: # noqa: BLE001 - re-raised in the caller's thread
90
+ failure.append(error)
91
+
92
+ with _lock:
93
+ old_limit = sys.getrecursionlimit()
94
+ old_size = threading.stack_size()
95
+ try:
96
+ threading.stack_size(_STACK_SIZE)
97
+ sys.setrecursionlimit(max(old_limit, _RECURSION_LIMIT))
98
+ worker = threading.Thread(target=target, name="prettycsharp-format")
99
+ worker.start()
100
+ worker.join()
101
+ finally:
102
+ threading.stack_size(old_size)
103
+ sys.setrecursionlimit(old_limit)
104
+ if failure:
105
+ raise failure[0]
106
+ return result[0]