diff --git a/src/core/builtin_concepts.py b/src/core/builtin_concepts.py index 8d2e4e5..6afdf2e 100644 --- a/src/core/builtin_concepts.py +++ b/src/core/builtin_concepts.py @@ -105,6 +105,7 @@ class BuiltinConcepts: # formatting TO_LIST = "__TO_LIST" + TO_DICT = "__TO_DICT" AllBuiltinConcepts = [v for n, v in BuiltinConcepts.__dict__.items() if not n.startswith("__")] @@ -176,6 +177,7 @@ BuiltinContainers = [ BuiltinConcepts.FILTERED, BuiltinConcepts.EXPLANATION, BuiltinConcepts.TO_LIST, + BuiltinConcepts.TO_DICT, ] """ diff --git a/src/core/sheerka/Sheerka.py b/src/core/sheerka/Sheerka.py index 6458ff2..68a805b 100644 --- a/src/core/sheerka/Sheerka.py +++ b/src/core/sheerka/Sheerka.py @@ -113,7 +113,8 @@ class Sheerka(Concept): self.methods_with_context = {"test_using_context"} # only the names, the method is defined in sheerka_methods self.sheerka_methods = { "test": SheerkaMethod(self.test, False), - "test_using_context": SheerkaMethod(self.test_using_context, False) + "test_using_context": SheerkaMethod(self.test_using_context, False), + "test_dict": SheerkaMethod(self.test_dict, False) } self.sheerka_pipeables = {} @@ -1028,6 +1029,20 @@ class Sheerka(Concept): # uncomment the following line to enable colors # logging.StreamHandler.emit = add_coloring_to_emit_ansi(logging.StreamHandler.emit) + def test_dict(self): + bag2 = { + "alpha": "value4", + "beta": ["item1", "item2", "item3", ] + } + bag = { + "a": "value1", + "baba": "value2", + "c": "value1", + "de": ["item1", "item2", "item3", ], + "e": bag2 + } + return self.new(BuiltinConcepts.TO_DICT, body=bag) + def to_profile(): sheerka = Sheerka() diff --git a/src/core/sheerka/services/SheerkaAdmin.py b/src/core/sheerka/services/SheerkaAdmin.py index e762704..3087b77 100644 --- a/src/core/sheerka/services/SheerkaAdmin.py +++ b/src/core/sheerka/services/SheerkaAdmin.py @@ -3,6 +3,7 @@ import time from os import path from core.builtin_concepts import BuiltinConcepts, BuiltinContainers +from core.builtin_helpers import ensure_concept from core.concept import Concept from core.sheerka.services.sheerka_service import BaseService @@ -22,6 +23,7 @@ class SheerkaAdmin(BaseService): self.sheerka.bind_service_method(self.cache, False) self.sheerka.bind_service_method(self.restore, True) self.sheerka.bind_service_method(self.concepts, False) + self.sheerka.bind_service_method(self.desc, False) self.sheerka.bind_service_method(self.last_created_concept, False) self.sheerka.bind_service_method(self.last_ret, False) self.sheerka.bind_service_method(self.last_error_ret, False) @@ -122,6 +124,28 @@ class SheerkaAdmin(BaseService): def concepts(self): return self.sheerka.new(BuiltinConcepts.TO_LIST, body=self.sheerka.sdp.list(self.sheerka.CONCEPTS_BY_ID_ENTRY)) + def desc(self, *concepts): + ensure_concept(*concepts) + res = [] + for c in concepts: + bag = { + "id": c.id, + "name": c.name, + "key": c.key, + "definition": c.get_metadata().definition, + "type": c.get_metadata().definition_type, + "body": c.get_metadata().body, + "where": c.get_metadata().where, + "pre": c.get_metadata().pre, + "post": c.get_metadata().post, + "ret": c.get_metadata().ret, + "vars": c.get_metadata().variables, + "props": c.get_metadata().props, + } + res.append(self.sheerka.new(BuiltinConcepts.TO_DICT, body=bag)) + + return res[0] if len(res) == 1 else self.sheerka.new(BuiltinConcepts.TO_LIST, body=res) + def format_rules(self): return self.sheerka.new(BuiltinConcepts.TO_LIST, items=self.sheerka.get_format_rules()) diff --git a/src/core/sheerka/services/SheerkaDebugManager.py b/src/core/sheerka/services/SheerkaDebugManager.py index b6e905f..dab2d90 100644 --- a/src/core/sheerka/services/SheerkaDebugManager.py +++ b/src/core/sheerka/services/SheerkaDebugManager.py @@ -34,13 +34,16 @@ class BaseDebugLogger: def debug_entering(self, **kwargs): pass - def debug_var(self, name, value, is_error=False): + def debug_var(self, name, value, is_error=False, hint=None): pass def debug_rule(self, rule, results): pass - def debug_log(self, text): + def debug_log(self, text, is_error=False): + pass + + def is_enabled(self): pass @@ -48,6 +51,9 @@ class NullDebugLogger(BaseDebugLogger): def __init__(self): pass + def is_enabled(self): + return False + class ConsoleDebugLogger(BaseDebugLogger): @@ -60,6 +66,9 @@ class ConsoleDebugLogger(BaseDebugLogger): self.debug_id = debug_id self.is_highlighted = "" + def is_enabled(self): + return True + def debug_entering(self, **kwargs): super().debug_entering(**kwargs) @@ -71,17 +80,18 @@ class ConsoleDebugLogger(BaseDebugLogger): self.debug_manager.debug(self.prefix() + str_text) self.debug_manager.debug(self.prefix() + str_vars) - def debug_var(self, name, value, is_error=False): - enabled = self.debug_manager.compute_var_debug(self.service_name, - self.method_name, - self.context_id, - name, - self.context_id) + def debug_var(self, name, value, is_error=False, hint=None): + enabled = is_error or self.debug_manager.compute_var_debug(self.service_name, + self.method_name, + self.context_id, + name, + self.context_id) if enabled == False: return color = 'red' if is_error else 'green' - str_text = f"{CCM[color]}..{name}={CCM['reset']}" + hint_str = f"({hint})" if hint is not None else "" + str_text = f"{CCM[color]}..{name}{hint_str}={CCM['reset']}" str_vars = "" if isinstance(enabled, str) else pp.pformat(value) if "\n" not in str(str_vars): self.debug_manager.debug(self.prefix() + str_text + str_vars) @@ -101,8 +111,9 @@ class ConsoleDebugLogger(BaseDebugLogger): self.debug_manager.debug(self.prefix() + str_text) self.debug_manager.debug(self.prefix() + str_vars) - def debug_log(self, text): - self.debug_manager.debug(self.prefix() + f"{CCM['blue']}..{text}{CCM['reset']}") + def debug_log(self, text, is_error=False): + color = 'red' if is_error else 'blue' + self.debug_manager.debug(self.prefix() + f"{CCM[color]}..{text}{CCM['reset']}") def prefix(self): return f"[{self.context_id:2}][{self.debug_id:2}] {self.is_highlighted}" @@ -271,12 +282,8 @@ class SheerkaDebugManager(BaseService): res = {} for prop in props: res[prop] = evaluate_expression(prop, bag) - # res = { - # "return_values": to_inspect.values.get("return_values", None) - # } - pp.pprint(res) - return None + return self.sheerka.new(BuiltinConcepts.TO_DICT, body=res) def debug(self, *args, **kwargs): print(*args, **kwargs) @@ -437,4 +444,4 @@ class SheerkaDebugManager(BaseService): return self.sheerka.ret(SheerkaDebugManager.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS)) def get_debug_settings(self): - return self.debug_vars_settings + return self.sheerka.new(BuiltinConcepts.TO_LIST, body=self.debug_vars_settings) diff --git a/src/core/sheerka/services/SheerkaDump.py b/src/core/sheerka/services/SheerkaDump.py index 946ad5e..129cbef 100644 --- a/src/core/sheerka/services/SheerkaDump.py +++ b/src/core/sheerka/services/SheerkaDump.py @@ -21,7 +21,7 @@ class SheerkaDump(BaseService): super().__init__(sheerka) def initialize(self): - self.sheerka.bind_service_method(self.dump_desc, True, "desc") # has_side_effect 'cause concept is evaluated + self.sheerka.bind_service_method(self.dump_desc, True, "old_desc") # has_side_effect 'cause concept is evaluated self.sheerka.bind_service_method(self.dump_sdp, False, "dump_sdp") def dump_desc(self, *concept_names, eval=False): diff --git a/src/core/sheerka/services/SheerkaOut.py b/src/core/sheerka/services/SheerkaOut.py index 0e7f0c7..fea958c 100644 --- a/src/core/sheerka/services/SheerkaOut.py +++ b/src/core/sheerka/services/SheerkaOut.py @@ -16,7 +16,8 @@ class SheerkaOut(BaseService): self.sheerka.bind_service_method(self.process_return_values, False) def create_out_tree(self, context, obj): - return self.create_out_tree_recursive(context, {'__obj': obj}, DeveloperVisitor(self, set(), 0)) + debugger = context.get_debugger("Out.visitor", "create_out_tree") + return self.create_out_tree_recursive(context, {'__obj': obj}, DeveloperVisitor(self, debugger, set(), 0)) def create_out_tree_recursive(self, context, bag, visitor): debugger = context.get_debugger(SheerkaOut.NAME, "create_out_tree") @@ -40,8 +41,7 @@ class SheerkaOut(BaseService): visitor.already_seen.add(rule.id) bag.update(as_bag(current_obj)) # update with the current obj attributes - visitor.visit(context, rule.compiled_action, bag) - res = visitor.get_result() + res = visitor.visit(context, rule.compiled_action, bag) if res is None: debugger.debug_log(f"No matching rule.") @@ -67,9 +67,7 @@ class SheerkaOut(BaseService): if out_tree: for visitor in self.out_visitors: - visitor.visit(context, out_tree, None) - if hasattr(visitor, "finalize"): - visitor.finalize() + visitor.visit(out_tree) return self.sheerka.ret(self.NAME, True, self.sheerka.new(BuiltinConcepts.SUCCESS)) return self.sheerka.ret(self.NAME, False, self.sheerka.new(BuiltinConcepts.NO_RESULT)) diff --git a/src/core/sheerka/services/SheerkaRuleManager.py b/src/core/sheerka/services/SheerkaRuleManager.py index 6beed59..211af5f 100644 --- a/src/core/sheerka/services/SheerkaRuleManager.py +++ b/src/core/sheerka/services/SheerkaRuleManager.py @@ -63,6 +63,15 @@ class FormatAstNode: return ", ".join(repr(item) for item in items) + def clone(self, instance, props, **kwargs): + for prop_name in props: + setattr(instance, prop_name, getattr(self, prop_name)) + + for k, v in kwargs.items(): + setattr(instance, k, v) + + return instance + @dataclass class FormatAstRawText(FormatAstNode): @@ -73,9 +82,15 @@ class FormatAstRawText(FormatAstNode): class FormatAstVariable(FormatAstNode): name: str format: Union[str, None] = None + debug: bool = False value: object = None index: object = None + def clone(self, **kwargs): + return super().clone(FormatAstVariable(self.name), + ("format", "debug", "value", "index"), + **kwargs) + @dataclass class FormatAstVariableNotFound(FormatAstNode): @@ -93,29 +108,50 @@ class FormatAstList(FormatAstNode): items_prop: str = None # where to search the list if variable does not resolve to an iterable recurse_on: str = None recursion_depth: int = 0 + debug: bool = False + prefix: str = None + suffix: str = None + show_index: bool = False + items: object = None + def clone(self, **kwargs): + return super().clone( + FormatAstList(self.variable), + ("items_prop", "recurse_on", "recursion_depth", "debug", "prefix", "suffix", "show_index", "items"), + **kwargs) + + +@dataclass +class FormatAstDict(FormatAstNode): + variable: str + items_prop: str = None # where to search the dict if variable does not resolve to an iterable + debug: bool = False + prefix: str = None + suffix: str = None + + items: object = None + + def clone(self, **kwargs): + return super().clone( + FormatAstDict(self.variable), + ("items_prop", "debug", "prefix", "suffix", "items"), + **kwargs) + @dataclass class FormatAstColor(FormatAstNode): - def __init__(self, color, format_ast): - self.color = color - self.format_ast = format_ast + color: str + format_ast: FormatAstNode def __repr__(self): return f"{self.color}({self.format_ast})" - def __eq__(self, other): - if id(self) == id(other): - return True - - if not isinstance(other, FormatAstColor): - return False - - return self.color == other.color and self.format_ast == other.format_ast - - def __hash__(self): - return hash((self.color, self.format_ast)) + def clone(self, **kwargs): + return super().clone( + FormatAstColor(self.color, self.format_ast), + (), + **kwargs) @dataclass @@ -125,21 +161,19 @@ class FormatAstFunction(FormatAstNode): kwargs: dict = None +@dataclass class FormatAstSequence(FormatAstNode): - def __init__(self, items): - self.items = items + items: list + debug: bool = False def __repr__(self): return "FormatAstSequence(" + self.repr_value(self.items) + ")" - def __eq__(self, other): - if id(self) == id(other): - return True - - if not isinstance(other, FormatAstSequence): - return False - - return self.items == other.items + def clone(self, **kwargs): + return super().clone( + FormatAstSequence(self.items), + ("debug",), + **kwargs) class FormatRuleParser(IterParser): @@ -170,6 +204,9 @@ class FormatRuleParser(IterParser): if value[0] in ("'", '"'): return value[1:-1] + if value in ("True", "False"): + return bool(value) + try: return int(value) except ValueError: @@ -278,6 +315,8 @@ class FormatRuleParser(IterParser): return self.return_color(func_name.value, args, kwargs) elif func_name.value == "list": return self.return_list(args, kwargs) + elif func_name.value == "dict": + return self.return_dict(args, kwargs) return FormatAstFunction(func_name.value, self.to_text(args), self.to_text(kwargs)) @@ -347,13 +386,19 @@ class FormatRuleParser(IterParser): return FormatAstColor(color, FormatAstVariable(variable, vformat)) def return_list(self, args, kwargs): + """ + Looking for variable_name, [recurse_on], [recursion_depth], [items_prop] + :param args: + :param kwargs: + :return: + """ len_args = len(args) if len_args < 1: self.error_sink = FormatRuleSyntaxError("variable name not found", None) return None - if len_args > 3: - self.error_sink = FormatRuleSyntaxError("too many positional arguments", args[3][0]) + if len_args > 4: + self.error_sink = FormatRuleSyntaxError("too many positional arguments", args[4][0]) return None variable_name = get_text_from_tokens(args[0]) @@ -364,6 +409,10 @@ class FormatRuleParser(IterParser): elif len_args == 3: recursion_depth = self.to_value(args[1]) recurse_on = self.to_value(args[2]) + elif len_args == 4: + recursion_depth = self.to_value(args[1]) + recurse_on = self.to_value(args[2]) + items_prop = self.to_value(args[3]) if "recurse_on" in kwargs: recurse_on = self.to_value(kwargs["recurse_on"]) @@ -383,6 +432,34 @@ class FormatRuleParser(IterParser): return FormatAstList(variable_name, items_prop, recurse_on, recursion_depth) + def return_dict(self, args, kwargs): + len_args = len(args) + if len_args < 1: + self.error_sink = FormatRuleSyntaxError("variable name not found", None) + return None + + if len_args > 1: + self.error_sink = FormatRuleSyntaxError("too many positional arguments", args[1][0]) + return None + + variable_name = get_text_from_tokens(args[0]) + + kwargs_parameters = {} + for prop in ("items_prop", "prefix", "suffix", "debug"): + if prop in kwargs: + kwargs_parameters[prop] = self.to_value(kwargs[prop]) + + if "debug" in kwargs_parameters: + if "prefix" not in kwargs_parameters: + kwargs_parameters["prefix"] = "{" + if "suffix" not in kwargs_parameters: + kwargs_parameters["suffix"] = "}" + + if self.error_sink: + return None + + return FormatAstDict(variable_name, **kwargs_parameters) + @dataclass() class RulePredicate: @@ -556,6 +633,9 @@ class SheerkaRuleManager(BaseService): Rule("print", "Display formatted list", "isinstance(__ret_container, BuiltinConcepts.TO_LIST)", "list(__ret_container)"), + Rule("print", "Display formatted dict", + "isinstance(__ret_container, BuiltinConcepts.TO_DICT)", + "dict(__ret_container)"), ] for r in rules: diff --git a/src/core/utils.py b/src/core/utils.py index 60b5f0b..b70e0a1 100644 --- a/src/core/utils.py +++ b/src/core/utils.py @@ -2,11 +2,11 @@ import ast import importlib import inspect import pkgutil -import re from cache.Cache import Cache from core.ast_helpers import ast_to_props from core.tokenizer import TokenKind, Tokenizer +from pyparsing import * default_debug_name = "*default*" debug_activated = set() @@ -38,6 +38,15 @@ PRIMITIVES_TYPES = (str, bool, type(None), int, float, list, dict, set, bytes, t expressions_cache = Cache() +ESC = Literal('\x1b') +integer = Word(nums) +escapeSeq = Combine(ESC + '[' + Optional(delimitedList(integer, ';')) + + oneOf(list(alphas))) + + +def no_color_str(text): + return Suppress(escapeSeq).transformString(str(text)) + def my_debug(*args, check_started=None): """ diff --git a/src/evaluators/DefConceptEvaluator.py b/src/evaluators/DefConceptEvaluator.py index 233a222..16fca9d 100644 --- a/src/evaluators/DefConceptEvaluator.py +++ b/src/evaluators/DefConceptEvaluator.py @@ -129,12 +129,14 @@ class DefConceptEvaluator(OneReturnValueEvaluator): This function can only be a draft, as there may be tons of different situations I guess that it can only be complete when will we have access to Sheerka memory """ + debugger = context.get_debugger(DefConceptEvaluator.NAME, "get_variables") # # Case of NameNode # if isinstance(ret_value, NameNode): names = [str(t.value) for t in ret_value.tokens if t.type in ( TokenKind.IDENTIFIER, TokenKind.STRING, TokenKind.KEYWORD)] + debugger.debug_var("names", names, hint="from NameNode") return set(filter(lambda x: x in concept_name and context.sheerka.not_is_variable(x), names)) # @@ -143,6 +145,7 @@ class DefConceptEvaluator(OneReturnValueEvaluator): if isinstance(ret_value.value, ParserResultConcept) and isinstance(ret_value.value.value, ParsingExpression): visitor = ConceptOrRuleNameVisitor() visitor.visit(ret_value.value.value) + debugger.debug_var("names", visitor.names, hint="from BNF") return set(visitor.names) # @@ -152,6 +155,7 @@ class DefConceptEvaluator(OneReturnValueEvaluator): if len(concept_name) > 1: visitor = UnreferencedVariablesVisitor(context) names = visitor.get_names(python_node.ast_) + debugger.debug_var("names", names, hint="from python node") return set(filter(lambda x: x in concept_name and context.sheerka.not_is_variable(x), names)) else: return set() @@ -168,6 +172,7 @@ class DefConceptEvaluator(OneReturnValueEvaluator): for identifier in [i for i in concept_name if str(i).isalnum()]: if identifier in tokens: variables.add(identifier) + debugger.debug_var("names", variables, hint="from concept") return variables return set() diff --git a/src/evaluators/PythonEvaluator.py b/src/evaluators/PythonEvaluator.py index 0671f51..857d296 100644 --- a/src/evaluators/PythonEvaluator.py +++ b/src/evaluators/PythonEvaluator.py @@ -92,6 +92,9 @@ class PythonEvaluator(OneReturnValueEvaluator): debugger = context.get_debugger(PythonEvaluator.NAME, "eval") debugger.debug_entering(node=node) + exception_debugger = context.get_debugger("Exceptions", PythonEvaluator.NAME + ".eval") + get_trace_back = context.debug_enabled or exception_debugger.is_enabled() + context.log(f"Evaluating python node {node}.", self.name) # If we evaluate a Concept metadata which is NOT the body ex (pre, post, where...) @@ -136,9 +139,12 @@ class PythonEvaluator(OneReturnValueEvaluator): except Exception as ex: if concepts_entries is None: concepts_entries = self.get_concepts_entries_from_globals(my_globals) - errors.append(PythonEvalError(ex, - traceback.format_exc() if context.debug_enabled else None, - self.get_concepts_values_from_globals(globals_, concepts_entries))) + eval_error = PythonEvalError(ex, + traceback.format_exc() if get_trace_back else None, + self.get_concepts_values_from_globals(globals_, concepts_entries)) + errors.append(eval_error) + exception_debugger.debug_var("exception", eval_error.error, is_error=True) + exception_debugger.debug_var("trace", eval_error.traceback, is_error=True) if evaluated == NotInit: if len(errors) == 1: diff --git a/src/jupyter/SheerkaKernel.py b/src/jupyter/SheerkaKernel.py index 5218259..5eb5ef5 100644 --- a/src/jupyter/SheerkaKernel.py +++ b/src/jupyter/SheerkaKernel.py @@ -1,7 +1,8 @@ +from IPython.core.display import JSON from core.sheerka.Sheerka import Sheerka from ipykernel.ipkernel import IPythonKernel from ipykernel.kernelapp import IPKernelApp -from sheerkapickle import encode +from sheerkapickle.SheerkaPickler import SheerkaPickler class SheerkaKernel(IPythonKernel): @@ -25,12 +26,19 @@ class SheerkaKernel(IPythonKernel): def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): result = self.sheerka.evaluate_user_input(code) + # md = Markdown(f'{input_data}') + as_json = JSON(SheerkaPickler(self.sheerka).flatten(result)) + a = { + "a": "value1", + "b": "value2", + } if not silent: display_data_content = { 'data': { - 'text/plain': str(result), - 'application/json': encode(self.sheerka, result) + # 'text/plain': str(result), + # "text/markdown": md._repr_markdown_(), + 'application/json': JSON(a)._repr_json_(), }, 'metadata': { 'application/json': {'expanded': True} @@ -38,9 +46,8 @@ class SheerkaKernel(IPythonKernel): 'execution_count': self.sheerka.execution_count, } - stream_content = {'name': 'stdout', 'text': display_data_content} - # self.send_response(self.iopub_socket, "stream", stream_content) - self.send_response(self.iopub_socket, "execute_result", display_data_content) + + self.send_response(self.iopub_socket, "display_data", display_data_content) return {'status': 'ok', # The base class increments the execution count diff --git a/src/out/AsStrVisitor.py b/src/out/AsStrVisitor.py new file mode 100644 index 0000000..4db5b19 --- /dev/null +++ b/src/out/AsStrVisitor.py @@ -0,0 +1,93 @@ +import re + +from core.sheerka.services.SheerkaRuleManager import FormatAstNode +from core.utils import CONSOLE_COLORS_MAP as CCM, no_color_str +from out.OutVisitor import OutVisitor + +get_start = re.compile(r"^([\(\{\[]\w*)") + + +class AsStrVisitor(OutVisitor): + + def __init__(self, expand=False): + self.expand = expand + + def visit_FormatAstRawText(self, format_ast): + return str(format_ast.text) + + def visit_FormatAstVariable(self, format_ast): + if isinstance(format_ast.value, FormatAstNode): + value = self.visit(format_ast.value) + else: + value = format_ast.value + + if format_ast.debug and isinstance(value, str): + value = "'" + str(value).translate(str.maketrans({"'": r"\'"})) + "'" + + return str(value) + + def visit_FormatAstVariableNotFound(self, format_ast): + return CCM["red"] + format_ast.name + CCM["reset"] + + def visit_FormatAstColor(self, format_ast): + return CCM[format_ast.color] + self.visit(format_ast.format_ast) + CCM["reset"] + + def visit_FormatAstSequence(self, format_ast): + return "".join([self.visit(item) for item in format_ast.items]) + + def visit_FormatAstList(self, format_ast): + first = True + result = "" + if format_ast.prefix: + result += format_ast.prefix + + sep = ",\n" if self.expand else ", " if format_ast.debug else "\n" + + for item in format_ast.items: + if not first: + result += sep + if format_ast.show_index: + result += f"[{item.index}] " + result += self.visit(item) + first = False + if format_ast.suffix: + result += format_ast.suffix + + return result + + def visit_FormatAstDict(self, format_ast): + first = True + result = "" + + keys_values = [] + max_len = 0 + for k, v in format_ast.items: + value = self.visit(k) + len_value = len(no_color_str(value)) + if len_value > max_len: + max_len = len_value + keys_values.append(value) + + if format_ast.prefix: + result += format_ast.prefix + + sep = ",\n" if self.expand else ", " if format_ast.debug else "\n" + + for i, (k, v) in enumerate(format_ast.items): + start = "" if first else sep + + key = f"{keys_values[i]:<{max_len}}" if (self.expand or not format_ast.debug) else keys_values[i] + colon = ": " + indent = len(no_color_str(key)) + len(colon) + value = self.visit(v) + if self.expand: + if m := get_start.match(no_color_str(value)): + indent += len(m.group(1)) + value = value.replace("\n", "\n" + " " * indent) + first = False + + result += start + key + colon + value + + if format_ast.suffix: + result += format_ast.suffix + return result diff --git a/src/out/ConsoleVisistor.py b/src/out/ConsoleVisistor.py index 630d692..b6d2079 100644 --- a/src/out/ConsoleVisistor.py +++ b/src/out/ConsoleVisistor.py @@ -1,55 +1,43 @@ -from out.OutVisitor import OutVisitor -from core.sheerka.services.SheerkaRuleManager import FormatAstNode +from out.AsStrVisitor import AsStrVisitor -class ConsoleVisitor(OutVisitor): +class ConsoleVisitor(AsStrVisitor): """ Prints to the console """ - COLORS = { - "reset": "\u001b[0m", - "black": "\u001b[30m", - "red": "\u001b[31m", - "green": "\u001b[32m", - "yellow": "\u001b[33m", - "blue": "\u001b[34m", - "magenta": "\u001b[35m", - "cyan": "\u001b[36m", - "white": "\u001b[37m", - } - def __init__(self): + def __init__(self, expand_mode="auto"): + super().__init__() self.out = print + self.expand_mode = expand_mode - def finalize(self): - self.out("") + def visit_FormatAstRawText(self, format_ast): + self.out(super().visit_FormatAstRawText(format_ast)) - def visit_FormatAstRawText(self, context, format_ast, bag): - self.out(format_ast.text, end="") + def visit_FormatAstVariable(self, format_ast): + self.out(super().visit_FormatAstVariable(format_ast)) - def visit_FormatAstVariable(self, context, format_ast, bag): - if isinstance(format_ast.value, FormatAstNode): - self.visit(context, format_ast.value, bag) - return - self.out(format_ast.value, end="") + def visit_FormatAstVariableNotFound(self, format_ast): + self.out(super().visit_FormatAstVariableNotFound(format_ast)) - def visit_FormatAstVariableNotFound(self, context, format_ast, bag): - self.out(self.COLORS["red"] + format_ast.name + self.COLORS["reset"], end="") + def visit_FormatAstColor(self, format_ast): + self.out(super().visit_FormatAstColor(format_ast)) - def visit_FormatAstSequence(self, context, format_ast, bag): - for item in format_ast.items: - self.visit(context, item, bag) + def visit_FormatAstSequence(self, format_ast): + visitor = AsStrVisitor() + self.out(visitor.visit_FormatAstSequence(format_ast)) - def visit_FormatAstList(self, context, format_ast, bag): - first = True - for item in format_ast.items: - if not first: - self.out("") # print new line - self.visit(context, item, bag) - first = False + def visit_FormatAstList(self, format_ast): + visitor = AsStrVisitor() + res = visitor.visit_FormatAstList(format_ast) + self.out(res) - def visit_FormatAstColor(self, context, format_ast, bag): - self.out(self.COLORS[format_ast.color], end="") - self.visit(context, format_ast.format_ast, bag) - self.out(self.COLORS["reset"], end="") + def visit_FormatAstDict(self, format_ast): + if self.expand_mode == "always": + expand = True + else: + expand = False + visitor = AsStrVisitor(expand) + res = visitor.visit_FormatAstDict(format_ast) + self.out(res) diff --git a/src/out/DeveloperVisitor.py b/src/out/DeveloperVisitor.py index 7c9d631..cfe99fe 100644 --- a/src/out/DeveloperVisitor.py +++ b/src/out/DeveloperVisitor.py @@ -1,31 +1,43 @@ -from core.sheerka.services.SheerkaRuleManager import FormatAstVariable, FormatAstVariableNotFound, FormatAstSequence, \ - FormatAstColor, FormatAstList, FormatAstRawText +from core.sheerka.services.SheerkaRuleManager import FormatAstVariable, FormatAstVariableNotFound, FormatAstColor, \ + FormatAstList, FormatAstRawText, FormatAstDict from core.utils import evaluate_expression, as_bag -from out.OutVisitor import OutVisitor fstring = compile('f"{value:{format}}"', "DeveloperVisitor.fstring", mode="eval") -class DeveloperVisitor(OutVisitor): +class DeveloperVisitor: """ This visitor is used to resolve all the variables as well as all the lists Once completed, it will be passed to the ConsoleVisitor for console print """ - def __init__(self, sheerka_out, already_seen, list_recursion_depth): + def __init__(self, sheerka_out, debugger, already_seen, list_recursion_depth): self._result = None self.sheerka_out = sheerka_out + self.debugger = debugger self.already_seen = already_seen self.list_recursion_depth = list_recursion_depth + def visit(self, context, format_ast, bag): + name = format_ast.__class__.__name__ + + method = 'visit_' + name + visit_method = getattr(self, method, self.generic_visit) + return visit_method(context, format_ast, bag) + + def generic_visit(self, context, format_ast, bag): + pass + def visit_FormatAstRawText(self, context, format_ast, bag): - if context.debug_enabled: - context.debug_entering("DeveloperVisitor", "visit_FormatAstRawText", format_ast=format_ast, bag=bag) - return self.set_result(format_ast) + if self.debugger.is_enabled(): + debug_bag = {"format_ast": format_ast, "bag": bag} + self.debugger.debug_log(f"Entering visit_FormatAstRawText with {debug_bag}") + return format_ast def visit_FormatAstVariable(self, context, format_ast, bag): - if context.debug_enabled: - context.debug_entering("DeveloperVisitor", "visit_FormatAstVariable", format_ast=format_ast, bag=bag) + if self.debugger.is_enabled(): + debug_bag = {"format_ast": format_ast, "bag": bag} + self.debugger.debug_log(f"Entering visit_FormatAstVariable with {debug_bag}") try: value = evaluate_expression(format_ast.name, bag) @@ -33,6 +45,8 @@ class DeveloperVisitor(OutVisitor): "__obj": value, format_ast.name: value } + # create a bag entry with the last part of the variable + # eg: for __ret.status, creates an entry status try: index = format_ast.name.rindex(".") sub_bag[format_ast.name[index + 1:]] = value @@ -43,32 +57,31 @@ class DeveloperVisitor(OutVisitor): if format_ast.format: res = eval(fstring, {"value": res, "format": format_ast.format}) - return self.set_result(FormatAstVariable(format_ast.name, - format_ast.format, - res, - format_ast.index)) - + return format_ast.clone(value=res) except NameError as error: context.debug("DeveloperVisitor", "visit_FormatAstList", "evaluate_expression", error, is_error=True) - return self.set_result(FormatAstVariableNotFound(format_ast.name)) + return FormatAstVariableNotFound(format_ast.name) def visit_FormatAstSequence(self, context, format_ast, bag): - if context.debug_enabled: - context.debug_entering("DeveloperVisitor", "visit_FormatAstSequence", format_ast=format_ast, bag=bag) - return self.set_result(FormatAstSequence([self.visit(context, item, bag) for item in format_ast.items])) + if self.debugger.is_enabled(): + debug_bag = {"format_ast": format_ast, "bag": bag} + self.debugger.debug_log(f"Entering visit_FormatAstSequence with {debug_bag}") + return format_ast.clone(items=[self.visit(context, item, bag) for item in format_ast.items]) def visit_FormatAstColor(self, context, format_ast, bag): - if context.debug_enabled: - context.debug_entering("DeveloperVisitor", "visit_FormatAstColor", format_ast=format_ast, bag=bag) - return self.set_result(FormatAstColor(format_ast.color, self.visit(context, format_ast.format_ast, bag))) + if self.debugger.is_enabled(): + debug_bag = {"format_ast": format_ast, "bag": bag} + self.debugger.debug_log(f"Entering visit_FormatAstColor with {debug_bag}") + return format_ast.clone(format_ast=self.visit(context, format_ast.format_ast, bag)) def visit_FormatAstList(self, context, format_ast, bag): - if context.debug_enabled: - context.debug_entering("DeveloperVisitor", "visit_FormatAstList", format_ast=format_ast, bag=bag) + if self.debugger.is_enabled(): + debug_bag = {"format_ast": format_ast, "bag": bag} + self.debugger.debug_log(f"Entering visit_FormatAstList with {debug_bag}") try: value = evaluate_expression(format_ast.variable, bag) if value is None: - return self.set_result(FormatAstVariable(format_ast.variable, format_ast.format, None)) + return FormatAstVariable(format_ast.variable, format_ast.format, value=None) if hasattr(value, "__iter__"): items = value @@ -78,22 +91,29 @@ class DeveloperVisitor(OutVisitor): items = evaluate_expression(f"self.{items_props}", {"self": value}) if not hasattr(items, "__iter__"): # Definition error ? No list found - return self.set_result(FormatAstVariable(format_ast.variable, None, value)) + return FormatAstVariable(format_ast.variable, value=value) - recursion_depth, recurse_on = self.get_recurse_info(value, format_ast.recursion_depth, format_ast.recurse_on) + recursion_depth, recurse_on = self.get_recurse_info(value, format_ast.recursion_depth, + format_ast.recurse_on) result = [] # TODO change into generator for i, item in enumerate(items): bag["__item"] = item - sub_visitor = DeveloperVisitor(self.sheerka_out, set(), self.list_recursion_depth) - result.append(sub_visitor.visit(context, FormatAstVariable("__item", None, item, i), bag)) + sub_visitor = DeveloperVisitor(self.sheerka_out, self.debugger, set(), self.list_recursion_depth) + result.append(sub_visitor.visit(context, FormatAstVariable("__item", + debug=format_ast.debug, + value=item, + index=i), bag)) # recursion management recursion_depth, recurse_on = self.get_recurse_info(item, recursion_depth, recurse_on) if recursion_depth > 0: sub_items = evaluate_expression(recurse_on, as_bag(item)) if sub_items and hasattr(sub_items, "__iter__"): - sub_visitor = DeveloperVisitor(self.sheerka_out, set(), self.list_recursion_depth + 1) + sub_visitor = DeveloperVisitor(self.sheerka_out, + self.debugger, + set(), + self.list_recursion_depth + 1) bag[f"__{recurse_on}"] = sub_items sub_items = sub_visitor.visit(context, FormatAstList(f"__{recurse_on}", @@ -102,29 +122,64 @@ class DeveloperVisitor(OutVisitor): recursion_depth - 1), bag) result.append(sub_items) - return self.set_result(FormatAstList(variable=format_ast.variable, - items_prop=format_ast.items_prop, - recurse_on=recurse_on, - recursion_depth=recursion_depth, - items=result)) + return format_ast.clone(recurse_on=recurse_on, recursion_depth=recursion_depth, items=result) except NameError as error: - context.debug("DeveloperVisitor", "visit_FormatAstList", "evaluate_expression", error, is_error=True) + self.debugger.debug_log(error, is_error=True) var_name = format_ast.variable if error.args[0] == format_ast.variable else \ format_ast.variable + "." + error.args[0] - return self.set_result(FormatAstVariableNotFound(var_name)) + return FormatAstVariableNotFound(var_name) def visit_FormatAstFunction(self, context, format_ast, bag): - if context.debug_enabled: - context.debug_entering("DeveloperVisitor", "visit_FormatAstFunction", format_ast=format_ast, bag=bag) - unknown = FormatAstColor("red", FormatAstRawText(f"function '{format_ast.name}' is unknown")) - return self.set_result(unknown) + if self.debugger.is_enabled(): + debug_bag = {"format_ast": format_ast, "bag": bag} + self.debugger.debug_log(f"Entering visit_FormatAstFunction with {debug_bag}") + return FormatAstColor("red", FormatAstRawText(f"function '{format_ast.name}' is unknown")) - def set_result(self, result): - self._result = result - return result + def visit_FormatAstDict(self, context, format_ast, bag): + if self.debugger.is_enabled(): + debug_bag = {"format_ast": format_ast, "bag": bag} + self.debugger.debug_log(f"Entering visit_FormatAstDict with {debug_bag}") - def get_result(self): - return self._result + try: + result = [] + + value = evaluate_expression(format_ast.variable, bag) + if value is None: + return FormatAstVariable(format_ast.variable, format_ast.format, value=None) + + if isinstance(value, dict): + items = value + else: + # the variable does not resolve to a dictionary, let's look at one of its attribute + items_props = format_ast.items_prop or "body" + items = evaluate_expression(f"self.{items_props}", {"self": value}) + if not isinstance(items, dict): + # Definition error ? No Dictionary found + return FormatAstVariable(format_ast.variable, value=value) + + for i, (k, v) in enumerate(items.items()): + bag["__key"] = k + key_visitor = DeveloperVisitor(self.sheerka_out, self.debugger, set(), self.list_recursion_depth) + key_res = key_visitor.visit(context, + FormatAstVariable("__key", value=k, index=i, debug=format_ast.debug), + bag) + + bag["__value"] = v + value_visitor = DeveloperVisitor(self.sheerka_out, self.debugger, set(), self.list_recursion_depth) + to_visit = FormatAstDict("__value", debug=True, prefix='{', suffix='}') if isinstance(v, dict) else \ + FormatAstList("__value", debug=True, prefix='[', suffix=']') if isinstance(v, list) else \ + FormatAstVariable("__value", value=v, index=k, debug=format_ast.debug) + + value_res = value_visitor.visit(context, to_visit, bag) + + result.append((key_res, value_res)) + + return format_ast.clone(items=result) + except NameError as error: + self.debugger.debug_log(error, is_error=True) + var_name = format_ast.variable if error.args[0] == format_ast.variable else \ + format_ast.variable + "." + error.args[0] + return FormatAstVariableNotFound(var_name) @staticmethod def get_recurse_info(obj, recursion_depth, recurse_on): diff --git a/src/out/OutVisitor.py b/src/out/OutVisitor.py index 1b5b23f..b00e3e5 100644 --- a/src/out/OutVisitor.py +++ b/src/out/OutVisitor.py @@ -1,10 +1,10 @@ class OutVisitor: - def visit(self, context, format_ast, bag): + def visit(self, format_ast): name = format_ast.__class__.__name__ method = 'visit_' + name visit_method = getattr(self, method, self.generic_visit) - return visit_method(context, format_ast, bag) + return visit_method(format_ast) - def generic_visit(self, context, format_ast, bag): + def generic_visit(self, format_ast): pass diff --git a/src/parsers/PythonParser.py b/src/parsers/PythonParser.py index 4b14f41..f1dc9aa 100644 --- a/src/parsers/PythonParser.py +++ b/src/parsers/PythonParser.py @@ -37,10 +37,15 @@ class PythonNode(Node): def __init__(self, source, ast_=None, objects=None): self.source = source - self.ast_ = ast_ if ast_ else ast.parse(source, mode="eval") if source else None + self.ast_ = ast_ # if ast_ else ast.parse(source, mode="eval") if source else None self.objects = objects or {} # when objects (mainly concepts or rules) are recognized in the expression self.compiled = None + def init_ast(self): + if self.ast_ is None and self.source: + self.ast_ = ast.parse(self.source, mode="eval") + return self + def get_compiled(self): if self.compiled is None: self.compiled = compile(self.ast_, "", "eval") @@ -60,10 +65,12 @@ class PythonNode(Node): if self.source != other.source: return False - self_dump = self.get_dump(self.ast_) - other_dump = self.get_dump(other.ast_) + if self.ast_ and other.ast_: + self_dump = self.get_dump(self.ast_) + other_dump = self.get_dump(other.ast_) + return self_dump == other_dump - return self_dump == other_dump + return True def __hash__(self): return hash((self.source, self.ast_.hash)) diff --git a/tests/core/test_SheerkaEvaluateConcept.py b/tests/core/test_SheerkaEvaluateConcept.py index 16fe67a..10c4a1e 100644 --- a/tests/core/test_SheerkaEvaluateConcept.py +++ b/tests/core/test_SheerkaEvaluateConcept.py @@ -318,7 +318,7 @@ class TestSheerkaEvaluateConcept(TestUsingMemoryBasedSheerka): def test_i_can_evaluate_when_compiled_is_set_up_with_return_value(self): sheerka = self.get_sheerka() - python_node = PythonNode("1 +1 ") + python_node = PythonNode("1 +1 ").init_ast() parser_result = ParserResultConcept(parser="who", value=python_node) concept = Concept("to_eval").def_var("prop") diff --git a/tests/core/test_SheerkaRuleManager.py b/tests/core/test_SheerkaRuleManager.py index 152df45..32372d6 100644 --- a/tests/core/test_SheerkaRuleManager.py +++ b/tests/core/test_SheerkaRuleManager.py @@ -7,7 +7,7 @@ from core.global_symbols import RULE_COMPARISON_CONTEXT from core.rule import Rule from core.sheerka.services.SheerkaRuleManager import SheerkaRuleManager, FormatRuleParser, \ FormatAstRawText, FormatAstVariable, FormatAstSequence, FormatAstFunction, \ - FormatRuleSyntaxError, FormatAstList, UnexpectedEof, FormatAstColor, RulePredicate + FormatRuleSyntaxError, FormatAstList, UnexpectedEof, FormatAstColor, RulePredicate, FormatAstDict from core.tokenizer import Token, TokenKind from parsers.BaseNodeParser import SourceCodeWithConceptNode, SourceCodeNode from parsers.PythonParser import PythonNode @@ -102,8 +102,8 @@ class TestSheerkaRuleManager(TestUsingMemoryBasedSheerka): ('green("")', FormatAstColor("green", raw(""))), ("list(var_name, 2, 'children')", FormatAstList("var_name", recurse_on="children", recursion_depth=2)), ("list(var_name, recursion_depth=2, recurse_on='children')", FormatAstList("var_name", - recurse_on="children", - recursion_depth=2)), + recurse_on="children", + recursion_depth=2)), ("list(var_name, recursion_depth=2, 'children')", FormatAstList("var_name", recursion_depth=2)), ("list(var_name, 'children', recursion_depth=2)", FormatAstList("var_name", recursion_depth=2)), ("list(var_name)", FormatAstList("var_name")), @@ -112,6 +112,9 @@ class TestSheerkaRuleManager(TestUsingMemoryBasedSheerka): ("{variable:format}", FormatAstVariable("variable", "format")), ("{variable:3}", FormatAstVariable("variable", "3")), (r"\not_a_function(a={var})", seq([raw("not_a_function(a="), var("var"), raw(")")])), + ("dict(var_name)", FormatAstDict("var_name")), + ("dict(var_name, items_prop='props')", FormatAstDict("var_name", items_prop='props')), + ("dict(var_name, debug=True)", FormatAstDict("var_name", debug=True, prefix="{", suffix="}")) ]) def test_i_can_parse_format_rule(self, text, expected): assert FormatRuleParser(text).parse() == expected @@ -129,10 +132,11 @@ class TestSheerkaRuleManager(TestUsingMemoryBasedSheerka): ("red(xy {v})", FormatRuleSyntaxError("Invalid identifier", None)), ("list()", FormatRuleSyntaxError("variable name not found", None)), ("list(recursion_depth=2)", FormatRuleSyntaxError("variable name not found", None)), - ("list(a,b,c,d)", FormatRuleSyntaxError("too many positional arguments", - Token(TokenKind.IDENTIFIER, "d", 11, 1, 12))), + ("list(a,b,c,d,e)", FormatRuleSyntaxError("too many positional arguments", + Token(TokenKind.IDENTIFIER, "e", 13, 1, 14))), ("list(a, recursion_depth=hello)", FormatRuleSyntaxError("'hello' is not numeric", None)), ("list(a, recursion_depth='hello')", FormatRuleSyntaxError("'recursion_depth' must be an integer", None)), + ("dict()", FormatRuleSyntaxError("variable name not found", None)), ]) def test_i_cannot_parse_invalid_format(self, text, expected_error): parser = FormatRuleParser(text) diff --git a/tests/out/test_AsStrVisitor.py b/tests/out/test_AsStrVisitor.py new file mode 100644 index 0000000..8070674 --- /dev/null +++ b/tests/out/test_AsStrVisitor.py @@ -0,0 +1,168 @@ +import pytest +from core.sheerka.services.SheerkaRuleManager import FormatAstRawText, FormatAstVariable, FormatAstVariableNotFound, \ + FormatAstSequence, FormatAstList, FormatAstDict +from out.AsStrVisitor import AsStrVisitor + +from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka + + +class TestAsStrVisitor(TestUsingMemoryBasedSheerka): + + @pytest.mark.parametrize("format_text, expected", [ + (FormatAstRawText("hello word"), "hello word"), + (FormatAstVariable('xxx', value="hello word"), "hello word"), + (FormatAstVariable('xxx', value=1), "1"), + (FormatAstVariable('xxx', value=1.5), "1.5"), + (FormatAstVariable('xxx', value="hello word", debug=True), "'hello word'"), + (FormatAstVariable('xxx', value=1, debug=True), "1"), + (FormatAstVariable('xxx', value=1.5, debug=True), "1.5"), + (FormatAstVariable('xxx', value=False, debug=True), "False"), + (FormatAstVariableNotFound('va_name'), "\x1b[31mva_name\x1b[0m"), + ]) + def test_i_can_print_simple_ast(self, format_text, expected): + visitor = AsStrVisitor() + + res = visitor.visit(format_text) + assert res == expected + + def test_i_can_print_sequence(self, capsys): + visitor = AsStrVisitor() + sequence = FormatAstSequence([ + FormatAstRawText("hello word"), + FormatAstVariable('xxx', value=1), + FormatAstVariableNotFound('va_name'), + FormatAstVariable('xxx', value="hello word", debug=True) + ]) + + res = visitor.visit(sequence) + + assert res == "hello word1\x1b[31mva_name\x1b[0m'hello word'" + + def test_i_can_print_list(self, capsys): + visitor = AsStrVisitor() + lst = FormatAstList("xxx", items=[ + FormatAstVariable('__item', index=0, value="first element"), + FormatAstVariable('__item', index=1, value="second element", debug=True), + ]) + + res = visitor.visit(lst) + + assert res == "first element\n'second element'" + + def test_i_can_print_list_with_sub_items(self, capsys): + visitor = AsStrVisitor() + lst = FormatAstList("xxx", items=[ + FormatAstVariable('__item', index=0, value="first element"), + FormatAstList("__sub", items=[ + FormatAstVariable('__item', index=0, value="sub item 1"), + FormatAstVariable('__item', index=1, value="sub item 2"), + ]), + FormatAstVariable('__item', index=1, value="second element", debug=True), + ]) + + res = visitor.visit(lst) + + assert res == "first element\nsub item 1\nsub item 2\n'second element'" + + def test_i_can_print_list_in_debug_mode(self, capsys): + visitor = AsStrVisitor() + lst = FormatAstList("xxx", debug=True, prefix="[", suffix="]", items=[ + FormatAstVariable('__item', index=0, value="first element", debug=True), + FormatAstVariable('__item', index=1, value="second element", debug=True), + ]) + + res = visitor.visit(lst) + + assert res == "['first element', 'second element']" + + def test_i_can_print_expanded_list_in_debug_mode(self): + visitor = AsStrVisitor(expand=True) + lst = FormatAstList("xxx", debug=True, prefix="[", suffix="]", items=[ + FormatAstVariable('__item', index=0, value="first element", debug=True), + FormatAstVariable('__item', index=1, value="second element", debug=True), + ]) + + res = visitor.visit(lst) + + assert res == "['first element',\n'second element']" + + def test_i_can_print_list_with_index(self): + visitor = AsStrVisitor() + lst = FormatAstList("xxx", show_index=True, items=[ + FormatAstVariable('__item', index=0, value="first element"), + FormatAstVariable('__item', index=1, value="second element", debug=True), + ]) + + res = visitor.visit(lst) + + assert res == "[0] first element\n[1] 'second element'" + + def test_i_can_print_dict(self, capsys): + visitor = AsStrVisitor() + bag = FormatAstDict("xxx", items=[ + (FormatAstVariable('__key', index=0, value="key1"), + FormatAstVariable('__value', index="key1", value=1)), + (FormatAstVariable('__key', index=1, value="long_key2"), + FormatAstVariable('__value', index="key2", value="value2")), + ]) + + res = visitor.visit(bag) + + assert res == "key1 : 1\nlong_key2: value2" + + def test_i_can_print_dict_in_debug_mode(self, capsys): + visitor = AsStrVisitor() + bag = FormatAstDict("xxx", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="key1", debug=True), + FormatAstVariable('__value', index="key1", value=1, debug=True)), + (FormatAstVariable('__key', index=1, value="long_key2", debug=True), + FormatAstVariable('__value', index="key2", value="value2", debug=True)), + ]) + + res = visitor.visit(bag) + + assert res == "{'key1': 1, 'long_key2': 'value2'}" + + def test_i_can_print_expanded_dict_in_debug_mode(self): + visitor = AsStrVisitor(expand=True) + bag = FormatAstDict("xxx", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="key1", debug=True), + FormatAstVariable('__value', index="key1", value=1, debug=True)), + (FormatAstVariable('__key', index=1, value="long_key2", debug=True), + FormatAstVariable('__value', index="key2", value="value2", debug=True)), + ]) + + res = visitor.visit(bag) + + assert res == "{'key1' : 1,\n'long_key2': 'value2'}" + + def test_i_can_print_sub_level_of_dict_in_expand_mode(self): + visitor = AsStrVisitor(expand=True) + bag = FormatAstDict("xxx", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="key1", debug=True), + FormatAstVariable('__value', index="key1", value=1, debug=True)), + + (FormatAstVariable('__key', index=0, value="key2", debug=True), + FormatAstDict("__value", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="sub_key1", debug=True), + FormatAstVariable('__value', index="sub_key1", value=1, debug=True)), + (FormatAstVariable('__key', index=1, value="sub_long_key2", debug=True), + FormatAstDict("__value", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="sub_sub_key1", debug=True), + FormatAstVariable('__value', index="sub_sub_key1", value=1, debug=True)), + (FormatAstVariable('__key', index=1, value="sub_sub_key2", debug=True), + FormatAstVariable('__value', index="sub_sub_key2", value="sub_sub_value", debug=True)), + ])), + ])), + + (FormatAstVariable('__key', index=1, value="long_key3", debug=True), + FormatAstVariable('__value', index="key2", value="value2", debug=True)), + ]) + + res = visitor.visit(bag) + + assert res == """{'key1' : 1, +'key2' : {'sub_key1' : 1, + 'sub_long_key2': {'sub_sub_key1': 1, + 'sub_sub_key2': 'sub_sub_value'}}, +'long_key3': 'value2'}""" diff --git a/tests/out/test_ConsoleVisitor.py b/tests/out/test_ConsoleVisitor.py new file mode 100644 index 0000000..2da5cbb --- /dev/null +++ b/tests/out/test_ConsoleVisitor.py @@ -0,0 +1,143 @@ +import pytest +from core.sheerka.services.SheerkaRuleManager import FormatAstRawText, FormatAstVariable, FormatAstVariableNotFound, \ + FormatAstSequence, FormatAstList, FormatAstDict +from out.ConsoleVisistor import ConsoleVisitor + +from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka + + +class TestConsoleVisitor(TestUsingMemoryBasedSheerka): + + @pytest.mark.parametrize("format_text, expected", [ + (FormatAstRawText("hello word"), "hello word"), + (FormatAstVariable('xxx', value="hello word"), "hello word"), + (FormatAstVariable('xxx', value=1), "1"), + (FormatAstVariable('xxx', value=1.5), "1.5"), + (FormatAstVariable('xxx', value="hello word", debug=True), "'hello word'"), + (FormatAstVariable('xxx', value=1, debug=True), "1"), + (FormatAstVariable('xxx', value=1.5, debug=True), "1.5"), + (FormatAstVariable('xxx', value=False, debug=True), "False"), + (FormatAstVariableNotFound('va_name'), "\x1b[31mva_name\x1b[0m"), + ]) + def test_i_can_print_simple_ast(self, format_text, expected, capsys): + visitor = ConsoleVisitor() + + visitor.visit(format_text) + + captured = capsys.readouterr() + assert captured.out == expected + "\n" + + def test_i_can_print_sequence(self, capsys): + visitor = ConsoleVisitor() + sequence = FormatAstSequence([ + FormatAstRawText("hello word"), + FormatAstVariable('xxx', value=1), + FormatAstVariableNotFound('va_name'), + FormatAstVariable('xxx', value="hello word", debug=True) + ]) + + visitor.visit(sequence) + + captured = capsys.readouterr() + assert captured.out == "hello word1\x1b[31mva_name\x1b[0m'hello word'\n" + + def test_i_can_print_list(self, capsys): + visitor = ConsoleVisitor() + lst = FormatAstList("xxx", items=[ + FormatAstVariable('__item', index=0, value="first element"), + FormatAstVariable('__item', index=1, value="second element", debug=True), + ]) + + visitor.visit(lst) + + captured = capsys.readouterr() + assert captured.out == "first element\n'second element'\n" + + def test_i_can_print_list_with_sub_items(self, capsys): + visitor = ConsoleVisitor() + lst = FormatAstList("xxx", items=[ + FormatAstVariable('__item', index=0, value="first element"), + FormatAstList("__sub", items=[ + FormatAstVariable('__item', index=0, value="sub item 1"), + FormatAstVariable('__item', index=1, value="sub item 2"), + ]), + FormatAstVariable('__item', index=1, value="second element", debug=True), + ]) + + visitor.visit(lst) + + captured = capsys.readouterr() + assert captured.out == "first element\nsub item 1\nsub item 2\n'second element'\n" + + def test_i_can_print_list_in_debug_mode(self, capsys): + visitor = ConsoleVisitor() + lst = FormatAstList("xxx", debug=True, prefix="[", suffix="]", items=[ + FormatAstVariable('__item', index=0, value="first element", debug=True), + FormatAstVariable('__item', index=1, value="second element", debug=True), + ]) + + visitor.visit(lst) + + captured = capsys.readouterr() + assert captured.out == "['first element', 'second element']\n" + + def test_i_can_print_dict(self, capsys): + visitor = ConsoleVisitor() + bag = FormatAstDict("xxx", items=[ + (FormatAstVariable('__key', index=0, value="key1"), + FormatAstVariable('__value', index="key1", value=1)), + (FormatAstVariable('__key', index=1, value="long_key2"), + FormatAstVariable('__value', index="key2", value="value2")), + ]) + + visitor.visit(bag) + + captured = capsys.readouterr() + assert captured.out == "key1 : 1\nlong_key2: value2\n" + + def test_i_can_print_dict_in_debug_mode(self, capsys): + visitor = ConsoleVisitor() + bag = FormatAstDict("xxx", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="key1", debug=True), + FormatAstVariable('__value', index="key1", value=1, debug=True)), + (FormatAstVariable('__key', index=1, value="long_key2", debug=True), + FormatAstVariable('__value', index="key2", value="value2", debug=True)), + ]) + + visitor.visit(bag) + + captured = capsys.readouterr() + assert captured.out == "{'key1': 1, 'long_key2': 'value2'}\n" + + def test_i_can_print_sub_level_of_dict_in_expand_mode(self, capsys): + visitor = ConsoleVisitor(expand_mode='always') + bag = FormatAstDict("xxx", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="key1", debug=True), + FormatAstVariable('__value', index="key1", value=1, debug=True)), + + (FormatAstVariable('__key', index=0, value="key2", debug=True), + FormatAstDict("__value", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="sub_key1", debug=True), + FormatAstVariable('__value', index="sub_key1", value=1, debug=True)), + (FormatAstVariable('__key', index=1, value="sub_long_key2", debug=True), + FormatAstDict("__value", debug=True, prefix="{", suffix="}", items=[ + (FormatAstVariable('__key', index=0, value="sub_sub_key1", debug=True), + FormatAstVariable('__value', index="sub_sub_key1", value=1, debug=True)), + (FormatAstVariable('__key', index=1, value="sub_sub_key2", debug=True), + FormatAstVariable('__value', index="sub_sub_key2", value="sub_sub_value", debug=True)), + ])), + ])), + + (FormatAstVariable('__key', index=1, value="long_key3", debug=True), + FormatAstVariable('__value', index="key2", value="value2", debug=True)), + ]) + + visitor.visit(bag) + captured = capsys.readouterr() + + assert captured.out == """{'key1' : 1, +'key2' : {'sub_key1' : 1, + 'sub_long_key2': {'sub_sub_key1': 1, + 'sub_sub_key2': 'sub_sub_value'}}, +'long_key3': 'value2'} +""" diff --git a/tests/out/test_SheerkaOut.py b/tests/out/test_SheerkaOut.py index b2a08c4..c5ff3c8 100644 --- a/tests/out/test_SheerkaOut.py +++ b/tests/out/test_SheerkaOut.py @@ -1,10 +1,12 @@ +from dataclasses import dataclass + import pytest from core.builtin_concepts import ReturnValueConcept, BuiltinConcepts from core.concept import Concept from core.rule import Rule from core.sheerka.services.SheerkaOut import SheerkaOut from core.sheerka.services.SheerkaRuleManager import FormatAstRawText, FormatAstVariable, FormatAstSequence, \ - FormatAstColor, FormatAstVariableNotFound, FormatAstList + FormatAstColor, FormatAstVariableNotFound, FormatAstList, FormatAstDict from core.utils import flatten_all_children from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka @@ -14,6 +16,12 @@ raw = FormatAstRawText var = FormatAstVariable +@dataclass +class DummyObj: + prop_1: float + prop_2: str + + class TestSheerkaOut(TestUsingMemoryBasedSheerka): def init_service_with_rules(self, *rules, **kwargs): @@ -24,20 +32,20 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): @pytest.mark.parametrize("rule, expected", [ (("__ret", "hello world"), FormatAstRawText("hello world")), - (("__ret", "{__ret_value}"), FormatAstVariable("__ret_value", None, 1)), - (("__ret", "{status}"), FormatAstVariable("status", None, True)), + (("__ret", "{__ret_value}"), FormatAstVariable("__ret_value", value=1)), + (("__ret", "{status}"), FormatAstVariable("status", value=True)), (("__ret", "{foo}"), FormatAstVariableNotFound("foo")), (("__ret", "hello world {__ret_value} !"), seq([FormatAstRawText("hello world "), - FormatAstVariable("__ret_value", None, 1), + FormatAstVariable("__ret_value", value=1), FormatAstRawText(" !")])), - (("__ret", "red(__ret_value)"), FormatAstColor("red", FormatAstVariable("__ret_value", None, 1))), + (("__ret", "red(__ret_value)"), FormatAstColor("red", FormatAstVariable("__ret_value", value=1))), (("__ret", "blue('hello world {__ret_value} !')"), FormatAstColor("blue", seq([FormatAstRawText("hello world "), - FormatAstVariable("__ret_value", None, - 1), + FormatAstVariable("__ret_value", + value=1), FormatAstRawText(" !")]))), (("__ret", "list(foo)"), FormatAstVariableNotFound("foo")), - (("__ret", "{__ret_value:3}"), FormatAstVariable("__ret_value", "3", " 1")) + (("__ret", "{__ret_value:3}"), FormatAstVariable("__ret_value", "3", value=" 1")) ]) def test_i_can_develop_when_simple_rules(self, rule, expected): sheerka, context, service, rule = self.init_service_with_rules(rule) @@ -59,14 +67,14 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): res = service.create_out_tree(context, ret) assert res == seq([FormatAstRawText("status: "), - FormatAstVariable("status", None, True), + FormatAstVariable("status", value=True), FormatAstRawText(", value: "), - FormatAstVariable("__ret_value", None, seq([FormatAstVariable("id", None, "1001"), + FormatAstVariable("__ret_value", value=seq([FormatAstVariable("id", value="1001"), FormatAstRawText("-"), - FormatAstVariable("name", None, "foo"), + FormatAstVariable("name", value="foo"), FormatAstRawText(":"), - FormatAstVariable("body", None, - "hello world")]))]) + FormatAstVariable("body", + value="hello world")]))]) @pytest.mark.parametrize('second_rule', [ ("__ret.body", "{id}-{name}:{body}"), @@ -84,23 +92,32 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): res = service.create_out_tree(context, ret) assert res == seq([FormatAstRawText("status: "), - FormatAstVariable("status", None, True), + FormatAstVariable("status", value=True), FormatAstRawText(", value: "), - FormatAstVariable("__ret.body", None, seq([FormatAstVariable("id", None, "1001"), + FormatAstVariable("__ret.body", value=seq([FormatAstVariable("id", value="1001"), FormatAstRawText("-"), - FormatAstVariable("name", None, "foo"), + FormatAstVariable("name", value="foo"), FormatAstRawText(":"), - FormatAstVariable("body", None, - "hello world")]))]) + FormatAstVariable("body", + value="hello world")]))]) + + def test_i_can_develop_using_variable_properties(self): + sheerka, context, service, *rules = self.init_service_with_rules( + ("isinstance(__obj, Concept)", "{__obj.body}"), + ) + obj = Concept("bar", body="value for bar").auto_init() + + res = service.create_out_tree(context, obj) + assert res == FormatAstVariable("__obj.body", value="value for bar") def test_i_can_develop_list(self): sheerka, context, service, *rules = self.init_service_with_rules(("isinstance(__obj, list)", "list(__obj)")) lst = list(flatten_all_children(context, lambda item: item.achildren))[:3] res = service.create_out_tree(context, lst) - assert res == FormatAstList(variable="__obj", items=[FormatAstVariable("__item", None, lst[0], 0), - FormatAstVariable("__item", None, lst[1], 1), - FormatAstVariable("__item", None, lst[2], 2)]) + assert res == FormatAstList(variable="__obj", items=[FormatAstVariable("__item", value=lst[0], index=0), + FormatAstVariable("__item", value=lst[1], index=1), + FormatAstVariable("__item", value=lst[2], index=2)]) def test_i_can_develop_list_using_the_default_items_prop(self): sheerka, context, service, *rules = self.init_service_with_rules(("isinstance(__obj, 'foo')", "list(__obj)")) @@ -108,9 +125,9 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): foo = Concept("foo", body=lst, key="foo").auto_init() res = service.create_out_tree(context, foo) - assert res == FormatAstList(variable="__obj", items=[FormatAstVariable("__item", None, lst[0], 0), - FormatAstVariable("__item", None, lst[1], 1), - FormatAstVariable("__item", None, lst[2], 2)]) + assert res == FormatAstList(variable="__obj", items=[FormatAstVariable("__item", value=lst[0], index=0), + FormatAstVariable("__item", value=lst[1], index=1), + FormatAstVariable("__item", value=lst[2], index=2)]) def test_i_can_develop_list_using_the_custom_items_prop(self): sheerka, context, service, *rules = self.init_service_with_rules( @@ -121,16 +138,16 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): res = service.create_out_tree(context, foo) assert res == FormatAstList(variable="__obj", items_prop='custom', - items=[FormatAstVariable("__item", None, lst[0], 0), - FormatAstVariable("__item", None, lst[1], 1), - FormatAstVariable("__item", None, lst[2], 2)]) + items=[FormatAstVariable("__item", value=lst[0], index=0), + FormatAstVariable("__item", value=lst[1], index=1), + FormatAstVariable("__item", value=lst[2], index=2)]) def test_not_a_list_are_resolved_into_variable_ast(self): sheerka, context, service, *rules = self.init_service_with_rules(("isinstance(__obj, 'foo')", "list(__obj)")) foo = Concept("foo", key="foo") res = service.create_out_tree(context, foo) - assert res == FormatAstVariable("__obj", None, foo) + assert res == FormatAstVariable("__obj", value=foo) def test_i_can_develop_list_of_return_values(self): sheerka, context, service, *rules = self.init_service_with_rules(("__rets", "list(__rets)")) @@ -140,9 +157,9 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): ] res = service.create_out_tree(context, lst) - assert res == FormatAstList(variable="__rets", items=[FormatAstVariable("__item", None, lst[0], 0), - FormatAstVariable("__item", None, lst[1], 1), - FormatAstVariable("__item", None, lst[2], 2)]) + assert res == FormatAstList(variable="__rets", items=[FormatAstVariable("__item", value=lst[0], index=0), + FormatAstVariable("__item", value=lst[1], index=1), + FormatAstVariable("__item", value=lst[2], index=2)]) def test_rules_are_correctly_reset_when_list(self): sheerka, context, service, *rules = self.init_service_with_rules( @@ -156,8 +173,8 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): res = service.create_out_tree(context, lst) assert res == FormatAstList(variable="__rets", items=[ - FormatAstVariable("__item", None, FormatAstColor("red", FormatAstVariable("__ret", None, lst[0])), 0), - FormatAstVariable("__item", None, FormatAstColor("red", FormatAstVariable("__ret", None, lst[1])), 1), + FormatAstVariable("__item", value=FormatAstColor("red", FormatAstVariable("__ret", value=lst[0])), index=0), + FormatAstVariable("__item", value=FormatAstColor("red", FormatAstVariable("__ret", value=lst[1])), index=1), ]) def test_i_can_develop_list_with_recurse(self): @@ -174,15 +191,15 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): res = service.create_out_tree(context, lst) assert res == FormatAstList(variable="__rets", recurse_on='parents', recursion_depth=2, items=[ - FormatAstVariable("__item", None, r1, 0), + FormatAstVariable("__item", value=r1, index=0), FormatAstList(variable="__parents", recurse_on='parents', recursion_depth=1, items=[ - FormatAstVariable("__item", None, r11, 0), + FormatAstVariable("__item", value=r11, index=0), FormatAstList(variable="__parents", recurse_on='parents', recursion_depth=0, items=[ - FormatAstVariable("__item", None, r111, 0)]) + FormatAstVariable("__item", value=r111, index=0)]) ]), - FormatAstVariable("__item", None, r2, 1), + FormatAstVariable("__item", value=r2, index=1), FormatAstList(variable="__parents", recurse_on='parents', recursion_depth=1, items=[ - FormatAstVariable("__item", None, r22, 0)]), + FormatAstVariable("__item", value=r22, index=0)]), ]) def test_i_can_develop_list_with_recurse_using_container_format_instr(self): @@ -200,29 +217,53 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): res = service.create_out_tree(context, foo) assert res == FormatAstList(variable="__obj", recurse_on='parents', recursion_depth=2, items=[ - FormatAstVariable("__item", None, r1, 0), + FormatAstVariable("__item", value=r1, index=0), FormatAstList(variable="__parents", recurse_on='parents', recursion_depth=1, items=[ - FormatAstVariable("__item", None, r11, 0), + FormatAstVariable("__item", value=r11, index=0), FormatAstList(variable="__parents", recurse_on='parents', recursion_depth=0, items=[ - FormatAstVariable("__item", None, r111, 0)]) + FormatAstVariable("__item", value=r111, index=0)]) ]), - FormatAstVariable("__item", None, r2, 1), + FormatAstVariable("__item", value=r2, index=1), FormatAstList(variable="__parents", recurse_on='parents', recursion_depth=1, items=[ - FormatAstVariable("__item", None, r22, 0)]), + FormatAstVariable("__item", value=r22, index=0)]), ]) - def test_i_can_develop_using_variable_properties(self): - sheerka, context, service, *rules = self.init_service_with_rules( - ("isinstance(__obj, Concept)", "{__obj.body}"), - ) - lst = Concept("bar", body="value for bar").auto_init() + def test_i_can_develop_dict(self): + sheerka, context, service, *rules = self.init_service_with_rules(("isinstance(__obj, dict)", "dict(__obj)")) + obj = { + "key1": "value1", + "key2": "value2", + } - res = service.create_out_tree(context, lst) - assert res == FormatAstVariable("__obj.body", None, "value for bar") + res = service.create_out_tree(context, obj) + assert res == FormatAstDict(variable="__obj", items=[ + (FormatAstVariable("__key", value="key1", index=0), + FormatAstVariable("__value", value="value1", index="key1")), + (FormatAstVariable("__key", value="key2", index=1), + FormatAstVariable("__value", value="value2", index="key2")), + ]) + + def test_i_can_develop_dict_with_list(self): + sheerka, context, service, *rules = self.init_service_with_rules(("isinstance(__obj, dict)", "dict(__obj)")) + obj = { + "key1": "value1", + "key2": ["item1", "item2"], + } + + res = service.create_out_tree(context, obj) + assert res == FormatAstDict(variable="__obj", items=[ + (FormatAstVariable("__key", value="key1", index=0), + FormatAstVariable("__value", value="value1", index="key1")), + (FormatAstVariable("__key", value="key2", index=1), + FormatAstList(variable='__value', debug=True, prefix='[', suffix=']', items=[ + FormatAstVariable("__item", debug=True, value="item1", index=0), + FormatAstVariable("__item", debug=True, value="item2", index=1), + ])), + ]) @pytest.mark.parametrize("rule, expected", [ (("__ret", "red('hello world')"), FormatAstColor("red", FormatAstRawText("hello world"))), - (("__ret", "blue(__ret_value)"), FormatAstColor("blue", FormatAstVariable("__ret_value", None, 1))), + (("__ret", "blue(__ret_value)"), FormatAstColor("blue", FormatAstVariable("__ret_value", value=1))), ]) def test_i_can_develop_color(self, rule, expected): sheerka, context, service, rule = self.init_service_with_rules(rule) @@ -233,12 +274,10 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): assert res == expected @pytest.mark.parametrize("rule, expected", [ - (("__ret", "{__ret}"), FormatAstVariable("__ret", None, - ReturnValueConcept(who="Test", status=True, value=1))), + (("__ret", "{__ret}"), FormatAstVariable("__ret", value=ReturnValueConcept(who="Test", status=True, value=1))), (("__ret", "magenta(__ret)"), FormatAstColor("magenta", - FormatAstVariable("__ret", None, - ReturnValueConcept(who="Test", status=True, value=1)))), + FormatAstVariable("__ret", value=ReturnValueConcept(who="Test", status=True, value=1)))), ]) def test_i_can_manage_infinite_recursion(self, rule, expected): sheerka, context, service, rule = self.init_service_with_rules(rule) @@ -258,8 +297,8 @@ class TestSheerkaOut(TestUsingMemoryBasedSheerka): res = service.create_out_tree(context, ret) assert res == FormatAstColor("white", - FormatAstVariable("__ret", None, - FormatAstVariable("__ret.value", None, "hello world!"))) + FormatAstVariable("__ret", + value=FormatAstVariable("__ret.value", value="hello world!"))) def test_i_can_print_out_the_result(self, capsys): sheerka, context, service, *rules = self.init_service_with_rules( @@ -379,3 +418,34 @@ ReturnValue(who=Test, status=True, value=r2, message=None) service.process_return_values(context, ret) captured = capsys.readouterr() assert captured.out == "status: \x1b[32mTrue\x1b[0m, \x1b[34mvalue: (1001)foo\x1b[0m\n" + + def test_i_can_print_out_dict(self, capsys): + sheerka, context, service, *rules = self.init_service_with_rules( + ("isinstance(__obj, dict)", "dict(__obj)"), + ("__key=='key1'", "green(__key)") + ) + obj = { + "key1": "value1", + "key2": "value2", + } + service.process_return_values(context, obj) + captured = capsys.readouterr() + assert captured.out == """\x1b[32mkey1\x1b[0m: value1 +key2: value2 +""" + + def test_i_can_print_out_dict_in_debug_mode(self, capsys): + sheerka, context, service, *rules = self.init_service_with_rules( + ("isinstance(__obj, dict)", "dict(__obj, debug=True)"), + ("__key=='key1'", "green(__key)") + ) + obj = { + "key1": "value1", + "key2": 1, + "key3": DummyObj(3.15, "a string"), + "key4": ["alpha", 0] + } + service.process_return_values(context, obj) + captured = capsys.readouterr() + assert captured.out == """{'\x1b[32mkey1\x1b[0m': 'value1', 'key2': 1, 'key3': DummyObj(prop_1=3.15, prop_2='a string'), 'key4': ['alpha', 0]} +"""