ripple-down-rules 0.3.0__py3-none-any.whl → 0.4.1__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.
@@ -0,0 +1,635 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from copy import copy
5
+ from types import MethodType
6
+
7
+ from PyQt6.QtCore import Qt
8
+ from PyQt6.QtGui import QPixmap, QPainter, QPalette
9
+ from PyQt6.QtWidgets import (
10
+ QWidget, QVBoxLayout, QLabel, QScrollArea,
11
+ QSizePolicy, QToolButton, QHBoxLayout, QPushButton, QMainWindow, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem
12
+ )
13
+ from qtconsole.inprocess import QtInProcessKernelManager
14
+ from qtconsole.rich_jupyter_widget import RichJupyterWidget
15
+ from typing_extensions import Optional, Any, List, Dict
16
+
17
+ from ..datastructures.dataclasses import CaseQuery
18
+ from ..datastructures.enums import PromptFor
19
+ from .template_file_creator import TemplateFileCreator
20
+ from ..utils import is_iterable, contains_return_statement, encapsulate_user_input
21
+ from .object_diagram import generate_object_graph
22
+
23
+
24
+ class ImageViewer(QGraphicsView):
25
+ def __init__(self):
26
+ super().__init__()
27
+ self.setScene(QGraphicsScene(self))
28
+ self.setDragMode(QGraphicsView.ScrollHandDrag)
29
+ self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
30
+ self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
31
+ self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
32
+ self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
33
+
34
+ self.pixmap_item = None
35
+
36
+ self._zoom = 0
37
+
38
+ def update_image(self, image_path: str):
39
+ if self.pixmap_item is None:
40
+ self.pixmap_item = QGraphicsPixmapItem()
41
+ self.scene().addItem(self.pixmap_item)
42
+ pixmap = QPixmap(image_path)
43
+ self.pixmap_item.setPixmap(pixmap)
44
+ self.setSceneRect(self.pixmap_item.boundingRect())
45
+
46
+ def wheelEvent(self, event):
47
+ # Zoom in or out with Ctrl + mouse wheel
48
+ if event.modifiers() == Qt.ControlModifier:
49
+ angle = event.angleDelta().y()
50
+ factor = 1.2 if angle > 0 else 0.8
51
+
52
+ self._zoom += 1 if angle > 0 else -1
53
+ if self._zoom > 10: # max zoom in limit
54
+ self._zoom = 10
55
+ return
56
+ if self._zoom < -10: # max zoom out limit
57
+ self._zoom = -10
58
+ return
59
+
60
+ self.scale(factor, factor)
61
+ else:
62
+ super().wheelEvent(event)
63
+
64
+
65
+ class BackgroundWidget(QWidget):
66
+ def __init__(self, image_path, parent=None):
67
+ super().__init__(parent)
68
+ self.pixmap = QPixmap(image_path)
69
+
70
+ # Layout for buttons
71
+ self.layout = QVBoxLayout(self)
72
+ self.layout.setContentsMargins(20, 20, 20, 20)
73
+ self.layout.setSpacing(10)
74
+
75
+ accept_btn = QPushButton("Accept")
76
+ accept_btn.setStyleSheet("background-color: #4CAF50; color: white;") # Green button
77
+ edit_btn = QPushButton("Edit")
78
+ edit_btn.setStyleSheet("background-color: #2196F3; color: white;") # Blue button
79
+
80
+ self.layout.addWidget(accept_btn)
81
+ self.layout.addWidget(edit_btn)
82
+ self.layout.addStretch() # Push buttons to top
83
+
84
+ def paintEvent(self, event):
85
+ painter = QPainter(self)
86
+ if not self.pixmap.isNull():
87
+ # Calculate the vertical space used by buttons
88
+ button_area_height = 0
89
+ for i in range(self.layout.count()):
90
+ item = self.layout.itemAt(i)
91
+ if item.widget():
92
+ button_area_height += item.widget().height() + self.layout.spacing()
93
+
94
+ remaining_height = self.height() - button_area_height
95
+ if remaining_height <= 0:
96
+ return # No space to draw
97
+
98
+ # Scale image to the remaining area (width, height)
99
+ scaled = self.pixmap.scaled(
100
+ self.width(),
101
+ remaining_height,
102
+ Qt.AspectRatioMode.KeepAspectRatio,
103
+ Qt.TransformationMode.SmoothTransformation
104
+ )
105
+
106
+ x = (self.width() - scaled.width()) // 2
107
+
108
+ # Draw the image starting just below the buttons
109
+ painter.drawPixmap(x, button_area_height + 20, scaled)
110
+
111
+ def resizeEvent(self, event):
112
+ self.update() # Force repaint on resize
113
+ super().resizeEvent(event)
114
+
115
+
116
+ class CollapsibleBox(QWidget):
117
+ def __init__(self, title="", parent=None, viewer: Optional[RDRCaseViewer]=None, chain_name: Optional[str] = None,
118
+ main_obj: Optional[Dict[str, Any]] = None):
119
+ super().__init__(parent)
120
+
121
+ self.viewer = viewer
122
+ self.chain_name = chain_name
123
+ self.main_obj = main_obj
124
+ self.toggle_button = QToolButton(checkable=True, checked=False)
125
+ self.toggle_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
126
+ self.toggle_button.setArrowType(Qt.ArrowType.RightArrow)
127
+ self.toggle_button.clicked.connect(self.toggle)
128
+ self.toggle_button.setStyleSheet("""
129
+ QToolButton {
130
+ border: none;
131
+ font-weight: bold;
132
+ color: #FFA07A; /* Light orange */
133
+ }
134
+ """)
135
+ self.title_label = QLabel(title)
136
+ self.title_label.setTextFormat(Qt.TextFormat.RichText) # Enable HTML rendering
137
+ self.title_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
138
+ self.title_label.setStyleSheet("QLabel { padding: 1px; color: #FFA07A; }")
139
+
140
+ self.content_area = QWidget()
141
+ self.content_area.setVisible(False)
142
+ self.content_layout = QVBoxLayout(self.content_area)
143
+ self.content_layout.setContentsMargins(15, 2, 0, 2)
144
+ self.content_layout.setSpacing(2)
145
+
146
+ layout = QVBoxLayout(self)
147
+ header_layout = QHBoxLayout()
148
+ header_layout.addWidget(self.toggle_button)
149
+ header_layout.addWidget(self.title_label)
150
+ layout.addLayout(header_layout)
151
+ layout.addWidget(self.content_area)
152
+ layout.setContentsMargins(0, 0, 0, 0)
153
+ layout.setSpacing(2)
154
+ self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
155
+
156
+ def toggle(self, object_diagram_only=False):
157
+ is_expanded = self.toggle_button.isChecked()
158
+ self.update_object_diagram(is_expanded)
159
+ if object_diagram_only:
160
+ return
161
+ self.toggle_button.setArrowType(
162
+ Qt.ArrowType.DownArrow if is_expanded else Qt.ArrowType.RightArrow
163
+ )
164
+ self.content_area.setVisible(is_expanded)
165
+
166
+ # toggle children
167
+ if not is_expanded:
168
+ for i in range(self.content_layout.count()):
169
+ item = self.content_layout.itemAt(i)
170
+ if isinstance(item.widget(), CollapsibleBox):
171
+ item.widget().toggle_button.setChecked(False)
172
+ item.widget().toggle()
173
+
174
+ def update_object_diagram(self, is_expanded):
175
+ if is_expanded and self.viewer is not None:
176
+ self.viewer.included_attrs.append(self.chain_name)
177
+ main_obj_name = self.chain_name.split('.')[0]
178
+ main_obj = self.main_obj.get(main_obj_name)
179
+ self.viewer.update_object_diagram(
180
+ main_obj, main_obj_name
181
+ )
182
+ elif self.viewer is not None and self.chain_name in self.viewer.included_attrs:
183
+ for name in self.viewer.included_attrs:
184
+ if name.startswith(self.chain_name):
185
+ self.viewer.included_attrs.remove(name)
186
+ main_obj_name = self.chain_name.split('.')[0]
187
+ main_obj = self.main_obj.get(main_obj_name)
188
+ self.viewer.update_object_diagram(
189
+ main_obj, main_obj_name)
190
+
191
+ def add_widget(self, widget):
192
+ self.content_layout.addWidget(widget)
193
+
194
+ def adjust_size_recursive(self):
195
+ # Trigger resize
196
+ self.adjustSize()
197
+
198
+ # Traverse upwards to main window and call adjustSize on it too
199
+ parent = self.parent()
200
+ while parent:
201
+ if isinstance(parent, QWidget):
202
+ parent.layout().activate() # Force layout refresh
203
+ parent.adjustSize()
204
+ elif isinstance(parent, QScrollArea):
205
+ parent.widget().adjustSize()
206
+ parent.viewport().update()
207
+ if isinstance(parent, BackgroundWidget):
208
+ parent.update()
209
+ parent.updateGeometry()
210
+ parent.repaint()
211
+ if parent.parent() is None:
212
+ top_window = parent.window() # The main top-level window
213
+ top_window.updateGeometry()
214
+ top_window.repaint()
215
+ parent = parent.parent()
216
+
217
+
218
+ def python_colored_repr(value):
219
+ if isinstance(value, str):
220
+ return f'<span style="color:#90EE90;">"{value}"</span>'
221
+ elif isinstance(value, (int, float)):
222
+ return f'<span style="color:#ADD8E6;">{value}</span>'
223
+ elif isinstance(value, bool) or value is None:
224
+ return f'<span style="color:darkorange;">{value}</span>'
225
+ elif isinstance(value, type):
226
+ return f'<span style="color:#C1BCBB;">{{{value.__name__}}}</span>'
227
+ elif callable(value):
228
+ return ''
229
+ else:
230
+ try:
231
+ return f'<span style="color:white;">{repr(value)}</span>'
232
+ except Exception as e:
233
+ return f'<span style="color:red;">&lt;error: {e}&gt;</span>'
234
+
235
+
236
+ def style(text, color=None, font_size=None, font_weight=None):
237
+ s = '<span style="'
238
+ if color:
239
+ s += f'color:{color_name_to_html(color)};'
240
+ if font_size:
241
+ s += f'font-size:{font_size}px;'
242
+ if font_weight:
243
+ s += f'font-weight:{font_weight};'
244
+ s += '">'
245
+ s += text
246
+ s += '</span>'
247
+ return s
248
+
249
+
250
+ def color_name_to_html(color_name):
251
+ single_char_to_name = {
252
+ 'r': 'red',
253
+ 'g': 'green',
254
+ 'b': 'blue',
255
+ 'o': 'orange',
256
+ }
257
+ color_map = {
258
+ 'red': '#d6336c',
259
+ 'green': '#2eb872',
260
+ 'blue': '#007acc',
261
+ 'orange': '#FFA07A',
262
+ }
263
+ if len(color_name) == 1:
264
+ color_name = single_char_to_name.get(color_name, color_name)
265
+ return color_map.get(color_name.lower(), color_name) # Default to the name itself if not found
266
+
267
+
268
+ class RDRCaseViewer(QMainWindow):
269
+ case_query: Optional[CaseQuery] = None
270
+ prompt_for: Optional[PromptFor] = None
271
+ code_to_modify: Optional[str] = None
272
+ template_file_creator: Optional[TemplateFileCreator] = None
273
+ code_lines: Optional[List[str]] = None
274
+ included_attrs: Optional[List[str]] = None
275
+ main_obj: Optional[Dict[str, Any]] = None
276
+ user_input: Optional[str] = None
277
+ attributes_widget: Optional[QWidget] = None
278
+
279
+ def __init__(self, parent=None):
280
+ super().__init__(parent)
281
+ self.setWindowTitle("RDR Case Viewer")
282
+
283
+ self.setBaseSize(1600, 600) # or your preferred initial size
284
+
285
+ main_widget = QWidget()
286
+ self.setCentralWidget(main_widget)
287
+ self.setStyleSheet("background-color: #333333;")
288
+ main_widget.setStyleSheet("background-color: #333333;")
289
+
290
+ main_layout = QHBoxLayout(main_widget) # Horizontal layout to split window
291
+
292
+ # === Left: Attributes ===
293
+ self._create_attribute_widget()
294
+
295
+ # === Middle: Ipython & Action buttons ===
296
+ middle_widget = QWidget()
297
+ self.middle_widget_layout = QVBoxLayout(middle_widget)
298
+
299
+ self.title_label = self.create_label_widget(style(style(f"Welcome to {style('RDRViewer', 'b')} "
300
+ f"{style('App', 'g')}")
301
+ , 'o', 28, 'bold'))
302
+
303
+ self.buttons_widget = self.create_buttons_widget()
304
+
305
+ self.ipython_console = IPythonConsole(parent=self)
306
+
307
+ self.middle_widget_layout.addWidget(self.title_label)
308
+ self.middle_widget_layout.addWidget(self.ipython_console)
309
+ self.middle_widget_layout.addWidget(self.buttons_widget)
310
+
311
+ # === Right: Object Diagram ===
312
+ self.obj_diagram_viewer = ImageViewer() # put your image path here
313
+
314
+ # Add both to main layout
315
+ main_layout.addWidget(self.attributes_widget, stretch=1)
316
+ main_layout.addWidget(middle_widget, stretch=2)
317
+ main_layout.addWidget(self.obj_diagram_viewer, stretch=2)
318
+
319
+ def print(self, msg):
320
+ """
321
+ Print a message to the console.
322
+ """
323
+ self.ipython_console._append_plain_text(msg + '\n', True)
324
+
325
+ def update_for_case_query(self, case_query: CaseQuery, title_txt: Optional[str] = None,
326
+ prompt_for: Optional[PromptFor] = None, code_to_modify: Optional[str] = None):
327
+ self.case_query = case_query
328
+ self.prompt_for = prompt_for
329
+ self.code_to_modify = code_to_modify
330
+ title_text = title_txt or ""
331
+ case_attr_type = ', '.join([t.__name__ for t in case_query.core_attribute_type])
332
+ case_attr_type = style(f"{case_attr_type}", 'g', 28, 'bold')
333
+ case_name = style(f"{case_query.name}", 'b', 28, 'bold')
334
+ title_text = style(f"{title_text} {case_name} of type {case_attr_type}", 'o', 28, 'bold')
335
+ self.update_for_object(case_query.case, case_query.case_name, case_query.scope, title_text)
336
+
337
+ def update_for_object(self, obj: Any, name: str, scope: Optional[dict] = None,
338
+ title_text: Optional[str] = None):
339
+ self.update_main_obj(obj, name)
340
+ title_text = title_text or style(f"{name}", 'o', 28, 'bold')
341
+ scope = scope or {}
342
+ scope.update({name: obj})
343
+ self.update_object_diagram(obj, name)
344
+ self.update_attribute_layout(obj, name)
345
+ self.title_label.setText(title_text)
346
+ self.ipython_console.update_namespace(scope)
347
+
348
+ def update_main_obj(self, obj, name):
349
+ self.main_obj = {name: obj}
350
+ self.included_attrs = []
351
+ self.user_input = None
352
+
353
+ def update_object_diagram(self, obj: Any, name: str):
354
+ self.included_attrs = self.included_attrs or []
355
+ graph = generate_object_graph(obj, name, included_attrs=self.included_attrs)
356
+ graph.render("object_diagram", view=False, format='svg')
357
+ self.obj_diagram_viewer.update_image("object_diagram.svg")
358
+
359
+ def _create_attribute_widget(self):
360
+ main_widget = QWidget()
361
+ main_layout = QVBoxLayout(main_widget)
362
+
363
+ buttons_widget = QWidget()
364
+ buttons_layout = QHBoxLayout(buttons_widget)
365
+
366
+ expand_btn = QPushButton("Expand All")
367
+ expand_btn.clicked.connect(self.expand_all)
368
+ expand_btn.setStyleSheet(f"background-color: white; color: black;") # Green button
369
+ buttons_layout.addWidget(expand_btn)
370
+
371
+ collapse_btn = QPushButton("Collapse All")
372
+ collapse_btn.clicked.connect(self.collapse_all)
373
+ collapse_btn.setStyleSheet(f"background-color: white; color: black;") # Green button
374
+ buttons_layout.addWidget(collapse_btn)
375
+
376
+ main_layout.addWidget(buttons_widget)
377
+
378
+ scroll = QScrollArea()
379
+ scroll.setWidgetResizable(True)
380
+ scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
381
+
382
+ attr_widget = QWidget()
383
+ self.attr_widget_layout = QVBoxLayout(attr_widget)
384
+ self.attr_widget_layout.setSpacing(2)
385
+ self.attr_widget_layout.setContentsMargins(6, 6, 6, 6)
386
+ attr_widget.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
387
+
388
+ scroll.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
389
+ scroll.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
390
+ scroll.setWidget(attr_widget)
391
+ main_layout.addWidget(scroll)
392
+ self.attributes_widget = main_widget
393
+
394
+ def expand_all(self):
395
+ # Expand all collapsible boxes
396
+ for i in range(self.attr_widget_layout.count()):
397
+ item = self.attr_widget_layout.itemAt(i)
398
+ if isinstance(item.widget(), CollapsibleBox):
399
+ self.expand_collapse_all(item.widget(), expand=True)
400
+
401
+ def collapse_all(self):
402
+ # Collapse all collapsible boxes
403
+ for i in range(self.attr_widget_layout.count()):
404
+ item = self.attr_widget_layout.itemAt(i)
405
+ if isinstance(item.widget(), CollapsibleBox):
406
+ self.expand_collapse_all(item.widget(), expand=False)
407
+
408
+ def expand_collapse_all(self, widget, expand=True, curr_depth=0, max_depth=2):
409
+ widget.toggle_button.setChecked(expand)
410
+ widget.toggle()
411
+ if expand and curr_depth < max_depth:
412
+ # do it for recursive children
413
+ for i in range(widget.content_layout.count()):
414
+ item = widget.content_layout.itemAt(i)
415
+ if isinstance(item.widget(), CollapsibleBox):
416
+ self.expand_collapse_all(item.widget(), expand=True, curr_depth=curr_depth + 1, max_depth=max_depth)
417
+
418
+
419
+
420
+ def create_buttons_widget(self):
421
+ button_widget = QWidget()
422
+ button_widget_layout = QHBoxLayout(button_widget)
423
+
424
+ accept_btn = QPushButton("Accept")
425
+ accept_btn.clicked.connect(self._accept)
426
+ accept_btn.setStyleSheet(f"background-color: {color_name_to_html('g')}; color: white;") # Green button
427
+
428
+ edit_btn = QPushButton("Edit")
429
+ edit_btn.clicked.connect(self._edit)
430
+ edit_btn.setStyleSheet(f"background-color: {color_name_to_html('o')}; color: white;") # Orange button
431
+
432
+ load_btn = QPushButton("Load")
433
+ load_btn.clicked.connect(self._load)
434
+ load_btn.setStyleSheet(f"background-color: {color_name_to_html('b')}; color: white;") # Blue button
435
+
436
+ button_widget_layout.addWidget(accept_btn)
437
+ button_widget_layout.addWidget(edit_btn)
438
+ button_widget_layout.addWidget(load_btn)
439
+ return button_widget
440
+
441
+ def _accept(self):
442
+ # close the window
443
+ self.close()
444
+
445
+ def _edit(self):
446
+ self.template_file_creator = TemplateFileCreator(self.ipython_console.kernel.shell,
447
+ self.case_query, self.prompt_for, self.code_to_modify,
448
+ self.print)
449
+ self.template_file_creator.edit()
450
+
451
+ def _load(self):
452
+ if not self.template_file_creator:
453
+ return
454
+ self.code_lines = self.template_file_creator.load()
455
+ if self.code_lines is not None:
456
+ self.user_input = encapsulate_code_lines_into_a_function(
457
+ self.code_lines, self.template_file_creator.func_name,
458
+ self.template_file_creator.function_signature,
459
+ self.template_file_creator.func_doc, self.case_query)
460
+ self.template_file_creator = None
461
+
462
+ def update_attribute_layout(self, obj, name: str):
463
+ # Clear the existing layout
464
+ clear_layout(self.attr_widget_layout)
465
+
466
+ # Re-add the collapsible box with the new object
467
+ self.add_collapsible(name, obj, self.attr_widget_layout, 0, 3, chain_name=name)
468
+ self.attr_widget_layout.addStretch()
469
+
470
+ def create_label_widget(self, text):
471
+ # Create a QLabel with rich text
472
+ label = QLabel()
473
+ label.setText(text)
474
+ label.setAlignment(Qt.AlignCenter) # Optional: center the text
475
+ label.setWordWrap(True) # Optional: allow wrapping if needed
476
+ return label
477
+
478
+ def add_attributes(self, obj, name, layout, current_depth=0, max_depth=3, chain_name=None):
479
+ if current_depth > max_depth:
480
+ return
481
+ if isinstance(obj, dict):
482
+ items = obj.items()
483
+ elif isinstance(obj, (list, tuple, set)):
484
+ items = enumerate(obj)
485
+ else:
486
+ methods = []
487
+ attributes = []
488
+ iterables = []
489
+ for attr in dir(obj):
490
+ if attr.startswith("_") or attr == "scope":
491
+ continue
492
+ try:
493
+ value = getattr(obj, attr)
494
+ if callable(value):
495
+ methods.append((attr, value))
496
+ continue
497
+ elif is_iterable(value):
498
+ iterables.append((attr, value))
499
+ continue
500
+ except Exception as e:
501
+ value = f"<error: {e}>"
502
+ attributes.append((attr, value))
503
+ items = attributes + iterables + methods
504
+ chain_name = chain_name if chain_name is not None else name
505
+ for attr, value in items:
506
+ attr = f"{attr}"
507
+ try:
508
+ if is_iterable(value) or hasattr(value, "__dict__") and not inspect.isfunction(value):
509
+ self.add_collapsible(attr, value, layout, current_depth + 1, max_depth, chain_name=f"{chain_name}.{attr}")
510
+ else:
511
+ self.add_non_collapsible(attr, value, layout)
512
+ except Exception as e:
513
+ err = QLabel(f"<b>{attr}</b>: <span style='color:red;'>&lt;error: {e}&gt;</span>")
514
+ err.setTextFormat(Qt.TextFormat.RichText)
515
+ layout.addWidget(err)
516
+
517
+ def add_collapsible(self, attr, value, layout, current_depth, max_depth, chain_name=None):
518
+ type_name = type(value) if not isinstance(value, type) else value
519
+ collapsible = CollapsibleBox(
520
+ f'<b><span style="color:#FFA07A;">{attr}</span></b> {python_colored_repr(type_name)}', viewer=self,
521
+ chain_name=chain_name, main_obj=self.main_obj)
522
+ self.add_attributes(value, attr, collapsible.content_layout, current_depth, max_depth, chain_name=chain_name)
523
+ layout.addWidget(collapsible)
524
+
525
+ def add_non_collapsible(self, attr, value, layout):
526
+ type_name = type(value) if not isinstance(value, type) else value
527
+ text = f'<b><span style="color:#FFA07A;">{attr}</span></b> {python_colored_repr(type_name)}: {python_colored_repr(value)}'
528
+ item_label = QLabel()
529
+ item_label.setTextFormat(Qt.TextFormat.RichText)
530
+ item_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
531
+ item_label.setStyleSheet("QLabel { padding: 1px; color: #FFA07A; }")
532
+ item_label.setText(text)
533
+ item_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
534
+ layout.addWidget(item_label)
535
+
536
+
537
+ def encapsulate_code_lines_into_a_function(code_lines: List[str], function_name: str, function_signature: str,
538
+ func_doc: str, case_query: CaseQuery) -> str:
539
+ """
540
+ Encapsulate the given code lines into a function with the specified name, signature, and docstring.
541
+
542
+ :param code_lines: The lines of code to include in the user input.
543
+ :param function_name: The name of the function to include in the user input.
544
+ :param function_signature: The function signature to include in the user input.
545
+ :param func_doc: The function docstring to include in the user input.
546
+ :param case_query: The case query object.
547
+ """
548
+ code = '\n'.join(code_lines)
549
+ code = encapsulate_user_input(code, function_signature, func_doc)
550
+ if case_query.is_function:
551
+ args = "**case"
552
+ else:
553
+ args = "case"
554
+ if f"return {function_name}({args})" not in code:
555
+ code = code.strip() + f"\nreturn {function_name}({args})"
556
+ return code
557
+
558
+
559
+ class IPythonConsole(RichJupyterWidget):
560
+ def __init__(self, namespace=None, parent=None):
561
+ super(IPythonConsole, self).__init__(parent)
562
+
563
+ self.kernel_manager = QtInProcessKernelManager()
564
+ self.kernel_manager.start_kernel()
565
+ self.kernel = self.kernel_manager.kernel
566
+ self.kernel.gui = 'qt'
567
+ self.command_log = []
568
+
569
+ # Monkey patch its run_cell method
570
+ def custom_run_cell(this, raw_cell, **kwargs):
571
+ print(raw_cell)
572
+ if contains_return_statement(raw_cell) and 'def ' not in raw_cell:
573
+ if self.parent.template_file_creator and self.parent.template_file_creator.func_name in raw_cell:
574
+ self.command_log = self.parent.code_lines
575
+ self.command_log.append(raw_cell)
576
+ this.history_manager.store_inputs(line_num=this.execution_count, source=raw_cell)
577
+ return None
578
+ result = original_run_cell(raw_cell, **kwargs)
579
+ if result.error_in_exec is None and result.error_before_exec is None:
580
+ self.command_log.append(raw_cell)
581
+ return result
582
+
583
+ original_run_cell = self.kernel.shell.run_cell
584
+ self.kernel.shell.run_cell = MethodType(custom_run_cell, self.kernel.shell)
585
+
586
+ self.kernel_client = self.kernel_manager.client()
587
+ self.kernel_client.start_channels()
588
+
589
+ # Update the user namespace with your custom variables
590
+ if namespace:
591
+ self.update_namespace(namespace)
592
+
593
+ # Set the underlying QTextEdit's palette
594
+ palette = QPalette()
595
+ self._control.setPalette(palette)
596
+
597
+ # Override the stylesheet to force background and text colors
598
+ # self._control.setStyleSheet("""
599
+ # background-color: #615f5f;
600
+ # color: #3ba8e7;
601
+ # selection-background-color: #006400;
602
+ # selection-color: white;
603
+ # """)
604
+
605
+ # Use a dark syntax style like monokai
606
+ # self.syntax_style = 'monokai'
607
+ self.set_default_style(colors='linux')
608
+
609
+ self.exit_requested.connect(self.stop)
610
+
611
+ def update_namespace(self, namespace):
612
+ """
613
+ Update the user namespace with new variables.
614
+ """
615
+ self.kernel.shell.user_ns.update(namespace)
616
+
617
+ def execute(self, source=None, hidden=False, interactive=False):
618
+ # Log the command before execution
619
+ source = source if source is not None else self.input_buffer
620
+ # self.command_log.append(source)
621
+ super().execute(source, hidden, interactive)
622
+
623
+ def stop(self):
624
+ self.kernel_client.stop_channels()
625
+ self.kernel_manager.shutdown_kernel()
626
+
627
+
628
+ def clear_layout(layout):
629
+ while layout.count():
630
+ item = layout.takeAt(0)
631
+ widget = item.widget()
632
+ if widget is not None:
633
+ widget.setParent(None)
634
+ elif item.layout() is not None:
635
+ clear_layout(item.layout())