ripple-down-rules 0.6.28__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.
@@ -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, code_to_modify=prev_user_input)
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 == "exit":
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(f"{Fore.YELLOW}The given expression gave an empty result for case {case_query.name}."
77
- f" Please accept or modify!{Style.RESET_ALL}")
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
- self.print_func("Entered shell")
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
- code_to_modify=code_to_modify)
116
+ code_to_modify=code_to_modify)
111
117
  else:
112
- prompt_str = initial_prompt_str + prompt_for_str
113
- self.viewer.update_for_case_query(case_query, prompt_str,
114
- 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)
115
121
  user_input, expression_tree = self.prompt_user_input_and_parse_to_expression(shell=shell)
116
- self.print_func("Exited shell")
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 += (f"\n{Fore.CYAN}{case_query.name}{Fore.MAGENTA} of type(s) "
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 == 'exit':
169
+ if user_input is None or user_input in ['exit', 'quit']:
156
170
  return user_input, None
157
- self.print_func(f"{Fore.GREEN}Captured User input: {Style.RESET_ALL}")
158
- highlighted_code = highlight(user_input, PythonLexer(), TerminalFormatter())
159
- 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)
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(0)
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, self.temp_file_path],
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), self.temp_file_path],
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", self.temp_file_path])
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
- self.print_func(f"Edit the file: {Fore.MAGENTA}{self.temp_file_path}")
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.BLUE}Loaded `{func_name}` function into user namespace.{Style.RESET_ALL}")
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,
@@ -21,8 +21,10 @@ from subprocess import check_call
21
21
  from tempfile import NamedTemporaryFile
22
22
  from textwrap import dedent
23
23
  from types import NoneType
24
+ import inspect
24
25
 
25
26
  import six
27
+ from graphviz import Source
26
28
  from sqlalchemy.exc import NoInspectionAvailable
27
29
  from . import logger
28
30
 
@@ -45,7 +47,7 @@ except ImportError as e:
45
47
 
46
48
  import requests
47
49
  from anytree import Node, RenderTree, PreOrderIter
48
- from sqlalchemy import MetaData, inspect
50
+ from sqlalchemy import MetaData, inspect as sql_inspect
49
51
  from sqlalchemy.orm import Mapped, registry, class_mapper, DeclarativeBase as SQLTable, Session
50
52
  from tabulate import tabulate
51
53
  from typing_extensions import Callable, Set, Any, Type, Dict, TYPE_CHECKING, get_type_hints, \
@@ -146,7 +148,9 @@ def extract_imports(file_path: Optional[str] = None, tree: Optional[ast.AST] = N
146
148
  def extract_function_source(file_path: str,
147
149
  function_names: List[str], join_lines: bool = True,
148
150
  return_line_numbers: bool = False,
149
- include_signature: bool = True) \
151
+ include_signature: bool = True,
152
+ as_list: bool = False,
153
+ is_class: bool = False) \
150
154
  -> Union[Dict[str, Union[str, List[str]]],
151
155
  Tuple[Dict[str, Union[str, List[str]]], Dict[str, Tuple[int, int]]]]:
152
156
  """
@@ -157,6 +161,9 @@ def extract_function_source(file_path: str,
157
161
  :param join_lines: Whether to join the lines of the function.
158
162
  :param return_line_numbers: Whether to return the line numbers of the function.
159
163
  :param include_signature: Whether to include the function signature in the source code.
164
+ :param as_list: Whether to return a list of function sources instead of dict (useful when there is multiple
165
+ functions with same name).
166
+ :param is_class: Whether to also look for class definitions
160
167
  :return: A dictionary mapping function names to their source code as a string if join_lines is True,
161
168
  otherwise as a list of strings.
162
169
  """
@@ -167,24 +174,39 @@ def extract_function_source(file_path: str,
167
174
  tree = ast.parse(source)
168
175
  function_names = make_list(function_names)
169
176
  functions_source: Dict[str, Union[str, List[str]]] = {}
177
+ functions_source_list: List[Union[str, List[str]]] = []
170
178
  line_numbers: Dict[str, Tuple[int, int]] = {}
179
+ line_numbers_list: List[Tuple[int, int]] = []
180
+ if is_class:
181
+ look_for_type = ast.ClassDef
182
+ else:
183
+ look_for_type = ast.FunctionDef
184
+
171
185
  for node in tree.body:
172
- if isinstance(node, ast.FunctionDef) and (node.name in function_names or len(function_names) == 0):
186
+ if isinstance(node, look_for_type) and (node.name in function_names or len(function_names) == 0):
173
187
  # Get the line numbers of the function
174
188
  lines = source.splitlines()
175
189
  func_lines = lines[node.lineno - 1:node.end_lineno]
176
190
  if not include_signature:
177
191
  func_lines = func_lines[1:]
178
- line_numbers[node.name] = (node.lineno, node.end_lineno)
179
- functions_source[node.name] = dedent("\n".join(func_lines)) if join_lines else func_lines
180
- if (len(functions_source) >= len(function_names)) and (not len(function_names) == 0):
181
- break
182
- if len(functions_source) < len(function_names):
192
+ if as_list:
193
+ line_numbers_list.append((node.lineno, node.end_lineno))
194
+ else:
195
+ line_numbers[node.name] = (node.lineno, node.end_lineno)
196
+ parsed_function = dedent("\n".join(func_lines)) if join_lines else func_lines
197
+ if as_list:
198
+ functions_source_list.append(parsed_function)
199
+ else:
200
+ functions_source[node.name] = parsed_function
201
+ if len(function_names) > 0:
202
+ if len(functions_source) >= len(function_names) or len(functions_source_list) >= len(function_names):
203
+ break
204
+ if len(functions_source) < len(function_names) and len(functions_source_list) < len(function_names):
183
205
  logger.warning(f"Could not find all functions in {file_path}: {function_names} not found, "
184
206
  f"functions not found: {set(function_names) - set(functions_source.keys())}")
185
207
  if return_line_numbers:
186
- return functions_source, line_numbers
187
- return functions_source
208
+ return functions_source if not as_list else functions_source_list, line_numbers if not as_list else line_numbers_list
209
+ return functions_source if not as_list else functions_source_list
188
210
 
189
211
 
190
212
  def encapsulate_user_input(user_input: str, func_signature: str, func_doc: Optional[str] = None) -> str:
@@ -791,6 +813,13 @@ def get_import_path_from_path(path: str) -> Optional[str]:
791
813
  return package_name
792
814
 
793
815
 
816
+ def get_class_file_path(cls):
817
+ """
818
+ Get the file path of a class.
819
+ """
820
+ return os.path.abspath(inspect.getfile(cls))
821
+
822
+
794
823
  def get_function_import_data(func: Callable) -> Tuple[str, str]:
795
824
  """
796
825
  Get the import path of a function.
@@ -1281,7 +1310,7 @@ def copy_orm_instance(instance: SQLTable) -> SQLTable:
1281
1310
  :return: The copied instance.
1282
1311
  """
1283
1312
  try:
1284
- session: Session = inspect(instance).session
1313
+ session: Session = sql_inspect(instance).session
1285
1314
  except NoInspectionAvailable:
1286
1315
  session = None
1287
1316
  if session is not None:
@@ -1833,6 +1862,24 @@ class FilteredDotExporter(object):
1833
1862
  yield node
1834
1863
  for edge in self.__iter_edges(indent, nodenamefunc, edgeattrfunc, edgetypefunc):
1835
1864
  yield edge
1865
+ legend_dot_graph = """
1866
+ // Color legend as a subgraph
1867
+ subgraph cluster_legend {
1868
+ label = "Legend";
1869
+ style = dashed;
1870
+ color = gray;
1871
+
1872
+ legend_green [label="Fired->Query Related Value", shape=box, style=filled, fillcolor=green, fontcolor=black, size=0.5];
1873
+ legend_yellow [label="Fired->Some Value", shape=box, style=filled, fillcolor=yellow, fontcolor=black, size=0.5];
1874
+ legend_orange [label="Fired->Empty Value", shape=box, style=filled, fillcolor=orange, fontcolor=black, size=0.5];
1875
+ legend_red [label="Evaluated->Not Fired", shape=box, style=filled, fillcolor=red, fontcolor=black, size=0.5];
1876
+ legend_white [label="Not Evaluated", shape=box, style=filled, fillcolor=white, fontcolor=black, size=0.5];
1877
+
1878
+ // Invisible edges to arrange legend vertically
1879
+ legend_white -> legend_red -> legend_orange -> legend_yellow -> legend_green [style=invis];
1880
+ }"""
1881
+ for line in legend_dot_graph.splitlines():
1882
+ yield "%s" % (line.strip())
1836
1883
  yield "}"
1837
1884
 
1838
1885
  def __iter_options(self, indent):
@@ -1915,6 +1962,12 @@ class FilteredDotExporter(object):
1915
1962
  msg = 'Could not remove temporary file %s' % dotfilename
1916
1963
  logger.warning(msg)
1917
1964
 
1965
+ def to_source(self) -> Source:
1966
+ """
1967
+ Return the source code of the graph as a Source object.
1968
+ """
1969
+ return Source("\n".join(self), filename=self.name)
1970
+
1918
1971
  @staticmethod
1919
1972
  def esc(value):
1920
1973
  """Escape Strings."""
@@ -1922,7 +1975,9 @@ class FilteredDotExporter(object):
1922
1975
 
1923
1976
 
1924
1977
  def render_tree(root: Node, use_dot_exporter: bool = False,
1925
- filename: str = "scrdr", only_nodes: List[Node] = None, show_in_console: bool = False):
1978
+ filename: str = "scrdr", only_nodes: List[Node] = None, show_in_console: bool = False,
1979
+ color_map: Optional[Callable[[Node], str]] = None,
1980
+ view: bool = False) -> None:
1926
1981
  """
1927
1982
  Render the tree using the console and optionally export it to a dot file.
1928
1983
 
@@ -1931,6 +1986,8 @@ def render_tree(root: Node, use_dot_exporter: bool = False,
1931
1986
  :param filename: The name of the file to export the tree to.
1932
1987
  :param only_nodes: A list of nodes to include in the dot export.
1933
1988
  :param show_in_console: Whether to print the tree to the console.
1989
+ :param color_map: A function that returns a color for certain nodes.
1990
+ :param view: Whether to view the dot file in a viewer.
1934
1991
  """
1935
1992
  if not root:
1936
1993
  logger.warning("No rules to render")
@@ -1947,10 +2004,15 @@ def render_tree(root: Node, use_dot_exporter: bool = False,
1947
2004
  include_nodes=only_nodes,
1948
2005
  nodenamefunc=unique_node_names,
1949
2006
  edgeattrfunc=edge_attr_setter,
1950
- nodeattrfunc=lambda node: f'style=filled, fillcolor={node.color}'
2007
+ nodeattrfunc=lambda node: f'style=filled,'
2008
+ f' fillcolor={color_map(node) if color_map else node.color}',
1951
2009
  )
1952
- de.to_dotfile(f"{filename}{'.dot'}")
1953
- # de.to_picture(f"{filename}{'.png'}")
2010
+ if view:
2011
+ de.to_source().view()
2012
+ else:
2013
+ filename = filename or "rule_tree"
2014
+ de.to_dotfile(f"{filename}{'.dot'}")
2015
+ de.to_picture(f"{filename}{'.svg'}")
1954
2016
 
1955
2017
 
1956
2018
  def draw_tree(root: Node, fig: Figure):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ripple_down_rules
3
- Version: 0.6.28
3
+ Version: 0.6.30
4
4
  Summary: Implements the various versions of Ripple Down Rules (RDR) for knowledge representation and reasoning.
5
5
  Author-email: Abdelrhman Bassiouny <abassiou@uni-bremen.de>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -0,0 +1,24 @@
1
+ ripple_down_rules/__init__.py,sha256=97mtCfVEw1HwkdrprDGcbF2yNl3fqkWb_6oSEj-rOFo,99
2
+ ripple_down_rules/experts.py,sha256=KXwWCmDrCffu9HW3yNewqWc1e5rnPI5Rnc981w_5M7U,17896
3
+ ripple_down_rules/helpers.py,sha256=X1psHOqrb4_xYN4ssQNB8S9aRKKsqgihAyWJurN0dqk,5499
4
+ ripple_down_rules/rdr.py,sha256=KsZbAbOs8U2PL19YOjFqSer8coXkSMDL3ztIrWHmTCA,62833
5
+ ripple_down_rules/rdr_decorators.py,sha256=xoBGsIJMkJYUdsrsEaPZqoAsGuXkuVZAKCoP-xD2Iv8,11668
6
+ ripple_down_rules/rules.py,sha256=W-y4aGKrGn2Xc7fbn1PNyz_jUzufFMtYwhLYiST28Ww,29213
7
+ ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
8
+ ripple_down_rules/utils.py,sha256=1fiSF4MOaOUrxlMz8sZA_e10258sMWuX5fG9WDawd2o,76674
9
+ ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
10
+ ripple_down_rules/datastructures/callable_expression.py,sha256=IrlnufVsKrUDLVkc2owoFQ05oSOby3HiGuNXoFVj4Dw,13494
11
+ ripple_down_rules/datastructures/case.py,sha256=dfLnrjsHIVF2bgbz-4ID7OdQvw68V71btCeTK372P-g,15667
12
+ ripple_down_rules/datastructures/dataclasses.py,sha256=3vX52WrAHgVyw0LUSgSBOVFaQNTSxU8hQpdr7cW-tSg,13278
13
+ ripple_down_rules/datastructures/enums.py,sha256=CvcROl8fE7A6uTbMfs2lLpyxwS_ZFtFcQlBDDKFfoHc,6059
14
+ ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ ripple_down_rules/user_interface/gui.py,sha256=K6cvA9TOXIDpk0quGCamrWqDRlvz0QruDaTj4Y4PWWI,28544
16
+ ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=RLdPqPxx-a0Sh74UZWyRBuxS_OKeXJnzoDfms4i-Aus,7710
17
+ ripple_down_rules/user_interface/object_diagram.py,sha256=FEa2HaYR9QmTE6NsOwBvZ0jqmu3DKyg6mig2VE5ZP4Y,4956
18
+ ripple_down_rules/user_interface/prompt.py,sha256=WPbw_8_-8SpF2ISyRZRuFwPKBEuGC4HaX3lbCPFHhh8,10314
19
+ ripple_down_rules/user_interface/template_file_creator.py,sha256=uSbosZS15MOR3Nv7M3MrFuoiKXyP4cBId-EK3I6stHM,13660
20
+ ripple_down_rules-0.6.30.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
21
+ ripple_down_rules-0.6.30.dist-info/METADATA,sha256=5y9rt2-t0p_N1BJt2_fMrLzYk0KZl5W8uDDB27LHiWw,48294
22
+ ripple_down_rules-0.6.30.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ ripple_down_rules-0.6.30.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
24
+ ripple_down_rules-0.6.30.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- ripple_down_rules/__init__.py,sha256=XvJJjrncdmKjrvKVVILZq7uuTOYiohoP3iLBN_oKp_M,99
2
- ripple_down_rules/experts.py,sha256=bQfDB7RZE_CANVkpBMb9bi8ZWFwk5p7hn1AzjpaO5ow,12877
3
- ripple_down_rules/helpers.py,sha256=X1psHOqrb4_xYN4ssQNB8S9aRKKsqgihAyWJurN0dqk,5499
4
- ripple_down_rules/rdr.py,sha256=hVqfCRrru6TUnqzY7yGMjiNnxUIFjfL2U7txmGNjeGI,61661
5
- ripple_down_rules/rdr_decorators.py,sha256=xoBGsIJMkJYUdsrsEaPZqoAsGuXkuVZAKCoP-xD2Iv8,11668
6
- ripple_down_rules/rules.py,sha256=N4dEx-xyqxGZpoEYzRd9P5u97_DcDEVLY_UiNhZ4E7g,28726
7
- ripple_down_rules/start-code-server.sh,sha256=otClk7VmDgBOX2TS_cjws6K0UwvgAUJhoA0ugkPCLqQ,949
8
- ripple_down_rules/utils.py,sha256=9xW0N2cB7X4taVANtLg-kVTPS-6ajWZylKkTqw2PKw4,73825
9
- ripple_down_rules/datastructures/__init__.py,sha256=V2aNgf5C96Y5-IGghra3n9uiefpoIm_QdT7cc_C8cxQ,111
10
- ripple_down_rules/datastructures/callable_expression.py,sha256=P3o-z54Jt4rtIczeFWiuHFTNqMzYEOm94OyOP535D6Q,13378
11
- ripple_down_rules/datastructures/case.py,sha256=PJ7_-AdxYic6BO5z816piFODj6nU5J6Jt1YzTFH-dds,15510
12
- ripple_down_rules/datastructures/dataclasses.py,sha256=kI3Kv8GiVR8igMgA_BlKN6djUYxC2mLecvyh19pqQQA,10998
13
- ripple_down_rules/datastructures/enums.py,sha256=CvcROl8fE7A6uTbMfs2lLpyxwS_ZFtFcQlBDDKFfoHc,6059
14
- ripple_down_rules/user_interface/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- ripple_down_rules/user_interface/gui.py,sha256=JCz6vA2kWVvRD6CupoUfMbtGz39cvUlmVnGnw_fY6OA,27635
16
- ripple_down_rules/user_interface/ipython_custom_shell.py,sha256=yp-F8YRWGhj1PLB33HE6vJkdYWFN5Zn2244S2DUWRTM,6576
17
- ripple_down_rules/user_interface/object_diagram.py,sha256=FEa2HaYR9QmTE6NsOwBvZ0jqmu3DKyg6mig2VE5ZP4Y,4956
18
- ripple_down_rules/user_interface/prompt.py,sha256=nLIAviClSmVCY80vQgTazDPs4a1AYmNQmT7sksLDJpE,9449
19
- ripple_down_rules/user_interface/template_file_creator.py,sha256=kwBbFLyN6Yx2NTIHPSwOoytWgbJDYhgrUOVFw_jkDQ4,13522
20
- ripple_down_rules-0.6.28.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
21
- ripple_down_rules-0.6.28.dist-info/METADATA,sha256=fPfcVvFD7eEuG5FKX26HRRWPJkZatA0mfWdarnTqPa8,48294
22
- ripple_down_rules-0.6.28.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- ripple_down_rules-0.6.28.dist-info/top_level.txt,sha256=VeoLhEhyK46M1OHwoPbCQLI1EifLjChqGzhQ6WEUqeM,18
24
- ripple_down_rules-0.6.28.dist-info/RECORD,,