ripple-down-rules 0.6.1__py3-none-any.whl → 0.6.6__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.
@@ -1,15 +1,19 @@
1
- from __future__ import annotations
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, QGraphicsPixmapItem
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
@@ -21,7 +25,7 @@ except ImportError as e:
21
25
  from typing_extensions import Optional, Any, List, Dict, Callable
22
26
 
23
27
  from ..datastructures.dataclasses import CaseQuery
24
- from ..datastructures.enums import PromptFor
28
+ from ..datastructures.enums import PromptFor, ExitStatus
25
29
  from .template_file_creator import TemplateFileCreator
26
30
  from ..utils import is_iterable, contains_return_statement, encapsulate_code_lines_into_a_function
27
31
  from .object_diagram import generate_object_graph
@@ -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)
@@ -282,14 +288,13 @@ class RDRCaseViewer(QMainWindow):
282
288
  user_input: Optional[str] = None
283
289
  attributes_widget: Optional[QWidget] = None
284
290
  save_function: Optional[Callable[str, str], None] = None
291
+ instances: List[RDRCaseViewer] = []
292
+ exit_status: ExitStatus = ExitStatus.CLOSE
285
293
 
286
- def __init__(self, parent=None,
287
- save_dir: Optional[str] = None,
288
- save_model_name: Optional[str] = None):
294
+ def __init__(self, parent=None):
289
295
  super().__init__(parent)
290
- self.save_dir = save_dir
291
- self.save_model_name = save_model_name
292
-
296
+ self.instances.clear()
297
+ self.instances.append(self)
293
298
  self.setWindowTitle("RDR Case Viewer")
294
299
 
295
300
  self.setBaseSize(1600, 600) # or your preferred initial size
@@ -325,38 +330,31 @@ class RDRCaseViewer(QMainWindow):
325
330
 
326
331
  # Add both to main layout
327
332
  main_layout.addWidget(self.attributes_widget, stretch=1)
328
- main_layout.addWidget(middle_widget, stretch=1)
333
+ main_layout.addWidget(middle_widget, stretch=2)
329
334
  main_layout.addWidget(self.obj_diagram_viewer, stretch=2)
330
335
 
331
- def set_save_function(self, save_function: Callable[[str, str], None]) -> None:
332
- """
333
- Set the function to save the file.
334
-
335
- :param save_function: The function to save the file.
336
- """
337
- self.save_function = save_function
338
- self.save_btn.clicked.connect(lambda: self.save_function(self.save_dir, self.save_model_name))
339
-
340
336
  def print(self, msg):
341
337
  """
342
338
  Print a message to the console.
343
339
  """
344
340
  self.ipython_console._append_plain_text(msg + '\n', True)
345
341
 
346
- def update_for_case_query(self, case_query: CaseQuery, title_txt: Optional[str] = None,
347
- 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):
348
345
  self.case_query = case_query
349
346
  self.prompt_for = prompt_for
350
347
  self.code_to_modify = code_to_modify
351
- title_text = title_txt or ""
348
+ title_text = title or ""
352
349
  case_attr_type = ', '.join([t.__name__ for t in case_query.core_attribute_type])
353
350
  case_attr_type = style(f"{case_attr_type}", 'g', 28, 'bold')
354
351
  case_name = style(f"{case_query.name}", 'b', 28, 'bold')
355
352
  title_text = style(f"{title_text} {case_name} of type {case_attr_type}", 'o', 28, 'bold')
356
- 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)
357
355
 
358
356
  def update_for_object(self, obj: Any, name: str, scope: Optional[dict] = None,
359
- title_text: Optional[str] = None):
357
+ title_text: Optional[str] = None, header: Optional[str] = None):
360
358
  self.update_main_obj(obj, name)
361
359
  title_text = title_text or style(f"{name}", 'o', 28, 'bold')
362
360
  scope = scope or {}
@@ -365,6 +363,9 @@ class RDRCaseViewer(QMainWindow):
365
363
  self.update_attribute_layout(obj, name)
366
364
  self.title_label.setText(title_text)
367
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
368
369
 
369
370
  def update_main_obj(self, obj, name):
370
371
  self.main_obj = {name: obj}
@@ -436,8 +437,6 @@ class RDRCaseViewer(QMainWindow):
436
437
  if isinstance(item.widget(), CollapsibleBox):
437
438
  self.expand_collapse_all(item.widget(), expand=True, curr_depth=curr_depth + 1, max_depth=max_depth)
438
439
 
439
-
440
-
441
440
  def create_buttons_widget(self):
442
441
  button_widget = QWidget()
443
442
  button_widget_layout = QVBoxLayout(button_widget)
@@ -461,30 +460,36 @@ class RDRCaseViewer(QMainWindow):
461
460
  load_btn.clicked.connect(self._load)
462
461
  load_btn.setStyleSheet(f"background-color: {color_name_to_html('b')}; color: white;") # Blue button
463
462
 
464
- self.save_btn = QPushButton("Save")
465
- self.save_btn.setStyleSheet(f"background-color: {color_name_to_html('b')}; color: white;") # Blue button
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;")
466
470
 
467
471
  row_1_button_widget_layout.addWidget(edit_btn)
468
472
  row_1_button_widget_layout.addWidget(load_btn)
469
- row_1_button_widget_layout.addWidget(accept_btn)
470
- row_2_button_widget_layout.addWidget(self.save_btn)
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)
471
476
  return button_widget
472
477
 
473
478
  def _accept(self):
479
+ self.exit_status = ExitStatus.SUCCESS
474
480
  # close the window
475
481
  self.close()
476
482
 
477
483
  def _edit(self):
478
- self.template_file_creator = TemplateFileCreator(self.case_query, self.prompt_for, self.code_to_modify,
479
- self.print)
484
+ self.template_file_creator = self.create_template_file_creator()
480
485
  self.template_file_creator.edit()
481
486
 
482
487
  def _load(self):
483
488
  if not self.template_file_creator:
484
489
  return
485
490
  self.code_lines, updates = self.template_file_creator.load(self.template_file_creator.temp_file_path,
486
- self.template_file_creator.func_name,
487
- self.template_file_creator.print_func)
491
+ self.template_file_creator.func_name,
492
+ self.template_file_creator.print_func)
488
493
  self.ipython_console.kernel.shell.user_ns.update(updates)
489
494
  if self.code_lines is not None:
490
495
  self.user_input = encapsulate_code_lines_into_a_function(
@@ -492,7 +497,22 @@ class RDRCaseViewer(QMainWindow):
492
497
  self.template_file_creator.function_signature,
493
498
  self.template_file_creator.func_doc, self.case_query)
494
499
  self.case_query.scope.update(updates)
495
- self.template_file_creator = None
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)
496
516
 
497
517
  def update_attribute_layout(self, obj, name: str):
498
518
  # Clear the existing layout
@@ -541,7 +561,8 @@ class RDRCaseViewer(QMainWindow):
541
561
  attr = f"{attr}"
542
562
  try:
543
563
  if is_iterable(value) or hasattr(value, "__dict__") and not inspect.isfunction(value):
544
- self.add_collapsible(attr, value, layout, current_depth + 1, max_depth, chain_name=f"{chain_name}.{attr}")
564
+ self.add_collapsible(attr, value, layout, current_depth + 1, max_depth,
565
+ chain_name=f"{chain_name}.{attr}")
545
566
  else:
546
567
  self.add_non_collapsible(attr, value, layout)
547
568
  except Exception as e:
@@ -553,7 +574,7 @@ class RDRCaseViewer(QMainWindow):
553
574
  type_name = type(value) if not isinstance(value, type) else value
554
575
  collapsible = CollapsibleBox(
555
576
  f'<b><span style="color:#FFA07A;">{attr}</span></b> {python_colored_repr(type_name)}', viewer=self,
556
- chain_name=chain_name, main_obj=self.main_obj)
577
+ chain_name=chain_name, main_obj=self.main_obj)
557
578
  self.add_attributes(value, attr, collapsible.content_layout, current_depth, max_depth, chain_name=chain_name)
558
579
  layout.addWidget(collapsible)
559
580
 
@@ -581,7 +602,6 @@ class IPythonConsole(RichJupyterWidget):
581
602
 
582
603
  # Monkey patch its run_cell method
583
604
  def custom_run_cell(this, raw_cell, **kwargs):
584
- print(raw_cell)
585
605
  if contains_return_statement(raw_cell) and 'def ' not in raw_cell:
586
606
  if self.parent.template_file_creator and self.parent.template_file_creator.func_name in raw_cell:
587
607
  self.command_log = self.parent.code_lines
@@ -595,7 +615,6 @@ class IPythonConsole(RichJupyterWidget):
595
615
 
596
616
  original_run_cell = self.kernel.shell.run_cell
597
617
  self.kernel.shell.run_cell = MethodType(custom_run_cell, self.kernel.shell)
598
-
599
618
  self.kernel_client = self.kernel_manager.client()
600
619
  self.kernel_client.start_channels()
601
620
 
@@ -624,7 +643,7 @@ class IPythonConsole(RichJupyterWidget):
624
643
  .in-prompt { font-weight: bold; color: %(in_prompt_color)s }
625
644
  .out-prompt-number { font-weight: bold; color: %(out_prompt_number_color)s }
626
645
  .out-prompt { font-weight: bold; color: %(out_prompt_color)s }
627
- '''%dict(
646
+ ''' % dict(
628
647
  bgcolor='#0b0d0b', fgcolor='#47d9cc', select="#555",
629
648
  in_prompt_number_color='lime', in_prompt_color='lime',
630
649
  out_prompt_number_color='red', out_prompt_color='red'
@@ -642,6 +661,12 @@ class IPythonConsole(RichJupyterWidget):
642
661
  """
643
662
  self.kernel.shell.user_ns.update(namespace)
644
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
+
645
670
  def execute(self, source=None, hidden=False, interactive=False):
646
671
  # Log the command before execution
647
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,12 +21,19 @@ 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
27
+ self.edited: bool = False
28
+ self.loaded: bool = False
25
29
 
26
30
  @line_magic
27
31
  def edit(self, line):
28
- self.rule_editor.edit()
32
+ if self.edited:
33
+ self.rule_editor.open_file_in_editor()
34
+ else:
35
+ self.rule_editor.edit()
36
+ self.edited = True
29
37
 
30
38
  @line_magic
31
39
  def load(self, line):
@@ -33,6 +41,31 @@ class MyMagics(Magics):
33
41
  self.rule_editor.func_name,
34
42
  self.rule_editor.print_func)
35
43
  self.shell.user_ns.update(updates)
44
+ self.case_query.scope.update(updates)
45
+ self.loaded = True
46
+
47
+ @line_magic
48
+ def current_value(self, line):
49
+ """
50
+ Display the current value of the attribute of the case.
51
+ """
52
+ if self.case_query is None:
53
+ print(f"{Fore.RED}No case query provided.{Style.RESET_ALL}")
54
+ return
55
+ print(self.case_query.current_value_str)
56
+
57
+ @line_magic
58
+ def show_rule_tree(self, line):
59
+ """
60
+ Display the rule tree for the current case query.
61
+ """
62
+ if self.case_query is None:
63
+ print(f"{Fore.RED}No case query provided.{Style.RESET_ALL}")
64
+ return
65
+ if self.case_query.rdr is None:
66
+ print(f"{Fore.RED}No RDR available for the current case query.{Style.RESET_ALL}")
67
+ return
68
+ self.case_query.render_rule_tree(view=True)
36
69
 
37
70
  @line_magic
38
71
  def help(self, line):
@@ -42,12 +75,16 @@ class MyMagics(Magics):
42
75
  help_text = f"""
43
76
  Directly write python code in the shell, and then `{Fore.GREEN}return {Fore.RESET}output`. Or use
44
77
  the magic commands to write the code in a temporary file and edit it in PyCharm:
45
- {Fore.MAGENTA}Usage: %edit{Style.RESET_ALL}
78
+ {Fore.MAGENTA}%edit{Style.RESET_ALL}
46
79
  Opens a temporary file in PyCharm for editing a function (conclusion or conditions for case)
47
80
  that will be executed on the case object.
48
- {Fore.MAGENTA}Usage: %load{Style.RESET_ALL}
81
+ {Fore.MAGENTA}%load{Style.RESET_ALL}
49
82
  Loads the function defined in the temporary file into the user namespace, that can then be used inside the
50
83
  Ipython shell. You can then do `{Fore.GREEN}return {Fore.RESET}function_name(case)`.
84
+ {Fore.MAGENTA}%current_value{Style.RESET_ALL}
85
+ Shows the current value of the case attribute on which the rule are being fit.
86
+ {Fore.MAGENTA}%show_rule_tree{Style.RESET_ALL}
87
+ Displays the rule tree for the current case query.
51
88
  """
52
89
  print(help_text)
53
90
 
@@ -136,7 +173,7 @@ class IPythonShell:
136
173
  """
137
174
  if self.shell.all_lines[-1] in ['quit', 'exit']:
138
175
  self.user_input = 'exit'
139
- elif self.shell.all_lines[0].replace('return', '').strip() == '':
176
+ elif self.shell.all_lines[-1].replace('return', '').strip() == '':
140
177
  self.user_input = None
141
178
  else:
142
179
  self.all_code_lines = extract_dependencies(self.shell.all_lines)
@@ -144,7 +181,7 @@ class IPythonShell:
144
181
  self.user_input = None
145
182
  else:
146
183
  self.user_input = encapsulate_code_lines_into_a_function(self.all_code_lines,
147
- function_name=self.shell.my_magics.rule_editor.func_name,
148
- function_signature=self.shell.my_magics.rule_editor.function_signature,
149
- func_doc=self.shell.my_magics.rule_editor.func_doc,
150
- case_query=self.case_query)
184
+ function_name=self.shell.my_magics.rule_editor.func_name,
185
+ function_signature=self.shell.my_magics.rule_editor.function_signature,
186
+ func_doc=self.shell.my_magics.rule_editor.func_doc,
187
+ case_query=self.case_query)
@@ -1,10 +1,13 @@
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
- from .gui import RDRCaseViewer
10
+ from .gui import RDRCaseViewer, style
8
11
  except ImportError:
9
12
  QApplication = None
10
13
  RDRCaseViewer = None
@@ -17,27 +20,24 @@ from typing_extensions import Optional, Tuple
17
20
 
18
21
  from ..datastructures.callable_expression import CallableExpression, parse_string_to_expression
19
22
  from ..datastructures.dataclasses import CaseQuery
20
- from ..datastructures.enums import PromptFor
23
+ from ..datastructures.enums import PromptFor, ExitStatus
21
24
  from .ipython_custom_shell import IPythonShell
22
25
  from ..utils import make_list
23
- from threading import RLock
26
+ from threading import Lock
24
27
 
25
28
 
26
29
  class UserPrompt:
27
30
  """
28
31
  A class to handle user prompts for the RDR.
29
32
  """
30
- shell_lock: RLock = RLock() # To ensure that only one thread can access the shell at a time
33
+ shell_lock: Lock = Lock() # To ensure that only one thread can access the shell at a time
31
34
 
32
- def __init__(self, viewer: Optional[RDRCaseViewer] = None):
35
+ def __init__(self, prompt_user: bool = True):
33
36
  """
34
37
  Initialize the UserPrompt class.
35
-
36
- :param viewer: The RDRCaseViewer instance to use for prompting the user.
37
38
  """
38
- self.viewer = viewer
39
- self.print_func = print if viewer is None else viewer.print
40
-
39
+ self.viewer = RDRCaseViewer.instances[0] if RDRCaseViewer and any(RDRCaseViewer.instances) else None
40
+ self.print_func = self.viewer.print if self.viewer else print
41
41
 
42
42
  def prompt_user_for_expression(self, case_query: CaseQuery, prompt_for: PromptFor, prompt_str: Optional[str] = None) \
43
43
  -> Tuple[Optional[str], Optional[CallableExpression]]:
@@ -49,39 +49,43 @@ class UserPrompt:
49
49
  :param prompt_str: The prompt string to display to the user.
50
50
  :return: A callable expression that takes a case and executes user expression on it.
51
51
  """
52
- prev_user_input: Optional[str] = None
53
- callable_expression: Optional[CallableExpression] = None
54
- while True:
55
- with self.shell_lock:
56
- user_input, expression_tree = self.prompt_user_about_case(case_query, prompt_for, prompt_str, code_to_modify=prev_user_input)
57
- if user_input is None:
58
- if prompt_for == PromptFor.Conclusion:
59
- self.print_func(f"{Fore.YELLOW}No conclusion provided. Exiting.{Style.RESET_ALL}")
60
- return None, None
61
- else:
62
- self.print_func(f"{Fore.RED}Conditions must be provided. Please try again.{Style.RESET_ALL}")
63
- continue
64
- elif user_input == "exit":
65
- self.print_func(f"{Fore.YELLOW}Exiting.{Style.RESET_ALL}")
66
- return user_input, None
67
-
68
- prev_user_input = '\n'.join(user_input.split('\n')[2:-1])
69
- conclusion_type = bool if prompt_for == PromptFor.Conditions else case_query.attribute_type
70
- callable_expression = CallableExpression(user_input, conclusion_type, expression_tree=expression_tree,
71
- scope=case_query.scope,
72
- mutually_exclusive=case_query.mutually_exclusive)
73
- try:
74
- result = callable_expression(case_query.case)
75
- if len(make_list(result)) == 0:
76
- self.print_func(f"{Fore.YELLOW}The given expression gave an empty result for case {case_query.name}."
77
- f" Please modify!{Style.RESET_ALL}")
78
- continue
79
- break
80
- except Exception as e:
81
- logging.error(e)
82
- self.print_func(f"{Fore.RED}{e}{Style.RESET_ALL}")
83
- return user_input, callable_expression
52
+ with self.shell_lock:
53
+ prev_user_input: Optional[str] = None
54
+ user_input_to_modify: Optional[str] = None
55
+ callable_expression: Optional[CallableExpression] = None
56
+ while True:
57
+ user_input, expression_tree = self.prompt_user_about_case(case_query, prompt_for, prompt_str,
58
+ code_to_modify=prev_user_input)
59
+ if user_input is None:
60
+ if prompt_for == PromptFor.Conclusion:
61
+ self.print_func(f"\n{Fore.YELLOW}No conclusion provided. Exiting.{Style.RESET_ALL}")
62
+ return None, None
63
+ else:
64
+ self.print_func(f"\n{Fore.RED}Conditions must be provided. Please try again.{Style.RESET_ALL}")
65
+ continue
66
+ elif user_input in ["exit", 'quit']:
67
+ self.print_func(f"\n{Fore.YELLOW}Exiting.{Style.RESET_ALL}")
68
+ return user_input, None
84
69
 
70
+ prev_user_input = '\n'.join(user_input.split('\n')[2:-1])
71
+ conclusion_type = bool if prompt_for == PromptFor.Conditions else case_query.attribute_type
72
+ callable_expression = CallableExpression(user_input, conclusion_type, expression_tree=expression_tree,
73
+ scope=case_query.scope,
74
+ mutually_exclusive=case_query.mutually_exclusive)
75
+ try:
76
+ result = callable_expression(case_query.case)
77
+ if len(make_list(result)) == 0 and (user_input_to_modify is not None
78
+ and (prev_user_input != user_input_to_modify)):
79
+ user_input_to_modify = prev_user_input
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}")
83
+ continue
84
+ break
85
+ except Exception as e:
86
+ logging.error(e)
87
+ self.print_func(f"{Fore.RED}{e}{Style.RESET_ALL}")
88
+ return user_input, callable_expression
85
89
 
86
90
  def prompt_user_about_case(self, case_query: CaseQuery, prompt_for: PromptFor,
87
91
  prompt_str: Optional[str] = None,
@@ -95,27 +99,40 @@ 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
- self.print_func("Entered shell")
99
- prompt_str = f"{Fore.WHITE}{prompt_str}" if prompt_str is not None else ''
102
+ logger.debug("Entered shell")
103
+ initial_prompt_str = f"{prompt_str}\n" if prompt_str is not None else ''
100
104
  if prompt_for == PromptFor.Conclusion:
101
- prompt_str += f"\n{Fore.MAGENTA}Give possible value(s) for:"
105
+ prompt_for_str = f"Give possible value(s) for:"
102
106
  else:
103
- prompt_str += f"\n{Fore.MAGENTA}Give conditions on when can the rule be evaluated for:"
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}:")
113
+ prompt_str = f"{Fore.WHITE}{initial_prompt_str}{Fore.MAGENTA}{prompt_for_str}"
107
114
  prompt_str = self.construct_prompt_str_for_shell(case_query, prompt_for, prompt_str)
108
115
  shell = IPythonShell(header=prompt_str, prompt_for=prompt_for, case_query=case_query,
109
- code_to_modify=code_to_modify)
116
+ code_to_modify=code_to_modify)
110
117
  else:
111
-
112
- self.viewer.update_for_case_query(case_query, prompt_str,
113
- prompt_for=prompt_for, code_to_modify=code_to_modify)
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)
114
121
  user_input, expression_tree = self.prompt_user_input_and_parse_to_expression(shell=shell)
115
- self.print_func("Exited shell")
122
+ logger.debug("Exited shell")
116
123
  return user_input, expression_tree
117
124
 
118
-
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
119
136
 
120
137
  def construct_prompt_str_for_shell(self, case_query: CaseQuery, prompt_for: PromptFor,
121
138
  prompt_str: Optional[str] = None) -> str:
@@ -126,8 +143,7 @@ class UserPrompt:
126
143
  :param prompt_for: The type of information the user should provide for the given case.
127
144
  :param prompt_str: The prompt string to display to the user.
128
145
  """
129
- prompt_str += (f"\n{Fore.CYAN}{case_query.name}{Fore.MAGENTA} of type(s) "
130
- 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
131
147
  if prompt_for == PromptFor.Conditions:
132
148
  prompt_str += (f"\ne.g. `{Fore.GREEN}return {Fore.BLUE}len{Fore.RESET}(case.attribute) > {Fore.BLUE}0` "
133
149
  f"{Fore.MAGENTA}\nOR `{Fore.GREEN}return {Fore.YELLOW}True`{Fore.MAGENTA} (If you want the"
@@ -137,7 +153,6 @@ class UserPrompt:
137
153
  prompt_str = f"{Fore.MAGENTA}{prompt_str}{Fore.YELLOW}\n(Write %help for guide){Fore.RESET}\n"
138
154
  return prompt_str
139
155
 
140
-
141
156
  def prompt_user_input_and_parse_to_expression(self, shell: Optional[IPythonShell] = None,
142
157
  user_input: Optional[str] = None) \
143
158
  -> Tuple[Optional[str], Optional[ast.AST]]:
@@ -151,17 +166,18 @@ class UserPrompt:
151
166
  while True:
152
167
  if user_input is None:
153
168
  user_input = self.start_shell_and_get_user_input(shell=shell)
154
- if user_input is None or user_input == 'exit':
169
+ if user_input is None or user_input in ['exit', 'quit']:
155
170
  return user_input, None
156
- self.print_func(f"{Fore.GREEN}Captured User input: {Style.RESET_ALL}")
157
- highlighted_code = highlight(user_input, PythonLexer(), TerminalFormatter())
158
- self.print_func(highlighted_code)
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)
159
175
  try:
160
176
  return user_input, parse_string_to_expression(user_input)
161
177
  except Exception as e:
162
178
  msg = f"Error parsing expression: {e}"
163
179
  logging.error(msg)
164
- self.print_func(f"{Fore.RED}{msg}{Style.RESET_ALL}")
180
+ self.print_func(f"\n{Fore.RED}{msg}{Style.RESET_ALL}")
165
181
  user_input = None
166
182
 
167
183
  def start_shell_and_get_user_input(self, shell: Optional[IPythonShell] = None) -> Optional[str]:
@@ -173,6 +189,8 @@ class UserPrompt:
173
189
  """
174
190
  if self.viewer is None:
175
191
  shell = IPythonShell() if shell is None else shell
192
+ if not hasattr(shell.shell, "auto_match"):
193
+ shell.shell.auto_match = True # or True, depending on your preference
176
194
  shell.run()
177
195
  user_input = shell.user_input
178
196
  else:
@@ -181,5 +199,7 @@ class UserPrompt:
181
199
  raise RuntimeError("QApplication instance is None. Please run the application first.")
182
200
  self.viewer.show()
183
201
  app.exec()
202
+ if self.viewer.exit_status == ExitStatus.CLOSE:
203
+ sys.exit()
184
204
  user_input = self.viewer.user_input
185
205
  return user_input