handsdown-fork 0.1.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.
Files changed (74) hide show
  1. handsdown/__init__.py +4 -0
  2. handsdown/__main__.py +5 -0
  3. handsdown/ast_parser/__init__.py +28 -0
  4. handsdown/ast_parser/analyzers/__init__.py +1 -0
  5. handsdown/ast_parser/analyzers/base_analyzer.py +29 -0
  6. handsdown/ast_parser/analyzers/class_analyzer.py +176 -0
  7. handsdown/ast_parser/analyzers/expression_analyzer.py +740 -0
  8. handsdown/ast_parser/analyzers/function_analyzer.py +170 -0
  9. handsdown/ast_parser/analyzers/module_analyzer.py +210 -0
  10. handsdown/ast_parser/module_record_list.py +75 -0
  11. handsdown/ast_parser/node_records/__init__.py +1 -0
  12. handsdown/ast_parser/node_records/argument_record.py +107 -0
  13. handsdown/ast_parser/node_records/attribute_record.py +69 -0
  14. handsdown/ast_parser/node_records/class_record.py +136 -0
  15. handsdown/ast_parser/node_records/expression_record.py +60 -0
  16. handsdown/ast_parser/node_records/function_record.py +141 -0
  17. handsdown/ast_parser/node_records/import_record.py +96 -0
  18. handsdown/ast_parser/node_records/module_record.py +298 -0
  19. handsdown/ast_parser/node_records/node_record.py +159 -0
  20. handsdown/ast_parser/node_records/text_record.py +49 -0
  21. handsdown/ast_parser/smart_ast.py +160 -0
  22. handsdown/ast_parser/type_defs.py +24 -0
  23. handsdown/cli_parser.py +294 -0
  24. handsdown/constants.py +22 -0
  25. handsdown/exceptions.py +21 -0
  26. handsdown/generators/__init__.py +0 -0
  27. handsdown/generators/base.py +418 -0
  28. handsdown/generators/material.py +23 -0
  29. handsdown/generators/rtd.py +13 -0
  30. handsdown/jinja_manager.py +60 -0
  31. handsdown/loader.py +159 -0
  32. handsdown/main.py +63 -0
  33. handsdown/md_document.py +327 -0
  34. handsdown/processors/__init__.py +6 -0
  35. handsdown/processors/base.py +308 -0
  36. handsdown/processors/pep257.py +116 -0
  37. handsdown/processors/rst.py +147 -0
  38. handsdown/processors/section.py +43 -0
  39. handsdown/processors/section_block.py +28 -0
  40. handsdown/processors/section_map.py +107 -0
  41. handsdown/processors/smart.py +45 -0
  42. handsdown/py.typed +0 -0
  43. handsdown/templates/common/argument.py.jinja2 +10 -0
  44. handsdown/templates/common/class.md.jinja2 +31 -0
  45. handsdown/templates/common/class_signature.py.jinja2 +15 -0
  46. handsdown/templates/common/docstring.md.jinja2 +8 -0
  47. handsdown/templates/common/function.md.jinja2 +19 -0
  48. handsdown/templates/common/function_signature.py.jinja2 +21 -0
  49. handsdown/templates/common/gh_pages_config.yml.jinja2 +5 -0
  50. handsdown/templates/common/index.md.jinja2 +28 -0
  51. handsdown/templates/common/method.md.jinja2 +21 -0
  52. handsdown/templates/material/mkdocs.yml.jinja2 +41 -0
  53. handsdown/templates/material/module.md.jinja2 +56 -0
  54. handsdown/templates/material/readthedocs.yml.jinja2 +19 -0
  55. handsdown/templates/material/requirements.mkdocs.txt.jinja2 +2 -0
  56. handsdown/templates/readthedocs/mkdocs.yml.jinja2 +9 -0
  57. handsdown/templates/readthedocs/module.md.jinja2 +56 -0
  58. handsdown/templates/readthedocs/readthedocs.yml.jinja2 +15 -0
  59. handsdown/utils/__init__.py +1 -0
  60. handsdown/utils/blackify.py +33 -0
  61. handsdown/utils/docstring_formatter.py +66 -0
  62. handsdown/utils/import_string.py +206 -0
  63. handsdown/utils/indent_trimmer.py +157 -0
  64. handsdown/utils/logger.py +32 -0
  65. handsdown/utils/markdown.py +104 -0
  66. handsdown/utils/path.py +20 -0
  67. handsdown/utils/path_finder.py +204 -0
  68. handsdown/utils/strings.py +74 -0
  69. handsdown_fork-0.1.0.dist-info/METADATA +436 -0
  70. handsdown_fork-0.1.0.dist-info/RECORD +74 -0
  71. handsdown_fork-0.1.0.dist-info/WHEEL +5 -0
  72. handsdown_fork-0.1.0.dist-info/entry_points.txt +2 -0
  73. handsdown_fork-0.1.0.dist-info/licenses/LICENSE +22 -0
  74. handsdown_fork-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,740 @@
1
+ """AST analyzer for `ast.expr` records."""
2
+
3
+ import handsdown.ast_parser.smart_ast as ast
4
+ from handsdown.ast_parser.analyzers.base_analyzer import BaseAnalyzer
5
+ from handsdown.ast_parser.type_defs import ASTIterable, Node
6
+ from handsdown.constants import ENCODING
7
+ from handsdown.utils.logger import get_logger
8
+
9
+
10
+ class ExpressionAnalyzer(BaseAnalyzer):
11
+ """
12
+ AST analyzer for `ast.expr` records.
13
+
14
+ Prepares `parts` for `NodeRecord.render` method.
15
+ """
16
+
17
+ def __init__(self) -> None:
18
+ super().__init__()
19
+ self._logger = get_logger()
20
+ self.parts: list[Node] = []
21
+
22
+ # dummy value to replace unknown nodes and operators
23
+ UNKNOWN = "..."
24
+
25
+ # representation map for binary operators
26
+ BINOP_SYMBOLS: dict[type[ast.AST], str] = {
27
+ ast.Add: "+",
28
+ ast.Sub: "-",
29
+ ast.Mult: "*",
30
+ ast.Div: "/",
31
+ ast.Mod: "%",
32
+ ast.Pow: "**",
33
+ ast.LShift: "<<",
34
+ ast.RShift: ">>",
35
+ ast.BitOr: "|",
36
+ ast.BitXor: "^",
37
+ ast.BitAnd: "&",
38
+ ast.FloorDiv: "//",
39
+ }
40
+
41
+ # representation map for boolean operators
42
+ BOOLOP_SYMBOLS: dict[type[ast.AST], str] = {ast.And: "and", ast.Or: "or"}
43
+
44
+ # representation map for comparison operators
45
+ CMPOP_SYMBOLS: dict[type[ast.AST], str] = {
46
+ ast.Eq: "==",
47
+ ast.NotEq: "!=",
48
+ ast.Lt: "<",
49
+ ast.LtE: "<=",
50
+ ast.Gt: ">",
51
+ ast.GtE: ">=",
52
+ ast.Is: "is",
53
+ ast.IsNot: "is not",
54
+ ast.In: "in",
55
+ ast.NotIn: "not in",
56
+ }
57
+
58
+ # representation map for unary operators
59
+ UNARYOP_SYMBOLS: dict[type[ast.AST], str] = {
60
+ ast.Invert: "~",
61
+ ast.Not: "not",
62
+ ast.UAdd: "+",
63
+ ast.USub: "-",
64
+ }
65
+
66
+ def visit_Constant(self, node: ast.Constant) -> None:
67
+ """
68
+ Parse info from `ast.Str` node and put it to `parts`.
69
+
70
+ Examples::
71
+
72
+ "my_string"
73
+
74
+ Arguments:
75
+ node -- AST node.
76
+
77
+ """
78
+ value = node.value
79
+ if isinstance(value, bytes):
80
+ value = value.decode(ENCODING)
81
+ self.parts.append(repr(value))
82
+
83
+ def visit_Name(self, node: ast.Name) -> None:
84
+ """
85
+ Parse info from `ast.Name` node and put it to `parts`.
86
+
87
+ Examples::
88
+
89
+ my_value
90
+
91
+ Arguments:
92
+ node -- AST node.
93
+
94
+ """
95
+ self.parts.append(node.id)
96
+ self.related_names.append(node.id)
97
+
98
+ def visit_Subscript(self, node: ast.Subscript) -> None:
99
+ """
100
+ Parse info from `ast.Subscript` node and put it to `parts`.
101
+
102
+ Type annotations are also matched by this method.
103
+
104
+ Examples::
105
+
106
+ Union[Name, bool]
107
+ List[1:4]
108
+
109
+ Arguments:
110
+ node -- AST node.
111
+
112
+ """
113
+ self.parts.append(node.value)
114
+ self.parts.append("[")
115
+ if isinstance(node.slice, ast.Index) and isinstance(node.slice.value, ast.Tuple): # type: ignore
116
+ self._visit_iterable(node.slice.value) # type: ignore
117
+ elif isinstance(node.slice, ast.Tuple):
118
+ self._visit_iterable(node.slice)
119
+ else:
120
+ self._analyze_child(node.slice)
121
+ self.parts.append("]")
122
+
123
+ def visit_Attribute(self, node: ast.Attribute) -> None:
124
+ """
125
+ Parse info from `ast.Attribute` node and put it to `parts`.
126
+
127
+ Examples::
128
+
129
+ my_object.attribute
130
+
131
+ Arguments:
132
+ node -- AST node.
133
+
134
+ """
135
+ self.parts.append(node.value)
136
+ self.parts.append(".")
137
+ self.parts.append(node.attr)
138
+
139
+ def _visit_iterable(self, node: ASTIterable) -> None:
140
+ """
141
+ Parse info from an iterable node and put it to `parts`.
142
+
143
+ Used for `ast.Tuple`, `ast.Subscript`, `ast.List`, `ast.Set`
144
+
145
+ Examples::
146
+
147
+ [1, 2, 3]
148
+ {1, 2, 3}
149
+ (1, 2, 3)
150
+ Union[str, bool]
151
+
152
+ Arguments:
153
+ node -- AST node.
154
+
155
+ """
156
+ if node.elts:
157
+ for index, element in enumerate(node.elts):
158
+ if index:
159
+ self.parts.append(", ")
160
+ self._analyze_child(element)
161
+
162
+ def _analyze_child(self, node: ast.AST) -> None:
163
+ if isinstance(node, str):
164
+ self.parts.append(node)
165
+ return
166
+ analyzer = self.__class__()
167
+ analyzer.visit(node)
168
+ self.parts.extend(analyzer.parts)
169
+ self.related_names.extend(analyzer.related_names)
170
+
171
+ def visit_List(self, node: ast.List) -> None:
172
+ """
173
+ Parse info from `ast.List` node and put it to `parts`.
174
+
175
+ Examples::
176
+
177
+ [1, 2, 3]
178
+
179
+ Arguments:
180
+ node -- AST node.
181
+
182
+ """
183
+ self.parts.append("[")
184
+ self._visit_iterable(node)
185
+ self.parts.append("]")
186
+
187
+ def visit_Set(self, node: ast.Set) -> None:
188
+ """
189
+ Parse info from `ast.Set` node and put it to `parts`.
190
+
191
+ Examples::
192
+
193
+ {1, 2, 3}
194
+
195
+ Arguments:
196
+ node -- AST node.
197
+
198
+ """
199
+ self.parts.append("{")
200
+ self._visit_iterable(node)
201
+ self.parts.append("}")
202
+
203
+ def visit_Tuple(self, node: ast.Tuple) -> None:
204
+ """
205
+ Parse info from `ast.Tuple` node and put it to `parts`.
206
+
207
+ Examples::
208
+
209
+ (1, 2, 3)
210
+
211
+ Arguments:
212
+ node -- AST node.
213
+
214
+ """
215
+ self.parts.append("(")
216
+ self._visit_iterable(node)
217
+ self.parts.append(")")
218
+
219
+ def visit_Call(self, node: ast.Call) -> None:
220
+ """
221
+ Parse info from `ast.Call` node and put it to `parts`.
222
+
223
+ Arguments:
224
+ node -- AST node.
225
+
226
+ """
227
+ self.parts.append(node.func)
228
+ self.parts.append("(")
229
+
230
+ # FIXME: `AST2` ast.Call stores args argument in `starargs`
231
+ starargs = getattr(node, "starargs", None)
232
+
233
+ # FIXME: `AST2` ast.Call stores kwargs argument in `kwargs`
234
+ kwargs = getattr(node, "kwargs", None)
235
+
236
+ elements = [
237
+ *node.args,
238
+ *node.keywords,
239
+ *([starargs] if starargs else []),
240
+ *([kwargs] if kwargs else []),
241
+ ]
242
+ for index, element in enumerate(elements):
243
+ if index:
244
+ self.parts.append(", ")
245
+ if element is starargs:
246
+ self.parts.append("*")
247
+ if element is kwargs:
248
+ self.parts.append("**")
249
+
250
+ self.parts.append(element)
251
+
252
+ self.parts.append(")")
253
+
254
+ def visit_Starred(self, node: ast.Starred) -> None:
255
+ """
256
+ Parse info from `ast.Starred` node and put it to `parts`.
257
+
258
+ Examples::
259
+
260
+ *arg
261
+
262
+ Arguments:
263
+ node -- AST node.
264
+
265
+ """
266
+ self.parts.append("*")
267
+ self.parts.append(node.value)
268
+
269
+ def visit_keyword(self, node: ast.keyword) -> None:
270
+ """
271
+ Parse info from `ast.keyword` node and put it to `parts`.
272
+
273
+ Examples::
274
+
275
+ my_func(**{"kwarg": "value"})
276
+ my_func(kwarg="value")
277
+
278
+ Arguments:
279
+ node -- AST node.
280
+
281
+ """
282
+ if not node.arg:
283
+ self.parts.append("**")
284
+ self.parts.append(node.value)
285
+ return
286
+
287
+ self.parts.append(node.arg)
288
+ self.parts.append("=")
289
+ self.parts.append(node.value)
290
+
291
+ def visit_Dict(self, node: ast.Dict) -> None:
292
+ """
293
+ Parse info from `ast.Dict` node and put it to `parts`.
294
+
295
+ Arguments:
296
+ node -- AST node.
297
+
298
+ """
299
+ self.parts.append("{")
300
+ for index, key in enumerate(node.keys or []):
301
+ if key is None:
302
+ continue
303
+ if index:
304
+ self.parts.append(", ")
305
+ self.parts.append(key)
306
+ self.parts.append(": ")
307
+ self.parts.append(node.values[index])
308
+ self.parts.append("}")
309
+
310
+ def visit_Compare(self, node: ast.Compare) -> None:
311
+ """
312
+ Parse info from `ast.Compare` node and put it to `parts`.
313
+
314
+ Examples::
315
+
316
+ value < 5
317
+ 1 < weekday < 7
318
+
319
+ Arguments:
320
+ node -- AST node.
321
+
322
+ """
323
+ self.parts.append(node.left)
324
+ for index, right in enumerate(node.comparators):
325
+ operator_class = type(node.ops[index])
326
+ self.parts.append(" ")
327
+ operator = self.CMPOP_SYMBOLS.get(operator_class, self.UNKNOWN)
328
+ if operator == self.UNKNOWN:
329
+ self._logger.warning(f"Unknown comparison operator: {operator_class.__name__}")
330
+ self.parts.append(operator)
331
+ self.parts.append(" ")
332
+ self.parts.append(right)
333
+
334
+ def visit_BinOp(self, node: ast.BinOp) -> None:
335
+ """
336
+ Parse info from `ast.BinOp` node and put it to `parts`.
337
+
338
+ Examples::
339
+
340
+ 1 + 5
341
+ value + 1
342
+
343
+ Arguments:
344
+ node -- AST node.
345
+
346
+ """
347
+ self.parts.append(node.left)
348
+ self.parts.append(" ")
349
+ operator = self.BINOP_SYMBOLS.get(type(node.op), self.UNKNOWN)
350
+ if operator == self.UNKNOWN:
351
+ self._logger.warning(f"Unknown binary operator: {node.op.__class__.__name__}")
352
+ self.parts.append(operator)
353
+ self.parts.append(" ")
354
+ self.parts.append(node.right)
355
+
356
+ def visit_BoolOp(self, node: ast.BoolOp) -> None:
357
+ """
358
+ Parse info from `ast.BoolOp` node and put it to `parts`.
359
+
360
+ Examples::
361
+
362
+ value or True
363
+ a and b
364
+
365
+ Arguments:
366
+ node -- AST node.
367
+
368
+ """
369
+ operator = self.BOOLOP_SYMBOLS.get(type(node.op), self.UNKNOWN)
370
+ if operator == self.UNKNOWN:
371
+ self._logger.warning(f"Unknown boolean operator: {node.op.__class__.__name__}")
372
+ for index, value in enumerate(node.values):
373
+ if index:
374
+ self.parts.append(" ")
375
+ self.parts.append(operator)
376
+ self.parts.append(" ")
377
+ self.parts.append(value)
378
+
379
+ def visit_UnaryOp(self, node: ast.UnaryOp) -> None:
380
+ """
381
+ Parse info from `ast.UnaryOp` node and put it to `parts`.
382
+
383
+ Examples::
384
+
385
+ +5
386
+ -12
387
+ ~1
388
+ not True
389
+
390
+ Arguments:
391
+ node -- AST node.
392
+
393
+ """
394
+ operator = self.UNARYOP_SYMBOLS.get(type(node.op), self.UNKNOWN)
395
+ if operator == self.UNKNOWN:
396
+ self._logger.warning(f"Unknown unary operator: {node.op.__class__.__name__}")
397
+ self.parts.append(operator)
398
+ if operator == "not":
399
+ self.parts.append(" ")
400
+ self.parts.append(node.operand)
401
+
402
+ def visit_Lambda(self, node: ast.Lambda) -> None:
403
+ """
404
+ Parse info from `ast.Lambda` node and put it to `parts`.
405
+
406
+ Examples::
407
+
408
+ lambda x: x + 5
409
+
410
+ Arguments:
411
+ node -- AST node.
412
+
413
+ """
414
+ self.parts.append("lambda ")
415
+ self.parts.append(node.args)
416
+ self.parts.append(": ")
417
+ self.parts.append(node.body)
418
+
419
+ def visit_arguments(self, node: ast.arguments) -> None:
420
+ """
421
+ Parse info from `ast.arguments` node and put it to `parts`.
422
+
423
+ Examples::
424
+
425
+ def my_func(arg, *args, **kwargs)
426
+
427
+ Arguments:
428
+ node -- AST node.
429
+
430
+ """
431
+ arg_count = 0
432
+ for index, arg in enumerate(node.args):
433
+ if arg_count:
434
+ self.parts.append(", ")
435
+ arg_count += 1
436
+ default = None
437
+ default_index = len(node.args) - len(node.defaults) + index
438
+ if default_index < len(node.defaults):
439
+ default = node.defaults[default_index]
440
+
441
+ self.parts.append(arg)
442
+ if default is not None:
443
+ self.parts.append("=")
444
+ self.parts.append(default)
445
+ if node.vararg is not None:
446
+ if arg_count:
447
+ self.parts.append(", ")
448
+
449
+ arg_count += 1
450
+ self.parts.append("*")
451
+ self.parts.append(node.vararg)
452
+ if node.kwarg is not None:
453
+ if arg_count:
454
+ self.parts.append(", ")
455
+
456
+ arg_count += 1
457
+ self.parts.append("**")
458
+ self.parts.append(node.kwarg)
459
+
460
+ if arg_count:
461
+ self.parts.append(",")
462
+
463
+ def visit_arg(self, node: ast.arg) -> None:
464
+ """
465
+ Parse info from `ast.arg` node and put it to `parts`.
466
+
467
+ Examples::
468
+
469
+ def my_func(arg)
470
+ def my_func(arg: str)
471
+
472
+ Arguments:
473
+ node -- AST node.
474
+
475
+ """
476
+ self.parts.append(node.arg)
477
+ if node.annotation:
478
+ self.parts.append(": ")
479
+ self.parts.append(node.annotation)
480
+
481
+ def visit_Index(self, node: ast.Index) -> None:
482
+ """
483
+ Parse info from `ast.Index` node and put it to `parts`.
484
+
485
+ Examples::
486
+
487
+ Union[str, bool]
488
+ Union[str]
489
+
490
+ Arguments:
491
+ node -- AST node.
492
+
493
+ """
494
+ if isinstance(node.value, ast.Tuple): # type: ignore
495
+ self._visit_iterable(node.value) # type: ignore
496
+ return
497
+ self.parts.append(node.value) # type: ignore
498
+
499
+ def visit_Slice(self, node: ast.Slice) -> None:
500
+ """
501
+ Parse info from `ast.Slice` node and put it to `parts`.
502
+
503
+ Examples::
504
+
505
+ [1:]
506
+ [:2]
507
+ [1:2]
508
+ [1:2:-1]
509
+ [::-1]
510
+
511
+ Arguments:
512
+ node -- AST node.
513
+
514
+ """
515
+ if node.lower:
516
+ self.parts.append(node.lower)
517
+ self.parts.append(":")
518
+ if node.upper:
519
+ self.parts.append(node.upper)
520
+ if node.step:
521
+ self.parts.append(":")
522
+ self.parts.append(node.step)
523
+
524
+ def visit_JoinedStr(self, node: ast.JoinedStr) -> None:
525
+ """
526
+ Parse info from `ast.JoinedStr` node and put it to `parts`.
527
+
528
+ Examples::
529
+
530
+ f'str: {my_string}'
531
+
532
+ Arguments:
533
+ node -- AST node.
534
+
535
+ """
536
+ self.parts.append("f'")
537
+ for value in node.values:
538
+ if isinstance(value, ast.Constant):
539
+ str_value = value.value
540
+ if isinstance(str_value, bytes):
541
+ str_value = str_value.decode(ENCODING)
542
+ self.parts.append(str_value)
543
+ else:
544
+ self.parts.append(value)
545
+ self.parts.append("'")
546
+
547
+ def visit_FormattedValue(self, node: ast.FormattedValue) -> None:
548
+ """
549
+ Parse info from `ast.FormattedValue` node and put it to `parts`.
550
+
551
+ Examples::
552
+
553
+ f"{formatted_value}"
554
+
555
+ Arguments:
556
+ node -- AST node.
557
+
558
+ """
559
+ self.parts.append("{")
560
+ self.parts.append(node.value)
561
+ self.parts.append("}")
562
+
563
+ def visit_comprehension(self, node: ast.comprehension) -> None:
564
+ """
565
+ Parse info from `ast.comprehension` node and put it to `parts`.
566
+
567
+ Examples::
568
+
569
+ for k in range(3) if k > 0 if True
570
+
571
+ Arguments:
572
+ node -- AST node.
573
+
574
+ """
575
+ self.parts.append("for ")
576
+ self.parts.append(node.target)
577
+ self.parts.append(" in ")
578
+ self.parts.append(node.iter)
579
+ for expr in node.ifs:
580
+ self.parts.append(" if ")
581
+ self.parts.append(expr)
582
+
583
+ def visit_DictComp(self, node: ast.DictComp) -> None:
584
+ """
585
+ Parse info from `ast.DictComp` node and put it to `parts`.
586
+
587
+ Examples::
588
+
589
+ {k: 1 for k in range(3)}
590
+
591
+ Arguments:
592
+ node -- AST node.
593
+
594
+ """
595
+ self.parts.append("{")
596
+ self.parts.append(node.key)
597
+ self.parts.append(": ")
598
+ self.parts.append(node.value)
599
+ self.parts.append(" ")
600
+ for comprehension in node.generators:
601
+ self.parts.append(comprehension)
602
+ self.parts.append("}")
603
+
604
+ def visit_ListComp(self, node: ast.ListComp) -> None:
605
+ """
606
+ Parse info from `ast.ListComp` node and put it to `parts`.
607
+
608
+ Examples::
609
+
610
+ [k + 1 for k in range(3)]
611
+
612
+ Arguments:
613
+ node -- AST node.
614
+
615
+ """
616
+ self.parts.append("[")
617
+ self.parts.append(node.elt)
618
+ self.parts.append(" ")
619
+ for comprehension in node.generators:
620
+ self.parts.append(comprehension)
621
+ self.parts.append("]")
622
+
623
+ def visit_SetComp(self, node: ast.SetComp) -> None:
624
+ """
625
+ Parse info from `ast.SetComp` node and put it to `parts`.
626
+
627
+ Examples::
628
+
629
+ {k + 1 for k in range(3)}
630
+
631
+ Arguments:
632
+ node -- AST node.
633
+
634
+ """
635
+ self.parts.append("{")
636
+ self.parts.append(node.elt)
637
+ self.parts.append(" ")
638
+ for comprehension in node.generators:
639
+ self.parts.append(comprehension)
640
+ self.parts.append("}")
641
+
642
+ def visit_GeneratorExp(self, node: ast.GeneratorExp) -> None:
643
+ """
644
+ Parse info from `ast.GeneratorExp` node and put it to `parts`.
645
+
646
+ Examples::
647
+
648
+ (k + 1 for k in range(3))
649
+
650
+ Arguments:
651
+ node -- AST node.
652
+
653
+ """
654
+ self.parts.append("(")
655
+ self.parts.append(node.elt)
656
+ self.parts.append(" ")
657
+ for comprehension in node.generators:
658
+ self.parts.append(comprehension)
659
+ self.parts.append(")")
660
+
661
+ def visit_IfExp(self, node: ast.IfExp) -> None:
662
+ """
663
+ Parse info from `ast.IfExp` node and put it to `parts`.
664
+
665
+ Examples::
666
+
667
+ 5 if my_value else 6
668
+
669
+ Arguments:
670
+ node -- AST node.
671
+
672
+ """
673
+ self.parts.append(node.body)
674
+ self.parts.append(" if ")
675
+ self.parts.append(node.test)
676
+ self.parts.append(" else ")
677
+ self.parts.append(node.orelse)
678
+
679
+ def visit_Await(self, node: ast.Await) -> None:
680
+ """
681
+ Parse info from `ast.Await` node and put it to `parts`.
682
+
683
+ Examples::
684
+
685
+ await result
686
+
687
+ Arguments:
688
+ node -- AST node.
689
+
690
+ """
691
+ self.parts.append("await ")
692
+ self.parts.append(node.value)
693
+
694
+ def visit_Yield(self, node: ast.Yield) -> None:
695
+ """
696
+ Parse info from `ast.Yield` node and put it to `parts`.
697
+
698
+ Examples::
699
+
700
+ Yield:
701
+ yield value
702
+
703
+ Arguments:
704
+ node -- AST node.
705
+
706
+ """
707
+ self.parts.append("yield")
708
+ if node.value:
709
+ self.parts.append(" ")
710
+ self.parts.append(node.value)
711
+
712
+ def visit_YieldFrom(self, node: ast.YieldFrom) -> None:
713
+ """
714
+ Parse info from `ast.YieldFrom` node and put it to `parts`.
715
+
716
+ Examples::
717
+
718
+ yield from my_generator
719
+
720
+ Arguments:
721
+ node -- AST node.
722
+
723
+ """
724
+ self.parts.append("yield from ")
725
+ self.parts.append(node.value)
726
+
727
+ def generic_visit(self, node: ast.AST) -> None:
728
+ """
729
+ Parse info from an unknown `ast.AST` node and put `...` to `parts`.
730
+
731
+ Logs warning with node class.
732
+
733
+ Arguments:
734
+ node -- AST node.
735
+
736
+ """
737
+ self._logger.warning(
738
+ f"Could not render node {node.__class__.__name__}, replaced with `{self.UNKNOWN}`",
739
+ )
740
+ self.parts.append(self.UNKNOWN)