ripple-down-rules 0.6.29__py3-none-any.whl → 0.6.30__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.
- ripple_down_rules/__init__.py +1 -1
- ripple_down_rules/datastructures/case.py +12 -11
- ripple_down_rules/datastructures/dataclasses.py +87 -9
- ripple_down_rules/experts.py +98 -20
- ripple_down_rules/rdr.py +37 -26
- ripple_down_rules/rules.py +15 -1
- ripple_down_rules/user_interface/gui.py +59 -40
- ripple_down_rules/user_interface/ipython_custom_shell.py +36 -7
- ripple_down_rules/user_interface/prompt.py +41 -26
- ripple_down_rules/user_interface/template_file_creator.py +10 -8
- ripple_down_rules/utils.py +57 -8
- {ripple_down_rules-0.6.29.dist-info → ripple_down_rules-0.6.30.dist-info}/METADATA +1 -1
- ripple_down_rules-0.6.30.dist-info/RECORD +24 -0
- ripple_down_rules-0.6.29.dist-info/RECORD +0 -24
- {ripple_down_rules-0.6.29.dist-info → ripple_down_rules-0.6.30.dist-info}/WHEEL +0 -0
- {ripple_down_rules-0.6.29.dist-info → ripple_down_rules-0.6.30.dist-info}/licenses/LICENSE +0 -0
- {ripple_down_rules-0.6.29.dist-info → ripple_down_rules-0.6.30.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,19 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
2
|
|
3
3
|
import inspect
|
4
4
|
import logging
|
5
|
+
import os.path
|
5
6
|
from types import MethodType
|
6
7
|
|
8
|
+
from graphviz import Digraph, Source
|
9
|
+
|
7
10
|
try:
|
8
11
|
from PyQt6.QtCore import Qt
|
9
12
|
from PyQt6.QtGui import QPixmap, QPainter, QPalette
|
10
13
|
from PyQt6.QtWidgets import (
|
11
14
|
QWidget, QVBoxLayout, QLabel, QScrollArea,
|
12
|
-
QSizePolicy, QToolButton, QHBoxLayout, QPushButton, QMainWindow, QGraphicsView, QGraphicsScene,
|
15
|
+
QSizePolicy, QToolButton, QHBoxLayout, QPushButton, QMainWindow, QGraphicsView, QGraphicsScene,
|
16
|
+
QGraphicsPixmapItem
|
13
17
|
)
|
14
18
|
from qtconsole.inprocess import QtInProcessKernelManager
|
15
19
|
from qtconsole.rich_jupyter_widget import RichJupyterWidget
|
@@ -120,7 +124,7 @@ class BackgroundWidget(QWidget):
|
|
120
124
|
|
121
125
|
|
122
126
|
class CollapsibleBox(QWidget):
|
123
|
-
def __init__(self, title="", parent=None, viewer: Optional[RDRCaseViewer]=None, chain_name: Optional[str] = None,
|
127
|
+
def __init__(self, title="", parent=None, viewer: Optional[RDRCaseViewer] = None, chain_name: Optional[str] = None,
|
124
128
|
main_obj: Optional[Dict[str, Any]] = None):
|
125
129
|
super().__init__(parent)
|
126
130
|
|
@@ -259,12 +263,14 @@ def color_name_to_html(color_name):
|
|
259
263
|
'g': 'green',
|
260
264
|
'b': 'blue',
|
261
265
|
'o': 'orange',
|
266
|
+
'm': 'magenta',
|
262
267
|
}
|
263
268
|
color_map = {
|
264
269
|
'red': '#d6336c',
|
265
270
|
'green': '#2eb872',
|
266
271
|
'blue': '#007acc',
|
267
272
|
'orange': '#FFA07A',
|
273
|
+
'magenta': '#a22bcf',
|
268
274
|
}
|
269
275
|
if len(color_name) == 1:
|
270
276
|
color_name = single_char_to_name.get(color_name, color_name)
|
@@ -285,16 +291,10 @@ class RDRCaseViewer(QMainWindow):
|
|
285
291
|
instances: List[RDRCaseViewer] = []
|
286
292
|
exit_status: ExitStatus = ExitStatus.CLOSE
|
287
293
|
|
288
|
-
def __init__(self, parent=None
|
289
|
-
save_dir: Optional[str] = None,
|
290
|
-
save_model_name: Optional[str] = None):
|
294
|
+
def __init__(self, parent=None):
|
291
295
|
super().__init__(parent)
|
292
|
-
self.exit_status = ExitStatus.CLOSE
|
293
296
|
self.instances.clear()
|
294
297
|
self.instances.append(self)
|
295
|
-
self.save_dir = save_dir
|
296
|
-
self.save_model_name = save_model_name
|
297
|
-
|
298
298
|
self.setWindowTitle("RDR Case Viewer")
|
299
299
|
|
300
300
|
self.setBaseSize(1600, 600) # or your preferred initial size
|
@@ -333,35 +333,28 @@ class RDRCaseViewer(QMainWindow):
|
|
333
333
|
main_layout.addWidget(middle_widget, stretch=2)
|
334
334
|
main_layout.addWidget(self.obj_diagram_viewer, stretch=2)
|
335
335
|
|
336
|
-
def set_save_function(self, save_function: Callable[[str, str], None]) -> None:
|
337
|
-
"""
|
338
|
-
Set the function to save the file.
|
339
|
-
|
340
|
-
:param save_function: The function to save the file.
|
341
|
-
"""
|
342
|
-
self.save_function = save_function
|
343
|
-
self.save_btn.clicked.connect(lambda: self.save_function(self.save_dir, self.save_model_name))
|
344
|
-
|
345
336
|
def print(self, msg):
|
346
337
|
"""
|
347
338
|
Print a message to the console.
|
348
339
|
"""
|
349
340
|
self.ipython_console._append_plain_text(msg + '\n', True)
|
350
341
|
|
351
|
-
def update_for_case_query(self, case_query: CaseQuery,
|
352
|
-
prompt_for: Optional[PromptFor] = None, code_to_modify: Optional[str] = None
|
342
|
+
def update_for_case_query(self, case_query: CaseQuery, prompt_str: Optional[str] = None,
|
343
|
+
prompt_for: Optional[PromptFor] = None, code_to_modify: Optional[str] = None,
|
344
|
+
title: Optional[str] = None):
|
353
345
|
self.case_query = case_query
|
354
346
|
self.prompt_for = prompt_for
|
355
347
|
self.code_to_modify = code_to_modify
|
356
|
-
title_text =
|
348
|
+
title_text = title or ""
|
357
349
|
case_attr_type = ', '.join([t.__name__ for t in case_query.core_attribute_type])
|
358
350
|
case_attr_type = style(f"{case_attr_type}", 'g', 28, 'bold')
|
359
351
|
case_name = style(f"{case_query.name}", 'b', 28, 'bold')
|
360
352
|
title_text = style(f"{title_text} {case_name} of type {case_attr_type}", 'o', 28, 'bold')
|
361
|
-
self.update_for_object(case_query.case, case_query.case_name, case_query.scope, title_text
|
353
|
+
self.update_for_object(case_query.case, case_query.case_name, scope=case_query.scope, title_text=title_text,
|
354
|
+
header=prompt_str)
|
362
355
|
|
363
356
|
def update_for_object(self, obj: Any, name: str, scope: Optional[dict] = None,
|
364
|
-
title_text: Optional[str] = None):
|
357
|
+
title_text: Optional[str] = None, header: Optional[str] = None):
|
365
358
|
self.update_main_obj(obj, name)
|
366
359
|
title_text = title_text or style(f"{name}", 'o', 28, 'bold')
|
367
360
|
scope = scope or {}
|
@@ -370,6 +363,9 @@ class RDRCaseViewer(QMainWindow):
|
|
370
363
|
self.update_attribute_layout(obj, name)
|
371
364
|
self.title_label.setText(title_text)
|
372
365
|
self.ipython_console.update_namespace(scope)
|
366
|
+
if header is not None and len(header) > 0:
|
367
|
+
self.ipython_console.print(header)
|
368
|
+
self.exit_status = ExitStatus.CLOSE
|
373
369
|
|
374
370
|
def update_main_obj(self, obj, name):
|
375
371
|
self.main_obj = {name: obj}
|
@@ -441,8 +437,6 @@ class RDRCaseViewer(QMainWindow):
|
|
441
437
|
if isinstance(item.widget(), CollapsibleBox):
|
442
438
|
self.expand_collapse_all(item.widget(), expand=True, curr_depth=curr_depth + 1, max_depth=max_depth)
|
443
439
|
|
444
|
-
|
445
|
-
|
446
440
|
def create_buttons_widget(self):
|
447
441
|
button_widget = QWidget()
|
448
442
|
button_widget_layout = QVBoxLayout(button_widget)
|
@@ -466,13 +460,19 @@ class RDRCaseViewer(QMainWindow):
|
|
466
460
|
load_btn.clicked.connect(self._load)
|
467
461
|
load_btn.setStyleSheet(f"background-color: {color_name_to_html('b')}; color: white;") # Blue button
|
468
462
|
|
469
|
-
|
470
|
-
|
463
|
+
current_value_btn = QPushButton("Current Value")
|
464
|
+
current_value_btn.clicked.connect(self._show_current_value)
|
465
|
+
current_value_btn.setStyleSheet(f"background-color: {color_name_to_html('m')}; color: white;")
|
466
|
+
|
467
|
+
rule_tree_btn = QPushButton("Rule Tree")
|
468
|
+
rule_tree_btn.clicked.connect(self._show_rule_tree) # Placeholder for rule tree functionality
|
469
|
+
rule_tree_btn.setStyleSheet(f"background-color: {color_name_to_html('r')}; color: white;")
|
471
470
|
|
472
471
|
row_1_button_widget_layout.addWidget(edit_btn)
|
473
472
|
row_1_button_widget_layout.addWidget(load_btn)
|
474
|
-
row_1_button_widget_layout.addWidget(
|
475
|
-
row_2_button_widget_layout.addWidget(
|
473
|
+
row_1_button_widget_layout.addWidget(current_value_btn)
|
474
|
+
row_2_button_widget_layout.addWidget(rule_tree_btn)
|
475
|
+
row_2_button_widget_layout.addWidget(accept_btn)
|
476
476
|
return button_widget
|
477
477
|
|
478
478
|
def _accept(self):
|
@@ -481,16 +481,15 @@ class RDRCaseViewer(QMainWindow):
|
|
481
481
|
self.close()
|
482
482
|
|
483
483
|
def _edit(self):
|
484
|
-
self.template_file_creator =
|
485
|
-
self.print)
|
484
|
+
self.template_file_creator = self.create_template_file_creator()
|
486
485
|
self.template_file_creator.edit()
|
487
486
|
|
488
487
|
def _load(self):
|
489
488
|
if not self.template_file_creator:
|
490
489
|
return
|
491
490
|
self.code_lines, updates = self.template_file_creator.load(self.template_file_creator.temp_file_path,
|
492
|
-
|
493
|
-
|
491
|
+
self.template_file_creator.func_name,
|
492
|
+
self.template_file_creator.print_func)
|
494
493
|
self.ipython_console.kernel.shell.user_ns.update(updates)
|
495
494
|
if self.code_lines is not None:
|
496
495
|
self.user_input = encapsulate_code_lines_into_a_function(
|
@@ -498,7 +497,22 @@ class RDRCaseViewer(QMainWindow):
|
|
498
497
|
self.template_file_creator.function_signature,
|
499
498
|
self.template_file_creator.func_doc, self.case_query)
|
500
499
|
self.case_query.scope.update(updates)
|
501
|
-
|
500
|
+
|
501
|
+
def _show_current_value(self):
|
502
|
+
self.ipython_console.print(self.case_query.current_value_str)
|
503
|
+
|
504
|
+
def _show_rule_tree(self):
|
505
|
+
if self.case_query is None:
|
506
|
+
self.ipython_console.print("No case query provided.")
|
507
|
+
return
|
508
|
+
if not self.case_query.rdr:
|
509
|
+
self.ipython_console.print("No rule tree available for this case query.")
|
510
|
+
return
|
511
|
+
self.case_query.render_rule_tree(view=True)
|
512
|
+
|
513
|
+
def create_template_file_creator(self) -> TemplateFileCreator:
|
514
|
+
return TemplateFileCreator(self.case_query, self.prompt_for, self.code_to_modify,
|
515
|
+
self.print)
|
502
516
|
|
503
517
|
def update_attribute_layout(self, obj, name: str):
|
504
518
|
# Clear the existing layout
|
@@ -547,7 +561,8 @@ class RDRCaseViewer(QMainWindow):
|
|
547
561
|
attr = f"{attr}"
|
548
562
|
try:
|
549
563
|
if is_iterable(value) or hasattr(value, "__dict__") and not inspect.isfunction(value):
|
550
|
-
self.add_collapsible(attr, value, layout, current_depth + 1, max_depth,
|
564
|
+
self.add_collapsible(attr, value, layout, current_depth + 1, max_depth,
|
565
|
+
chain_name=f"{chain_name}.{attr}")
|
551
566
|
else:
|
552
567
|
self.add_non_collapsible(attr, value, layout)
|
553
568
|
except Exception as e:
|
@@ -559,7 +574,7 @@ class RDRCaseViewer(QMainWindow):
|
|
559
574
|
type_name = type(value) if not isinstance(value, type) else value
|
560
575
|
collapsible = CollapsibleBox(
|
561
576
|
f'<b><span style="color:#FFA07A;">{attr}</span></b> {python_colored_repr(type_name)}', viewer=self,
|
562
|
-
|
577
|
+
chain_name=chain_name, main_obj=self.main_obj)
|
563
578
|
self.add_attributes(value, attr, collapsible.content_layout, current_depth, max_depth, chain_name=chain_name)
|
564
579
|
layout.addWidget(collapsible)
|
565
580
|
|
@@ -587,7 +602,6 @@ class IPythonConsole(RichJupyterWidget):
|
|
587
602
|
|
588
603
|
# Monkey patch its run_cell method
|
589
604
|
def custom_run_cell(this, raw_cell, **kwargs):
|
590
|
-
print(raw_cell)
|
591
605
|
if contains_return_statement(raw_cell) and 'def ' not in raw_cell:
|
592
606
|
if self.parent.template_file_creator and self.parent.template_file_creator.func_name in raw_cell:
|
593
607
|
self.command_log = self.parent.code_lines
|
@@ -601,7 +615,6 @@ class IPythonConsole(RichJupyterWidget):
|
|
601
615
|
|
602
616
|
original_run_cell = self.kernel.shell.run_cell
|
603
617
|
self.kernel.shell.run_cell = MethodType(custom_run_cell, self.kernel.shell)
|
604
|
-
|
605
618
|
self.kernel_client = self.kernel_manager.client()
|
606
619
|
self.kernel_client.start_channels()
|
607
620
|
|
@@ -630,7 +643,7 @@ class IPythonConsole(RichJupyterWidget):
|
|
630
643
|
.in-prompt { font-weight: bold; color: %(in_prompt_color)s }
|
631
644
|
.out-prompt-number { font-weight: bold; color: %(out_prompt_number_color)s }
|
632
645
|
.out-prompt { font-weight: bold; color: %(out_prompt_color)s }
|
633
|
-
'''%dict(
|
646
|
+
''' % dict(
|
634
647
|
bgcolor='#0b0d0b', fgcolor='#47d9cc', select="#555",
|
635
648
|
in_prompt_number_color='lime', in_prompt_color='lime',
|
636
649
|
out_prompt_number_color='red', out_prompt_color='red'
|
@@ -648,6 +661,12 @@ class IPythonConsole(RichJupyterWidget):
|
|
648
661
|
"""
|
649
662
|
self.kernel.shell.user_ns.update(namespace)
|
650
663
|
|
664
|
+
def print(self, msg):
|
665
|
+
"""
|
666
|
+
Custom print function to append messages to the command log.
|
667
|
+
"""
|
668
|
+
self.execute(f"print(\"\\n\\n{msg}\")", hidden=True)
|
669
|
+
|
651
670
|
def execute(self, source=None, hidden=False, interactive=False):
|
652
671
|
# Log the command before execution
|
653
672
|
source = source if source is not None else self.input_buffer
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import logging
|
2
|
+
import os
|
2
3
|
from typing import Optional, List
|
3
4
|
|
4
5
|
from IPython.core.magic import magics_class, Magics, line_magic
|
@@ -6,9 +7,9 @@ from IPython.terminal.embed import InteractiveShellEmbed
|
|
6
7
|
from colorama import Fore, Style
|
7
8
|
from traitlets.config import Config
|
8
9
|
|
10
|
+
from .template_file_creator import TemplateFileCreator
|
9
11
|
from ..datastructures.dataclasses import CaseQuery
|
10
12
|
from ..datastructures.enums import PromptFor
|
11
|
-
from .template_file_creator import TemplateFileCreator
|
12
13
|
from ..utils import contains_return_statement, extract_dependencies, encapsulate_code_lines_into_a_function
|
13
14
|
|
14
15
|
|
@@ -20,6 +21,7 @@ class MyMagics(Magics):
|
|
20
21
|
prompt_for: Optional[PromptFor] = None,
|
21
22
|
case_query: Optional[CaseQuery] = None):
|
22
23
|
super().__init__(shell)
|
24
|
+
self.case_query: Optional[CaseQuery] = case_query
|
23
25
|
self.rule_editor = TemplateFileCreator(case_query, prompt_for=prompt_for, code_to_modify=code_to_modify)
|
24
26
|
self.all_code_lines: Optional[List[str]] = None
|
25
27
|
|
@@ -34,6 +36,29 @@ class MyMagics(Magics):
|
|
34
36
|
self.rule_editor.print_func)
|
35
37
|
self.shell.user_ns.update(updates)
|
36
38
|
|
39
|
+
@line_magic
|
40
|
+
def current_value(self, line):
|
41
|
+
"""
|
42
|
+
Display the current value of the attribute of the case.
|
43
|
+
"""
|
44
|
+
if self.case_query is None:
|
45
|
+
print(f"{Fore.RED}No case query provided.{Style.RESET_ALL}")
|
46
|
+
return
|
47
|
+
print(self.case_query.current_value_str)
|
48
|
+
|
49
|
+
@line_magic
|
50
|
+
def show_rule_tree(self, line):
|
51
|
+
"""
|
52
|
+
Display the rule tree for the current case query.
|
53
|
+
"""
|
54
|
+
if self.case_query is None:
|
55
|
+
print(f"{Fore.RED}No case query provided.{Style.RESET_ALL}")
|
56
|
+
return
|
57
|
+
if self.case_query.rdr is None:
|
58
|
+
print(f"{Fore.RED}No RDR available for the current case query.{Style.RESET_ALL}")
|
59
|
+
return
|
60
|
+
self.case_query.render_rule_tree(view=True)
|
61
|
+
|
37
62
|
@line_magic
|
38
63
|
def help(self, line):
|
39
64
|
"""
|
@@ -42,12 +67,16 @@ class MyMagics(Magics):
|
|
42
67
|
help_text = f"""
|
43
68
|
Directly write python code in the shell, and then `{Fore.GREEN}return {Fore.RESET}output`. Or use
|
44
69
|
the magic commands to write the code in a temporary file and edit it in PyCharm:
|
45
|
-
{Fore.MAGENTA}
|
70
|
+
{Fore.MAGENTA}%edit{Style.RESET_ALL}
|
46
71
|
Opens a temporary file in PyCharm for editing a function (conclusion or conditions for case)
|
47
72
|
that will be executed on the case object.
|
48
|
-
{Fore.MAGENTA}
|
73
|
+
{Fore.MAGENTA}%load{Style.RESET_ALL}
|
49
74
|
Loads the function defined in the temporary file into the user namespace, that can then be used inside the
|
50
75
|
Ipython shell. You can then do `{Fore.GREEN}return {Fore.RESET}function_name(case)`.
|
76
|
+
{Fore.MAGENTA}%current_value{Style.RESET_ALL}
|
77
|
+
Shows the current value of the case attribute on which the rule are being fit.
|
78
|
+
{Fore.MAGENTA}%show_rule_tree{Style.RESET_ALL}
|
79
|
+
Displays the rule tree for the current case query.
|
51
80
|
"""
|
52
81
|
print(help_text)
|
53
82
|
|
@@ -144,7 +173,7 @@ class IPythonShell:
|
|
144
173
|
self.user_input = None
|
145
174
|
else:
|
146
175
|
self.user_input = encapsulate_code_lines_into_a_function(self.all_code_lines,
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
176
|
+
function_name=self.shell.my_magics.rule_editor.func_name,
|
177
|
+
function_signature=self.shell.my_magics.rule_editor.function_signature,
|
178
|
+
func_doc=self.shell.my_magics.rule_editor.func_doc,
|
179
|
+
case_query=self.case_query)
|
@@ -1,7 +1,10 @@
|
|
1
1
|
import ast
|
2
2
|
import logging
|
3
|
+
import sys
|
3
4
|
from _ast import AST
|
4
5
|
|
6
|
+
from .. import logger
|
7
|
+
|
5
8
|
try:
|
6
9
|
from PyQt6.QtWidgets import QApplication
|
7
10
|
from .gui import RDRCaseViewer, style
|
@@ -29,7 +32,7 @@ class UserPrompt:
|
|
29
32
|
"""
|
30
33
|
shell_lock: RLock = RLock() # To ensure that only one thread can access the shell at a time
|
31
34
|
|
32
|
-
def __init__(self):
|
35
|
+
def __init__(self, prompt_user: bool = True):
|
33
36
|
"""
|
34
37
|
Initialize the UserPrompt class.
|
35
38
|
"""
|
@@ -51,18 +54,19 @@ class UserPrompt:
|
|
51
54
|
callable_expression: Optional[CallableExpression] = None
|
52
55
|
while True:
|
53
56
|
with self.shell_lock:
|
54
|
-
user_input, expression_tree = self.prompt_user_about_case(case_query, prompt_for, prompt_str,
|
57
|
+
user_input, expression_tree = self.prompt_user_about_case(case_query, prompt_for, prompt_str,
|
58
|
+
code_to_modify=prev_user_input)
|
55
59
|
if user_input is None:
|
56
60
|
if prompt_for == PromptFor.Conclusion:
|
57
|
-
self.print_func(f"{Fore.YELLOW}No conclusion provided. Exiting.{Style.RESET_ALL}")
|
61
|
+
self.print_func(f"\n{Fore.YELLOW}No conclusion provided. Exiting.{Style.RESET_ALL}")
|
58
62
|
return None, None
|
59
63
|
else:
|
60
|
-
self.print_func(f"{Fore.RED}Conditions must be provided. Please try again.{Style.RESET_ALL}")
|
64
|
+
self.print_func(f"\n{Fore.RED}Conditions must be provided. Please try again.{Style.RESET_ALL}")
|
61
65
|
continue
|
62
|
-
elif user_input
|
63
|
-
self.print_func(f"{Fore.YELLOW}Exiting.{Style.RESET_ALL}")
|
66
|
+
elif user_input in ["exit", 'quit']:
|
67
|
+
self.print_func(f"\n{Fore.YELLOW}Exiting.{Style.RESET_ALL}")
|
64
68
|
return user_input, None
|
65
|
-
|
69
|
+
|
66
70
|
prev_user_input = '\n'.join(user_input.split('\n')[2:-1])
|
67
71
|
conclusion_type = bool if prompt_for == PromptFor.Conditions else case_query.attribute_type
|
68
72
|
callable_expression = CallableExpression(user_input, conclusion_type, expression_tree=expression_tree,
|
@@ -73,8 +77,9 @@ class UserPrompt:
|
|
73
77
|
if len(make_list(result)) == 0 and (user_input_to_modify is not None
|
74
78
|
and (prev_user_input != user_input_to_modify)):
|
75
79
|
user_input_to_modify = prev_user_input
|
76
|
-
self.print_func(
|
77
|
-
|
80
|
+
self.print_func(
|
81
|
+
f"{Fore.YELLOW}The given expression gave an empty result for case {case_query.name}."
|
82
|
+
f" Please accept or modify!{Style.RESET_ALL}")
|
78
83
|
continue
|
79
84
|
break
|
80
85
|
except Exception as e:
|
@@ -82,7 +87,6 @@ class UserPrompt:
|
|
82
87
|
self.print_func(f"{Fore.RED}{e}{Style.RESET_ALL}")
|
83
88
|
return user_input, callable_expression
|
84
89
|
|
85
|
-
|
86
90
|
def prompt_user_about_case(self, case_query: CaseQuery, prompt_for: PromptFor,
|
87
91
|
prompt_str: Optional[str] = None,
|
88
92
|
code_to_modify: Optional[str] = None) -> Tuple[Optional[str], Optional[AST]]:
|
@@ -95,7 +99,7 @@ class UserPrompt:
|
|
95
99
|
:param code_to_modify: The code to modify. If given will be used as a start for user to modify.
|
96
100
|
:return: The user input, and the executable expression that was parsed from the user input.
|
97
101
|
"""
|
98
|
-
|
102
|
+
logger.debug("Entered shell")
|
99
103
|
initial_prompt_str = f"{prompt_str}\n" if prompt_str is not None else ''
|
100
104
|
if prompt_for == PromptFor.Conclusion:
|
101
105
|
prompt_for_str = f"Give possible value(s) for:"
|
@@ -103,20 +107,32 @@ class UserPrompt:
|
|
103
107
|
prompt_for_str = f"Give conditions for:"
|
104
108
|
case_query.scope.update({'case': case_query.case})
|
105
109
|
shell = None
|
110
|
+
|
106
111
|
if self.viewer is None:
|
112
|
+
prompt_for_str = prompt_for_str.replace(":", f" {case_query.name}:")
|
107
113
|
prompt_str = f"{Fore.WHITE}{initial_prompt_str}{Fore.MAGENTA}{prompt_for_str}"
|
108
114
|
prompt_str = self.construct_prompt_str_for_shell(case_query, prompt_for, prompt_str)
|
109
115
|
shell = IPythonShell(header=prompt_str, prompt_for=prompt_for, case_query=case_query,
|
110
|
-
|
116
|
+
code_to_modify=code_to_modify)
|
111
117
|
else:
|
112
|
-
prompt_str =
|
113
|
-
self.viewer.update_for_case_query(case_query,
|
114
|
-
|
118
|
+
prompt_str = case_query.current_value_str
|
119
|
+
self.viewer.update_for_case_query(case_query, prompt_for=prompt_for, code_to_modify=code_to_modify,
|
120
|
+
title=prompt_for_str, prompt_str=prompt_str)
|
115
121
|
user_input, expression_tree = self.prompt_user_input_and_parse_to_expression(shell=shell)
|
116
|
-
|
122
|
+
logger.debug("Exited shell")
|
117
123
|
return user_input, expression_tree
|
118
124
|
|
119
|
-
|
125
|
+
def build_prompt_str_for_ai(self, case_query: CaseQuery, prompt_for: PromptFor,
|
126
|
+
initial_prompt_str: Optional[str] = None) -> str:
|
127
|
+
initial_prompt_str = f"{initial_prompt_str}\n" if initial_prompt_str is not None else ''
|
128
|
+
if prompt_for == PromptFor.Conclusion:
|
129
|
+
prompt_for_str = f"Give possible value(s) for:"
|
130
|
+
else:
|
131
|
+
prompt_for_str = f"Give conditions for:"
|
132
|
+
prompt_for_str = prompt_for_str.replace(":", f" {case_query.name}:")
|
133
|
+
prompt_str = f"{Fore.WHITE}{initial_prompt_str}{Fore.MAGENTA}{prompt_for_str}"
|
134
|
+
prompt_str += '\n' + case_query.current_value_str
|
135
|
+
return prompt_str
|
120
136
|
|
121
137
|
def construct_prompt_str_for_shell(self, case_query: CaseQuery, prompt_for: PromptFor,
|
122
138
|
prompt_str: Optional[str] = None) -> str:
|
@@ -127,8 +143,7 @@ class UserPrompt:
|
|
127
143
|
:param prompt_for: The type of information the user should provide for the given case.
|
128
144
|
:param prompt_str: The prompt string to display to the user.
|
129
145
|
"""
|
130
|
-
prompt_str +=
|
131
|
-
f"{Fore.CYAN}({', '.join(map(lambda x: x.__name__, case_query.core_attribute_type))}){Fore.MAGENTA}")
|
146
|
+
prompt_str += '\n' + case_query.current_value_str
|
132
147
|
if prompt_for == PromptFor.Conditions:
|
133
148
|
prompt_str += (f"\ne.g. `{Fore.GREEN}return {Fore.BLUE}len{Fore.RESET}(case.attribute) > {Fore.BLUE}0` "
|
134
149
|
f"{Fore.MAGENTA}\nOR `{Fore.GREEN}return {Fore.YELLOW}True`{Fore.MAGENTA} (If you want the"
|
@@ -138,7 +153,6 @@ class UserPrompt:
|
|
138
153
|
prompt_str = f"{Fore.MAGENTA}{prompt_str}{Fore.YELLOW}\n(Write %help for guide){Fore.RESET}\n"
|
139
154
|
return prompt_str
|
140
155
|
|
141
|
-
|
142
156
|
def prompt_user_input_and_parse_to_expression(self, shell: Optional[IPythonShell] = None,
|
143
157
|
user_input: Optional[str] = None) \
|
144
158
|
-> Tuple[Optional[str], Optional[ast.AST]]:
|
@@ -152,17 +166,18 @@ class UserPrompt:
|
|
152
166
|
while True:
|
153
167
|
if user_input is None:
|
154
168
|
user_input = self.start_shell_and_get_user_input(shell=shell)
|
155
|
-
if user_input is None or user_input
|
169
|
+
if user_input is None or user_input in ['exit', 'quit']:
|
156
170
|
return user_input, None
|
157
|
-
|
158
|
-
|
159
|
-
|
171
|
+
if logger.level <= logging.DEBUG:
|
172
|
+
self.print_func(f"\n{Fore.GREEN}Captured User input: {Style.RESET_ALL}")
|
173
|
+
highlighted_code = highlight(user_input, PythonLexer(), TerminalFormatter())
|
174
|
+
self.print_func(highlighted_code)
|
160
175
|
try:
|
161
176
|
return user_input, parse_string_to_expression(user_input)
|
162
177
|
except Exception as e:
|
163
178
|
msg = f"Error parsing expression: {e}"
|
164
179
|
logging.error(msg)
|
165
|
-
self.print_func(f"{Fore.RED}{msg}{Style.RESET_ALL}")
|
180
|
+
self.print_func(f"\n{Fore.RED}{msg}{Style.RESET_ALL}")
|
166
181
|
user_input = None
|
167
182
|
|
168
183
|
def start_shell_and_get_user_input(self, shell: Optional[IPythonShell] = None) -> Optional[str]:
|
@@ -185,6 +200,6 @@ class UserPrompt:
|
|
185
200
|
self.viewer.show()
|
186
201
|
app.exec()
|
187
202
|
if self.viewer.exit_status == ExitStatus.CLOSE:
|
188
|
-
exit(
|
203
|
+
sys.exit()
|
189
204
|
user_input = self.viewer.user_input
|
190
205
|
return user_input
|
@@ -109,20 +109,21 @@ class TemplateFileCreator:
|
|
109
109
|
|
110
110
|
self.open_file_in_editor()
|
111
111
|
|
112
|
-
def open_file_in_editor(self):
|
112
|
+
def open_file_in_editor(self, file_path: Optional[str] = None):
|
113
113
|
"""
|
114
114
|
Open the file in the available editor.
|
115
115
|
"""
|
116
|
+
file_path = file_path or self.temp_file_path
|
116
117
|
if self.editor_cmd is not None:
|
117
|
-
subprocess.Popen([self.editor_cmd,
|
118
|
+
subprocess.Popen([self.editor_cmd, file_path],
|
118
119
|
stdout=subprocess.DEVNULL,
|
119
120
|
stderr=subprocess.DEVNULL)
|
120
121
|
elif self.editor == Editor.Pycharm:
|
121
|
-
subprocess.Popen(["pycharm", "--line", str(self.user_edit_line),
|
122
|
+
subprocess.Popen(["pycharm", "--line", str(self.user_edit_line), file_path],
|
122
123
|
stdout=subprocess.DEVNULL,
|
123
124
|
stderr=subprocess.DEVNULL)
|
124
125
|
elif self.editor == Editor.Code:
|
125
|
-
subprocess.Popen(["code",
|
126
|
+
subprocess.Popen(["code", file_path])
|
126
127
|
elif self.editor == Editor.CodeServer:
|
127
128
|
try:
|
128
129
|
subprocess.check_output(["pgrep", "-f", "code-server"])
|
@@ -134,7 +135,8 @@ class TemplateFileCreator:
|
|
134
135
|
except (subprocess.CalledProcessError, ValueError) as e:
|
135
136
|
self.process = start_code_server(self.workspace)
|
136
137
|
self.print_func(f"Open code-server in your browser at http://localhost:{self.port}?folder={self.workspace}")
|
137
|
-
|
138
|
+
if file_path.endswith('.py'):
|
139
|
+
self.print_func(f"Edit the file: {Fore.MAGENTA}{file_path}")
|
138
140
|
|
139
141
|
def build_boilerplate_code(self):
|
140
142
|
imports = self.get_imports()
|
@@ -183,7 +185,7 @@ class TemplateFileCreator:
|
|
183
185
|
func_args = ', '.join([f"{k}: {v}" if str(v) not in ["NoneType", "None"] else str(k)
|
184
186
|
for k, v in func_args.items()])
|
185
187
|
else:
|
186
|
-
func_args = f"case: {self.case_type.__name__}"
|
188
|
+
func_args = f"case: {self.case_query.case_type.__name__}"
|
187
189
|
return func_args
|
188
190
|
|
189
191
|
def write_to_file(self, code: str):
|
@@ -212,7 +214,7 @@ class TemplateFileCreator:
|
|
212
214
|
else:
|
213
215
|
case_type_imports.append(v)
|
214
216
|
else:
|
215
|
-
case_type_imports.append(self.case_type)
|
217
|
+
case_type_imports.append(self.case_query.case_type)
|
216
218
|
if self.output_type is None:
|
217
219
|
output_type_imports = [Any]
|
218
220
|
else:
|
@@ -302,7 +304,7 @@ class TemplateFileCreator:
|
|
302
304
|
exec(source, scope, exec_globals)
|
303
305
|
user_function = exec_globals[func_name]
|
304
306
|
updates[func_name] = user_function
|
305
|
-
print_func(f"{Fore.
|
307
|
+
print_func(f"\n{Fore.WHITE}Loaded the following function into user namespace:\n{Fore.GREEN}{func_name}{Style.RESET_ALL}")
|
306
308
|
break
|
307
309
|
if updates:
|
308
310
|
all_code_lines = extract_function_source(file_path,
|