Fixed #61 : SheerkaDebugManager: Add get_value()

Fixed #60 : Hash error when ReturnValue is a list of list
Fixed #59 : Implement smart_get()
Fixed #58 : SheerkaPromptCompleter: Cannot parse concept token
Fixed #57 : SheerkaPrompt: Add concept autocompletion
Fixed #56 : automatically backup command
Fixed #54 : I can record execution status
Fixed #53 : ConceptManager: modify_concept fails
This commit is contained in:
2021-04-09 15:47:32 +02:00
parent 6cda2686fb
commit dd3d8f4abe
37 changed files with 1055 additions and 191 deletions
+2 -1
View File
@@ -12,4 +12,5 @@ tests/_concepts.txt
tests/**/*result_test tests/**/*result_test
testingPython.ipynb testingPython.ipynb
profile*.txt profile*.txt
*.prof *.prof
new.sb
@@ -4,6 +4,13 @@ def concept q from q ? as question(q) pre is_question()
set_is_lesser(__PRECEDENCE, q, 'Sya') set_is_lesser(__PRECEDENCE, q, 'Sya')
set_auto_eval(c:q:) set_auto_eval(c:q:)
def concept the x ret memory(x)
def concept a x where 'x is a concept' ret x
def concept an x where 'x is a concept' ret x
set_is_greatest(__PRECEDENCE, c:the x:, 'Sya')
set_is_greatest(__PRECEDENCE, c:a x:, 'Sya')
set_is_greatest(__PRECEDENCE, c:an x:, 'Sya')
def concept "x is a concept" as isinstance(x, Concept) pre is_question() def concept "x is a concept" as isinstance(x, Concept) pre is_question()
# is a # is a
@@ -38,9 +45,7 @@ set_is_lesser(__PRECEDENCE, c:x or y:, 'Sya')
set_is_greater_than(__PRECEDENCE, c:x and y:, c:x or y:, 'Sya') set_is_greater_than(__PRECEDENCE, c:x and y:, c:x or y:, 'Sya')
set_is_less_than(__PRECEDENCE, c:q:, c:x or y:, 'Sya') set_is_less_than(__PRECEDENCE, c:q:, c:x or y:, 'Sya')
def concept the x ret memory(x)
def concept a x where 'x is a concept' ret x
def concept an x where 'x is a concept' ret x
# default # default
def concept male def concept male
@@ -67,4 +72,6 @@ def concept friday
def concept saturday def concept saturday
def concept sunday def concept sunday
# questions
def concept what x is y pre is_question() where isa(x, adjective) as smart_get_attr(y, x)
def concept how is x pre is_question() as smart_get_attr(x, adjective)
-1
View File
@@ -112,7 +112,6 @@ class CacheManager:
old_key = cache_def.get_key(old) old_key = cache_def.get_key(old)
new_key = cache_def.get_key(new) new_key = cache_def.get_key(new)
cache_def.cache.update(old_key, old, new_key, new, alt_sdp=alt_sdp) cache_def.cache.update(old_key, old, new_key, new, alt_sdp=alt_sdp)
self.is_dirty = True self.is_dirty = True
+2 -6
View File
@@ -1,6 +1,7 @@
from core.builtin_concepts_ids import BuiltinConcepts from core.builtin_concepts_ids import BuiltinConcepts
from core.concept import Concept, ConceptParts from core.concept import Concept, ConceptParts
from core.global_symbols import ErrorObj from core.global_symbols import ErrorObj
from core.utils import compute_hash
class UserInputConcept(Concept): class UserInputConcept(Concept):
@@ -91,12 +92,7 @@ class ReturnValueConcept(Concept):
self.value == other.value self.value == other.value
def __hash__(self): def __hash__(self):
if hasattr(self.value, "__iter__") and not isinstance(self.value, str): return hash((self.who, self.status, compute_hash(self.value)))
value_hash = hash(tuple(self.value))
else:
value_hash = hash(self.value)
return hash((self.who, self.status, value_hash))
class UnknownPropertyConcept(Concept, ErrorObj): class UnknownPropertyConcept(Concept, ErrorObj):
+6 -5
View File
@@ -2,6 +2,7 @@ import hashlib
from copy import deepcopy from copy import deepcopy
from dataclasses import dataclass from dataclasses import dataclass
from threading import RLock from threading import RLock
from typing import List
import core.utils import core.utils
from core.builtin_concepts_ids import BuiltinDynamicAttrs from core.builtin_concepts_ids import BuiltinDynamicAttrs
@@ -59,6 +60,7 @@ class ConceptMetadata:
is_evaluated: bool = False # True is the concept is evaluated by sheerka.eval_concept() is_evaluated: bool = False # True is the concept is evaluated by sheerka.eval_concept()
need_validation = False # True if the properties of the concept need to be validated need_validation = False # True if the properties of the concept need to be validated
full_serialization: bool = False # If True, the full object will be serialized, rather than just the diff full_serialization: bool = False # If True, the full object will be serialized, rather than just the diff
all_attributes: List[str] = None # list of instance attributes
ALL_ATTRIBUTES = {} ALL_ATTRIBUTES = {}
@@ -157,7 +159,6 @@ class Concept:
self._original_definition_hash = None # concept hash before any alteration of the metadata self._original_definition_hash = None # concept hash before any alteration of the metadata
self._format = None # how to print the concept self._format = None # how to print the concept
self._hints = {} # extra processing information to help processing self._hints = {} # extra processing information to help processing
self._all_attributes = None # instance attributes
def __repr__(self): def __repr__(self):
text = f"({self._metadata.id}){self._metadata.name}" text = f"({self._metadata.id}){self._metadata.name}"
@@ -209,7 +210,7 @@ class Concept:
return hash(self._metadata.name) return hash(self._metadata.name)
def get_all_attributes(self): def get_all_attributes(self):
return self._all_attributes return self._metadata.all_attributes
def def_var(self, var_name, default_value=None): def def_var(self, var_name, default_value=None):
""" """
@@ -465,9 +466,9 @@ class Concept:
# I am not sure how cost efficient it is to check for new attribute everytime # I am not sure how cost efficient it is to check for new attribute everytime
# Need to find a better way # Need to find a better way
if name not in get_concept_attrs(self): if name not in get_concept_attrs(self):
if self._all_attributes is None: if self._metadata.all_attributes is None:
self._all_attributes = get_concept_attrs(self).copy() self._metadata.all_attributes = get_concept_attrs(self).copy()
self._all_attributes.append(name) self._metadata.all_attributes.append(name)
except AttributeError: except AttributeError:
print(f"Cannot set {name}") print(f"Cannot set {name}")
+5
View File
@@ -6,6 +6,7 @@ EVENT_RULE_PRECEDENCE_MODIFIED = "evt_rp_m"
EVENT_CONTEXT_DISPOSED = "evt_ctx_d" EVENT_CONTEXT_DISPOSED = "evt_ctx_d"
EVENT_USER_INPUT_EVALUATED = "evt_ui_e" EVENT_USER_INPUT_EVALUATED = "evt_ui_e"
EVENT_CONCEPT_CREATED = "evt_c_c" EVENT_CONCEPT_CREATED = "evt_c_c"
EVENT_CONCEPT_MODIFIED = "evt_c_m"
EVENT_CONCEPT_DELETED = "evt_c_d" EVENT_CONCEPT_DELETED = "evt_c_d"
EVENT_CONCEPT_ID_DELETED = "evt_c_id_d" EVENT_CONCEPT_ID_DELETED = "evt_c_id_d"
EVENT_RULE_CREATED = "evt_r_c" EVENT_RULE_CREATED = "evt_r_c"
@@ -79,3 +80,7 @@ class SyaAssociativity(Enum):
def __repr__(self): def __repr__(self):
return self.value return self.value
SHEERKA_BACKUP_FOLDER = "SHEERKA_BACKUP_FOLDER"
SHEERKA_BACKUP_FILE = "SHEERKA_BACKUP_FILE"
+14
View File
@@ -497,3 +497,17 @@ class ExecutionContext:
return False return False
return False return False
def set_state_is_modified(self):
root_parent = self
while root_parent._parent is not None:
root_parent = root_parent._parent
root_parent.values["is_state_modified"] = True
def is_state_modified(self):
root_parent = self
while root_parent._parent is not None:
root_parent = root_parent._parent
return "is_state_modified" in root_parent.values and bool(root_parent.values["is_state_modified"]) is True
+3 -3
View File
@@ -106,6 +106,7 @@ class Sheerka(Concept):
self.save_execution_context = True self.save_execution_context = True
self.enable_process_return_values = True self.enable_process_return_values = True
self.enable_process_rules = True self.enable_process_rules = True
self.enable_commands_backup = True
self.methods_with_context = {"test_using_context"} # only the names, the method is defined in sheerka_methods self.methods_with_context = {"test_using_context"} # only the names, the method is defined in sheerka_methods
self.sheerka_methods = { self.sheerka_methods = {
@@ -158,8 +159,6 @@ class Sheerka(Concept):
Loads the current configuration Loads the current configuration
Notes that when it's the first time, it also create the needed working folders Notes that when it's the first time, it also create the needed working folders
:param root_folder: root configuration folder :param root_folder: root configuration folder
:param save_execution_context:
:param enable_process_return_values:
:return: ReturnValue(Success or Error) :return: ReturnValue(Success or Error)
""" """
@@ -167,6 +166,7 @@ class Sheerka(Concept):
self.enable_process_return_values = kwargs.get("enable_process_return_values", self.enable_process_return_values = kwargs.get("enable_process_return_values",
self.enable_process_return_values) self.enable_process_return_values)
self.enable_process_rules = kwargs.get("enable_process_rules", self.enable_process_rules) self.enable_process_rules = kwargs.get("enable_process_rules", self.enable_process_rules)
self.enable_commands_backup = kwargs.get("enable_commands_backup", self.enable_commands_backup)
try: try:
self.during_initialisation = True self.during_initialisation = True
@@ -370,7 +370,7 @@ class Sheerka(Concept):
if self.enable_process_rules: if self.enable_process_rules:
ret = self.execute_rules(execution_context, ret, RULES_EVALUATE_STEPS, RULES_EXECUTE_STEPS) ret = self.execute_rules(execution_context, ret, RULES_EVALUATE_STEPS, RULES_EXECUTE_STEPS)
if self.om.is_dirty: if self.om.is_dirty():
self.om.commit(execution_context) self.om.commit(execution_context)
self.publish(execution_context, EVENT_USER_INPUT_EVALUATED) self.publish(execution_context, EVENT_USER_INPUT_EVALUATED)
+13 -9
View File
@@ -1,16 +1,16 @@
import sys import os
import time import time
from os import path from os import path
from core.builtin_concepts_ids import BuiltinConcepts, BuiltinContainers from core.builtin_concepts_ids import BuiltinConcepts, BuiltinContainers
from core.builtin_helpers import ensure_concept_or_rule from core.builtin_helpers import ensure_concept_or_rule
from core.concept import Concept from core.concept import Concept
from core.global_symbols import SHEERKA_BACKUP_FOLDER
from core.sheerka.services.SheerkaHistoryManager import SheerkaHistoryManager from core.sheerka.services.SheerkaHistoryManager import SheerkaHistoryManager
from core.sheerka.services.SheerkaMemory import SheerkaMemory from core.sheerka.services.SheerkaMemory import SheerkaMemory
from core.sheerka.services.sheerka_service import BaseService from core.sheerka.services.sheerka_service import BaseService
CONCEPTS_FILE_LITE = "_concepts_lite.txt" CONCEPTS_FILE_FULL = "full.sb"
CONCEPTS_FILE_FULL = "_concepts_full.txt"
CONCEPTS_FILE_TO_USE = CONCEPTS_FILE_FULL CONCEPTS_FILE_TO_USE = CONCEPTS_FILE_FULL
@@ -70,7 +70,7 @@ class SheerkaAdmin(BaseService):
return self.sheerka.om.current_sdp() return self.sheerka.om.current_sdp()
def restore(self, concept_file=CONCEPTS_FILE_TO_USE): def restore(self, backup_file=CONCEPTS_FILE_TO_USE):
""" """
Restore the state with all previous valid concept definitions Restore the state with all previous valid concept definitions
:return: :return:
@@ -79,7 +79,7 @@ class SheerkaAdmin(BaseService):
def restore_from_file(file_name): def restore_from_file(file_name):
_nb_lines, _nb_instructions, _nb_lines_in_error = 0, 0, 0 _nb_lines, _nb_instructions, _nb_lines_in_error = 0, 0, 0
_min_time, _max_time = None, None _min_time, _max_time = None, None
file_path = path.join(path.dirname(sys.argv[0]), file_name) file_path = path.join(backup_folder, file_name)
if not path.exists(file_path): if not path.exists(file_path):
print(f"\u001b[31mFile '{file_path}' is not found !\u001b[0m") print(f"\u001b[31mFile '{file_path}' is not found !\u001b[0m")
return 0, 0, 1 return 0, 0, 1
@@ -90,7 +90,7 @@ class SheerkaAdmin(BaseService):
line = line.strip() line = line.strip()
if line.startswith("#import "): if line.startswith("#import "):
to_import = "_concepts_" + line[8:] + ".txt" to_import = line[8:] + ".sb"
print(f" ==== Importing {to_import} ==== ") print(f" ==== Importing {to_import} ==== ")
res = restore_from_file(to_import) res = restore_from_file(to_import)
_nb_lines += res[0] _nb_lines += res[0]
@@ -122,8 +122,12 @@ class SheerkaAdmin(BaseService):
return _nb_lines, _nb_instructions, _nb_lines_in_error, _min_time, _max_time return _nb_lines, _nb_instructions, _nb_lines_in_error, _min_time, _max_time
if not concept_file.startswith("_concepts"): backup_folder = os.getenv(SHEERKA_BACKUP_FOLDER)
concept_file = f"_concepts_{concept_file}.txt" if backup_folder is None:
return self.sheerka.ret(self.NAME, False, self.sheerka.err(SHEERKA_BACKUP_FOLDER + " is not defined"))
if not backup_file.endswith(".sb"):
backup_file = backup_file + ".sb"
try: try:
start = time.time_ns() start = time.time_ns()
@@ -132,7 +136,7 @@ class SheerkaAdmin(BaseService):
enable_process_return_values_previous_value = self.sheerka.enable_process_return_values enable_process_return_values_previous_value = self.sheerka.enable_process_return_values
self.sheerka.enable_process_return_values = False self.sheerka.enable_process_return_values = False
nb_lines, nb_instructions, nb_lines_in_error, min_time, max_time = restore_from_file(concept_file) nb_lines, nb_instructions, nb_lines_in_error, min_time, max_time = restore_from_file(backup_file)
self.sheerka.enable_process_return_values = enable_process_return_values_previous_value self.sheerka.enable_process_return_values = enable_process_return_values_previous_value
self.sheerka.save_execution_context = True self.sheerka.save_execution_context = True
@@ -13,7 +13,8 @@ from core.builtin_concepts_ids import BuiltinConcepts, AllBuiltinConcepts, Built
from core.builtin_helpers import ensure_concept, ensure_bnf from core.builtin_helpers import ensure_concept, ensure_bnf
from core.concept import Concept, DEFINITION_TYPE_DEF, DEFINITION_TYPE_BNF, freeze_concept_attrs, ConceptMetadata, \ from core.concept import Concept, DEFINITION_TYPE_DEF, DEFINITION_TYPE_BNF, freeze_concept_attrs, ConceptMetadata, \
VARIABLE_PREFIX VARIABLE_PREFIX
from core.global_symbols import EVENT_CONCEPT_CREATED, NotInit, NotFound, ErrorObj, EVENT_CONCEPT_DELETED, NoFirstToken from core.global_symbols import EVENT_CONCEPT_CREATED, NotInit, NotFound, ErrorObj, EVENT_CONCEPT_DELETED, NoFirstToken, \
EVENT_CONCEPT_MODIFIED
from core.sheerka.services.sheerka_service import BaseService from core.sheerka.services.sheerka_service import BaseService
from core.tokenizer import Tokenizer, TokenKind from core.tokenizer import Tokenizer, TokenKind
from parsers.BnfNodeParser import RegExDef from parsers.BnfNodeParser import RegExDef
@@ -118,6 +119,7 @@ class SheerkaConceptManager(BaseService):
self.sheerka.bind_service_method(self.set_id_if_needed, True) self.sheerka.bind_service_method(self.set_id_if_needed, True)
self.sheerka.bind_service_method(self.set_attr, True) self.sheerka.bind_service_method(self.set_attr, True)
self.sheerka.bind_service_method(self.get_attr, False) self.sheerka.bind_service_method(self.get_attr, False)
self.sheerka.bind_service_method(self.smart_get_attr, False)
self.sheerka.bind_service_method(self.set_property, True, as_name="set_prop") self.sheerka.bind_service_method(self.set_property, True, as_name="set_prop")
self.sheerka.bind_service_method(self.get_property, False, as_name="get_prop") self.sheerka.bind_service_method(self.get_property, False, as_name="get_prop")
self.sheerka.bind_service_method(self.get_by_key, False, visible=False) self.sheerka.bind_service_method(self.get_by_key, False, visible=False)
@@ -346,7 +348,7 @@ class SheerkaConceptManager(BaseService):
if modify_source: if modify_source:
self._update_concept(context, concept, to_add, to_remove) self._update_concept(context, concept, to_add, to_remove)
# KSI 2021-02-16 publish the modification of the concept only when someone needs it sheerka.publish(context, EVENT_CONCEPT_MODIFIED, {"old": concept, "new": new_concept})
return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=new_concept)) return sheerka.ret(self.NAME, True, sheerka.new(BuiltinConcepts.NEW_CONCEPT, body=new_concept))
def remove_concept(self, context, concept): def remove_concept(self, context, concept):
@@ -390,7 +392,6 @@ class SheerkaConceptManager(BaseService):
def set_attr(self, concept, attribute, value): def set_attr(self, concept, attribute, value):
""" """
Modifies an attribute of a concept (concept.values) Modifies an attribute of a concept (concept.values)
:param context:
:param concept: :param concept:
:param attribute: :param attribute:
:param value: :param value:
@@ -416,7 +417,6 @@ class SheerkaConceptManager(BaseService):
def get_attr(self, concept, attribute): def get_attr(self, concept, attribute):
""" """
Returns the attribute of a concept Returns the attribute of a concept
:param context:
:param concept: :param concept:
:param attribute: :param attribute:
:return: :return:
@@ -431,6 +431,72 @@ class SheerkaConceptManager(BaseService):
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"#concept": concept, "#attr": attribute}) return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"#concept": concept, "#attr": attribute})
return value return value
def smart_get_attr(self, concept, attribute):
def get_obj_value(c, concept_to_look_for):
"""
Return the body of the concept c if it is an instance of concept_to_look_for
Go deeper in the bodies until the concept_to_look_for is found
:param c:
:param concept_to_look_for:
:return:
"""
while c.body is not NotInit:
if self.sheerka.isinstance(c.body, concept_to_look_for):
return c.body
c = c.body
return None
ensure_concept()
if not self.sheerka.is_success(concept):
return concept
value = self.get_attr(concept, attribute)
if not self.sheerka.isinstance(value, BuiltinConcepts.NOT_FOUND):
return value
if not isinstance(attribute, Concept):
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"#concept": concept, "#attr": attribute})
# try to be smart
result = []
# first look in children
for k, v in concept.variables().items():
attr_as_concept_key, attr_concept_id = core.utils.unstr_concept(k)
if attr_concept_id is not None:
attr_as_concept = self.sheerka.fast_resolve((attr_as_concept_key, attr_concept_id), return_new=False)
if attr_as_concept is not None and self.sheerka.isa(attribute, attr_as_concept):
if hasattr(v, "__iter__"):
for _v in v:
value = get_obj_value(_v, attribute)
if value is not None:
result.append(value)
else:
value = get_obj_value(v, attribute)
if value is not None:
result.append(value)
if len(result) > 0:
return result[0] if len(result) == 1 else result
# then try the ancestors
for k, v in concept.variables().items():
attr_as_concept_key, attr_concept_id = core.utils.unstr_concept(k)
if attr_concept_id is not None:
attr_as_concept = self.sheerka.fast_resolve((attr_as_concept_key, attr_concept_id), return_new=False)
if attr_as_concept is not None and self.sheerka.isa(attr_as_concept, attribute):
if isinstance(v, list):
result.extend(v)
else:
result.append(v)
if len(result) > 0:
return result[0] if len(result) == 1 else result
return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"#concept": concept, "#attr": attribute})
def get_property(self, concept, prop): def get_property(self, concept, prop):
""" """
Returns the value of a concept property Returns the value of a concept property
@@ -322,6 +322,7 @@ class SheerkaDebugManager(BaseService):
def initialize(self): def initialize(self):
self.sheerka.bind_service_method(self.set_debug, True) self.sheerka.bind_service_method(self.set_debug, True)
self.sheerka.bind_service_method(self.inspect, False) self.sheerka.bind_service_method(self.inspect, False)
self.sheerka.bind_service_method(self.get_value, False)
self.sheerka.bind_service_method(self.get_debugger, False) self.sheerka.bind_service_method(self.get_debugger, False)
self.sheerka.bind_service_method(self.reset_debug, False) self.sheerka.bind_service_method(self.reset_debug, False)
self.sheerka.bind_service_method(self.set_debug_var, True) self.sheerka.bind_service_method(self.set_debug_var, True)
@@ -802,6 +803,26 @@ class SheerkaDebugManager(BaseService):
return self.get_inner_values(obj.body, **kwargs) return self.get_inner_values(obj.body, **kwargs)
def get_value(self, obj):
"""
Returns the underlying values of an object
:param obj:
:return:
"""
if isinstance(obj, list):
return [self.get_value(item) for item in obj]
if isinstance(obj, set):
return {self.get_value(item) for item in obj}
if isinstance(obj, tuple):
return tuple(self.get_value(item) for item in obj)
if isinstance(obj, Concept):
return self.get_value(self.sheerka.objvalue(obj))
return obj
@staticmethod @staticmethod
def get_debug_repr(obj, **kwargs): def get_debug_repr(obj, **kwargs):
if kwargs.get("as_bag", False): if kwargs.get("as_bag", False):
@@ -897,3 +918,4 @@ class SheerkaDebugManager(BaseService):
del res["self"] del res["self"]
return res return res
@@ -31,9 +31,9 @@ class SheerkaHasAManager(BaseService):
return self.sheerka.ret( return self.sheerka.ret(
self.NAME, self.NAME,
False, False,
self.sheerka.new(BuiltinConcepts.PropertyAlreadyDefined, self.sheerka.new(BuiltinConcepts.PROPERTY_ALREADY_DEFINED,
body=concept_b, property_value=concept_b,
name=BuiltinConcepts.HASA, property_name=BuiltinConcepts.HASA,
concept=concept_a)) concept=concept_a))
merged_concepts = merge_sets(concept_a.get_prop(BuiltinConcepts.HASA), {concept_b}) merged_concepts = merge_sets(concept_a.get_prop(BuiltinConcepts.HASA), {concept_b})
@@ -1,5 +1,6 @@
from collections import namedtuple from collections import namedtuple
from core.global_symbols import NotInit
from core.sheerka.services.sheerka_service import BaseService from core.sheerka.services.sheerka_service import BaseService
from sdp.sheerkaDataProvider import Event from sdp.sheerkaDataProvider import Event
@@ -7,14 +8,22 @@ hist = namedtuple("HistoryTest", "text status") # tests purposes only
class History: class History:
def __init__(self, event: Event, result): def __init__(self, event: Event, result, extra_info):
self.event = event self.event = event
self.result = result self.result = result
self._status = None self.extra_info = extra_info
self._status = NotInit
self._is_state_modified = NotInit
self._format_instructions = None self._format_instructions = None
def __str__(self): def __str__(self):
msg = f"{self.event.get_digest()} {self.event.date.strftime('%d/%m/%Y %H:%M:%S')} : {self.event.message}" if self.is_state_modified is NotInit:
state_modified_str = "[?]"
else:
state_modified_str = "[X]" if self.is_state_modified else "[ ]"
event_date = self.event.date.strftime('%d/%m/%Y %H:%M:%S')
msg = f"{self.event.get_digest()} {event_date} {state_modified_str} : {self.event.message}"
status = self.status status = self.status
if status is not None: if status is not None:
msg += f" => {status}" msg += f" => {status}"
@@ -40,12 +49,28 @@ class History:
@property @property
def status(self): def status(self):
if self._status: if self._status is not NotInit:
return self._status
if self.extra_info and "status" in self.extra_info:
self._status = self.extra_info["status"]
return self._status return self._status
self._status = self.result.get_status() if self.result else None self._status = self.result.get_status() if self.result else None
return self._status return self._status
@property
def is_state_modified(self):
if self._is_state_modified is not NotInit:
return self._is_state_modified
if self.extra_info and "is_state_modified" in self.extra_info:
self._is_state_modified = self.extra_info["is_state_modified"]
return self._is_state_modified
self._is_state_modified = self.result.is_state_modified() if self.result else None
return self._is_state_modified
def get_format_instructions(self): def get_format_instructions(self):
return self._format_instructions return self._format_instructions
@@ -73,4 +98,10 @@ class SheerkaHistoryManager(BaseService):
result = self.sheerka.om.current_sdp().load_result(event.get_digest()) result = self.sheerka.om.current_sdp().load_result(event.get_digest())
except (IOError, KeyError): except (IOError, KeyError):
result = None result = None
yield History(event, result)
try:
extra_info = self.sheerka.om.current_sdp().load_result_extra_info(event.get_digest())
except (IOError, KeyError):
extra_info = None
yield History(event, result, extra_info)
@@ -1,8 +1,11 @@
import ast import ast
import os
from cache.Cache import Cache from cache.Cache import Cache
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.global_symbols import EVENT_USER_INPUT_EVALUATED, EVENT_CONCEPT_CREATED, NotFound from core.global_symbols import EVENT_USER_INPUT_EVALUATED, EVENT_CONCEPT_CREATED, NotFound, EVENT_CONCEPT_MODIFIED, \
EVENT_CONCEPT_DELETED, EVENT_CONCEPT_PRECEDENCE_MODIFIED, EVENT_RULE_CREATED, EVENT_RULE_DELETED, \
EVENT_RULE_PRECEDENCE_MODIFIED, SHEERKA_BACKUP_FOLDER, SHEERKA_BACKUP_FILE
from core.sheerka.services.sheerka_service import BaseService from core.sheerka.services.sheerka_service import BaseService
from core.utils import CONSOLE_COLORS_MAP as CCM from core.utils import CONSOLE_COLORS_MAP as CCM
from core.utils import as_bag from core.utils import as_bag
@@ -40,6 +43,14 @@ class SheerkaResultManager(BaseService):
self.sheerka.subscribe(EVENT_USER_INPUT_EVALUATED, self.user_input_evaluated) self.sheerka.subscribe(EVENT_USER_INPUT_EVALUATED, self.user_input_evaluated)
self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.new_concept_created) self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.new_concept_created)
self.sheerka.subscribe(EVENT_CONCEPT_CREATED, self.on_global_state_is_modified)
self.sheerka.subscribe(EVENT_CONCEPT_MODIFIED, self.on_global_state_is_modified)
self.sheerka.subscribe(EVENT_CONCEPT_DELETED, self.on_global_state_is_modified)
self.sheerka.subscribe(EVENT_CONCEPT_PRECEDENCE_MODIFIED, self.on_global_state_is_modified)
self.sheerka.subscribe(EVENT_RULE_CREATED, self.on_global_state_is_modified)
self.sheerka.subscribe(EVENT_RULE_DELETED, self.on_global_state_is_modified)
self.sheerka.subscribe(EVENT_RULE_PRECEDENCE_MODIFIED, self.on_global_state_is_modified)
def initialize_deferred(self, context, is_first_time): def initialize_deferred(self, context, is_first_time):
self.restore_values(*self.state_vars) self.restore_values(*self.state_vars)
@@ -59,6 +70,11 @@ class SheerkaResultManager(BaseService):
@staticmethod @staticmethod
def get_predicate(**kwargs): def get_predicate(**kwargs):
"""
Create a filtering predicate from a arguments
:param kwargs:
:return:
"""
if len(kwargs) == 0: if len(kwargs) == 0:
return None return None
res = [] res = []
@@ -241,13 +257,25 @@ class SheerkaResultManager(BaseService):
:param execution_context: :param execution_context:
:return: :return:
""" """
extra_info = {
"status": execution_context.get_status(),
"is_state_modified": execution_context.is_state_modified(),
}
if self.sheerka.save_execution_context: if self.sheerka.save_execution_context:
try: try:
self.sheerka.om.current_sdp().save_result(execution_context) self.sheerka.om.current_sdp().save_result(execution_context)
except Exception as ex: except Exception as ex:
print(f"{CCM['red']}Failed to save execution context. Reason: {ex}{CCM['reset']}") print(f"{CCM['red']}Failed to save execution context. Reason: {ex}{CCM['reset']}")
# save extra info
try:
self.sheerka.om.current_sdp().save_result_extra_info(execution_context.event.get_digest(), extra_info)
except Exception as ex:
print(f"{CCM['red']}Failed to save execution context extra info. Reason: {ex}{CCM['reset']}")
pass pass
# self.log.error(f"Failed to save execution context. Reason: {ex}")
# write the command in a backup file if needed
self.backup_command(execution_context)
self.executions_contexts_cache.put(execution_context.event.get_digest(), execution_context) self.executions_contexts_cache.put(execution_context.event.get_digest(), execution_context)
self.last_execution = execution_context self.last_execution = execution_context
@@ -298,11 +326,21 @@ class SheerkaResultManager(BaseService):
return self.last_errors return self.last_errors
def new_concept_created(self, context, concept): def new_concept_created(self, context, concept):
"""
Subscriber (callback) when a new concept is created
:param context:
:param concept:
:return:
"""
self.last_created_concept = concept self.last_created_concept = concept
self.last_created_concept_id = concept.id self.last_created_concept_id = concept.id
self.sheerka.record_var(context, self.NAME, "last_created_concept_id", concept.id) self.sheerka.record_var(context, self.NAME, "last_created_concept_id", concept.id)
@staticmethod
def on_global_state_is_modified(context, *argv, **kwargs):
context.set_state_is_modified()
def get_last_created_concept(self, context): def get_last_created_concept(self, context):
if self.last_created_concept: if self.last_created_concept:
return self.last_created_concept return self.last_created_concept
@@ -333,3 +371,24 @@ class SheerkaResultManager(BaseService):
consumed = 0 consumed = 0
return None return None
def backup_command(self, execution_context):
if (self.sheerka.during_restore or
not execution_context.is_state_modified() or
not self.sheerka.enable_commands_backup):
return
folder = os.getenv(SHEERKA_BACKUP_FOLDER)
if folder is None:
print(f"{CCM['red']}Cannot backup command. Backup folder (env SHEERKA_BACKUP_FOLDER) is not defined.{CCM['reset']}")
return
file = os.getenv(SHEERKA_BACKUP_FILE)
if file is None:
print(f"{CCM['red']}Cannot backup command. Backup file (env SHEERKA_BACKUP_FILE) is not defined.{CCM['reset']}")
return
full_path = os.path.join(folder, file)
with open(full_path, "a") as f:
f.write(f"{execution_context.event.message}\n")
@@ -42,8 +42,14 @@ class SheerkaVariableManager(BaseService):
def __init__(self, sheerka): def __init__(self, sheerka):
super().__init__(sheerka, order=3) super().__init__(sheerka, order=3)
# Bound variables:
# automatically set services (including Sheerka) attributes at startup or at ontology creation / deletion
self.bound_variables = { self.bound_variables = {
self.sheerka.name: {"enable_process_return_values", "save_execution_context", "enable_process_rules"} self.sheerka.name: {"enable_process_return_values",
"save_execution_context",
"enable_process_rules",
"enable_commands_backup"}
} }
def initialize(self): def initialize(self):
@@ -88,6 +94,7 @@ class SheerkaVariableManager(BaseService):
if who in self.bound_variables and key in self.bound_variables[who]: if who in self.bound_variables and key in self.bound_variables[who]:
service = self.sheerka if who == self.sheerka.name else self.sheerka.services[who] service = self.sheerka if who == self.sheerka.name else self.sheerka.services[who]
setattr(service, key, value) setattr(service, key, value)
print(f"{service=} is set")
def load_var(self, who, key): def load_var(self, who, key):
variable = self.sheerka.om.get(self.VARIABLES_ENTRY, who + "|" + key) variable = self.sheerka.om.get(self.VARIABLES_ENTRY, who + "|" + key)
+21
View File
@@ -777,6 +777,14 @@ def escape_str(x):
return x return x
def new_array(size):
res = []
for _ in range(size):
res.append(0)
return res
class NextIdManager: class NextIdManager:
""" """
solely return the next integer solely return the next integer
@@ -788,3 +796,16 @@ class NextIdManager:
def get_next_id(self): def get_next_id(self):
self.id += 1 self.id += 1
return self.id return self.id
def compute_hash(obj):
if isinstance(obj, list):
return hash(tuple([compute_hash(o) for o in obj]))
if isinstance(obj, set):
return hash(tuple([compute_hash(o) for o in sorted(list(obj))]))
if isinstance(obj, dict):
return hash(repr(obj))
return hash(obj)
+4
View File
@@ -53,6 +53,7 @@ class Expando:
@dataclass @dataclass
class PythonEvalError: class PythonEvalError:
error: Exception error: Exception
source: str
traceback: str = field(repr=False) traceback: str = field(repr=False)
concepts: dict = field(repr=False) concepts: dict = field(repr=False)
@@ -64,6 +65,7 @@ class PythonEvalError:
return False return False
return isinstance(self.error, type(other.error)) and \ return isinstance(self.error, type(other.error)) and \
self.source == other.source and \
self.traceback == other.traceback and \ self.traceback == other.traceback and \
self.concepts == other.concepts self.concepts == other.concepts
@@ -151,10 +153,12 @@ class PythonEvaluator(OneReturnValueEvaluator):
if concepts_entries is None: if concepts_entries is None:
concepts_entries = self.get_concepts_entries_from_globals(my_globals) concepts_entries = self.get_concepts_entries_from_globals(my_globals)
eval_error = PythonEvalError(ex, eval_error = PythonEvalError(ex,
node.source,
traceback.format_exc() if get_trace_back else None, traceback.format_exc() if get_trace_back else None,
self.get_concepts_values_from_globals(globals_, concepts_entries)) self.get_concepts_values_from_globals(globals_, concepts_entries))
errors.append(eval_error) errors.append(eval_error)
exception_debugger.debug_var("exception", eval_error.error, is_error=True) exception_debugger.debug_var("exception", eval_error.error, is_error=True)
exception_debugger.debug_var("source", eval_error.source, is_error=True)
exception_debugger.debug_var("trace", eval_error.traceback, is_error=True) exception_debugger.debug_var("trace", eval_error.traceback, is_error=True)
if evaluated == NotInit: if evaluated == NotInit:
+213 -94
View File
@@ -4,10 +4,13 @@ import inspect
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from prompt_toolkit.completion import Completer, Completion
from core.concept import Concept
from core.sheerka.Sheerka import EXIT_COMMANDS from core.sheerka.Sheerka import EXIT_COMMANDS
from core.sheerka.services.SheerkaConceptManager import SheerkaConceptManager
from core.sheerka.services.SheerkaFunctionsParametersHistory import SheerkaFunctionsParametersHistory from core.sheerka.services.SheerkaFunctionsParametersHistory import SheerkaFunctionsParametersHistory
from core.tokenizer import Tokenizer, TokenKind, LexerError from core.tokenizer import Tokenizer, TokenKind, LexerError
from prompt_toolkit.completion import Completer, Completion
NAME = re.compile(r'[a-zA-Z0-9_\.]*[a-zA-Z_]') NAME = re.compile(r'[a-zA-Z0-9_\.]*[a-zA-Z_]')
@@ -24,9 +27,119 @@ class FuncFound:
@dataclass @dataclass
class CompletionDesc: class CompletionDesc:
text: str text: str # what to autocomplete
display: str display: str # what to display in the list of proposals
meta_display: str meta_display: str # extra info when listing proposals
class ConceptCompleterHelper:
def __init__(self, concept: Concept, pos: int):
self.concept_id = concept.id
self.display = concept.name
self.parts = self.initialize_parts(concept)
self.start_text_index = pos - 1
self.last_part_index = 0 # for debug purpose
self.need_candidates = False # when multi-parts, we have reached the end of a part
self.completion = None
self.to_remove = False # True if completed or if a mismatch is found
def __repr__(self):
return f"ConceptCompleterHelper(id={self.concept_id}, pos={self.start_text_index}, part={self.last_part_index})"
@staticmethod
def initialize_parts(concept):
"""
initialize the different parts of the concept, when trying to auto complete
:param concept:
:return:
"""
parts = []
buffer = ""
for t in Tokenizer(concept.key, yield_eof=False):
if t.type == TokenKind.VAR_DEF:
if buffer.strip():
parts.append(buffer.lstrip())
if len(parts) > 0 and parts[-1] != TokenKind.VAR_DEF:
parts.append(TokenKind.VAR_DEF)
buffer = ""
else:
buffer += t.to_str(False)
# do not forget the remaining part
if buffer.strip():
parts.append(buffer.lstrip())
return parts
def match(self, text, pos):
if pos < self.start_text_index:
self.to_remove = True
return False
current_part_index = 0
current_text_index = 0
previous_char = None
self.completion = self.get_completion(text, current_part_index, current_text_index)
for c in text[self.start_text_index:pos]:
current_text = self.parts[current_part_index]
self.need_candidates = False
if current_text == TokenKind.VAR_DEF:
if (previous_char == " " and
current_part_index < len(self.parts) - 1 and c == self.parts[current_part_index + 1][0]):
# we are starting the next part
current_part_index += 1
current_text_index = 1
self.completion = self.get_completion(text, current_part_index, current_text_index)
else:
if c == current_text[current_text_index]:
# Validating a part
current_text_index += 1
self.completion = self.get_completion(text, current_part_index, current_text_index)
if current_text_index == len(current_text):
# The whole part is validated
current_part_index += 1
if current_part_index == len(self.parts):
# no more part to validate
self.need_candidates = True # ask for new candidates
# self.to_remove = True
return False
else:
current_text_index = 0
self.need_candidates = True
else:
# a misspell is found. go back to the previous state
current_part_index -= 1
if current_part_index < 0:
self.last_part_index = current_part_index
return False
self.completion = self.get_completion(text, current_part_index, current_text_index)
previous_char = c
self.last_part_index = current_part_index
return True
def get_completion(self, text, current_part_index, current_text_index):
if self.parts[current_part_index] == TokenKind.VAR_DEF:
if current_part_index == len(self.parts) - 1:
return text[self.start_text_index:]
else:
if text[-1] == " ":
return text[self.start_text_index:] + self.parts[current_part_index + 1]
else:
return text[self.start_text_index:] + " " + self.parts[current_part_index + 1]
# else
return text[self.start_text_index:] + self.parts[current_part_index][current_text_index:]
class SheerkaPromptCompleter(Completer): class SheerkaPromptCompleter(Completer):
@@ -36,29 +149,40 @@ class SheerkaPromptCompleter(Completer):
self.params_history_service = self.sheerka.services[SheerkaFunctionsParametersHistory.NAME] self.params_history_service = self.sheerka.services[SheerkaFunctionsParametersHistory.NAME]
self.builtins = [] self.builtins = []
for name, bound_method in sheerka.sheerka_methods.items(): for name, bound_method in sheerka.sheerka_methods.items():
self.builtins.append(self.get_completion_desc(name, bound_method.method, "builtin", ["context"])) self.builtins.append(self.get_completion_desc_for_function(name,
bound_method.method,
"builtin",
["context"]))
self.exit_commands = [CompletionDesc(c, c, "command") for c in EXIT_COMMANDS] self.exit_commands = [CompletionDesc(c, c, "command") for c in EXIT_COMMANDS]
self.globals = {k: v.method for k, v in self.sheerka.sheerka_methods.items()} self.globals = {k: v.method for k, v in self.sheerka.sheerka_methods.items()}
self.concepts_by_first_keyword = sheerka.om.copy(SheerkaConceptManager.RESOLVED_CONCEPTS_BY_FIRST_KEYWORD_ENTRY)
self.concepts_candidates = {} # key = (concept_id, pos), value = ConceptCompleterHelper
self.last_token_index = None
self.complete_concepts = True
self.complete_commands = True
self.complete_builtins = True
def get_completions(self, document, complete_event): def get_completions(self, document, complete_event):
text = document.text_before_cursor text = document.text_before_cursor
pos = document.cursor_position
if func_found := self.inside_function(document.text, document.cursor_position): last_token = self.get_last_token(text)
param_number, comma_index = self.get_param_number(text[func_found.paren_index + 1:]) if self.last_token_index is None or last_token and last_token.index != self.last_token_index:
values = self.params_history_service.get_function_parameters(func_found.name, param_number) self.last_token_index = last_token.index if last_token else 0
as_custom_desc = [CompletionDesc(v, v, "history") for v in values] # new word or new symbol detected
param_text = text[func_found.paren_index + comma_index + 2:].lstrip() self.update_concepts_candidates(last_token, pos)
yield from self.yield_completion_from_completion_desc(as_custom_desc, param_text)
return
if " " not in text: if self.complete_concepts:
yield from self.yield_completion_for_concepts(self.concepts_candidates.values(), text, pos)
if self.complete_commands:
yield from self.yield_completion_from_completion_desc(self.exit_commands, text) yield from self.yield_completion_from_completion_desc(self.exit_commands, text)
yield from self.yield_completion_from_completion_desc(self.builtins, text)
return
yield from self.yield_completion_from_completion_desc(self.builtins, text) if self.complete_builtins:
yield from self.yield_completion_from_completion_desc(self.builtins, text)
def get_completions_fom_jedi(self, document): def get_completions_fom_jedi(self, document):
script = self.get_jedi_script_from_document(document, self.globals, self.globals) script = self.get_jedi_script_from_document(document, self.globals, self.globals)
@@ -109,6 +233,39 @@ class SheerkaPromptCompleter(Completer):
display=c.name_with_symbols, display=c.name_with_symbols,
) )
def update_concepts_candidates(self, last_token, pos):
possible_concepts_ids = set()
candidates_that_need_candidates = [c for c in self.concepts_candidates.values() if c.need_candidates]
if len(self.concepts_candidates) == 0 or len(candidates_that_need_candidates) > 0:
last_word = last_token.str_value.strip() if last_token else ""
for k, v in self.concepts_by_first_keyword.items():
if k.startswith(last_word):
possible_concepts_ids.update(set(v))
for concept_id in possible_concepts_ids:
if (concept_id, pos) not in self.concepts_candidates:
concept = self.sheerka.fast_resolve((None, concept_id), return_new=False)
if concept:
concept_completer_helper = ConceptCompleterHelper(concept, pos)
self.concepts_candidates[(concept_id, pos)] = concept_completer_helper
def update_functions_candidates(self):
# if func_found := self.inside_function(document.text, document.cursor_position):
# param_number, comma_index = self.get_param_number(text[func_found.paren_index + 1:])
# values = self.params_history_service.get_function_parameters(func_found.name, param_number)
# as_custom_desc = [CompletionDesc(v, v, "history") for v in values]
# param_text = text[func_found.paren_index + comma_index + 2:].lstrip()
# yield from self.yield_completion_from_completion_desc(as_custom_desc, param_text)
# return
# if " " not in text:
# yield from self.yield_completion_from_completion_desc(self.exit_commands, text)
# yield from self.yield_completion_from_completion_desc(self.builtins, text)
# yield from self.yield_completion_from_completion_desc(possible_concepts, text)
# return
pass
@staticmethod @staticmethod
def yield_completion_from_completion_desc(definitions, text): def yield_completion_from_completion_desc(definitions, text):
for completion_desc in definitions: for completion_desc in definitions:
@@ -123,8 +280,35 @@ class SheerkaPromptCompleter(Completer):
display=completion_desc.display, display=completion_desc.display,
display_meta=completion_desc.meta_display) display_meta=completion_desc.meta_display)
def yield_completion_for_concepts(self, concepts_completion_helpers, text, pos):
for concept_completion_helper in concepts_completion_helpers:
if text is None or text == "":
yield Completion(concept_completion_helper.parts[0],
0,
display=concept_completion_helper.display,
display_meta="concept")
else:
if concept_completion_helper.match(text, pos):
yield Completion(concept_completion_helper.completion,
concept_completion_helper.start_text_index - pos,
display=concept_completion_helper.display,
display_meta="concept")
# clean
items = [i for i in self.concepts_candidates.values() if i.to_remove]
for i in items:
del self.concepts_candidates[(i.concept_id, i.start_text_index + 1)]
@staticmethod @staticmethod
def get_completion_desc(name, function, meta_display, skip_params): def get_completion_desc_for_function(name, function, meta_display, skip_params):
"""
Manage trailing parenthesis when autocompleting a function call
:param name: name of the function
:param function: actual function (Python object) to parse the signature
:param meta_display: function meta
:param skip_params: parameters that must not be displayed (like 'context' for example)
:return:
"""
function_name = name + "(" function_name = name + "("
signature = inspect.signature(function) signature = inspect.signature(function)
@@ -166,19 +350,21 @@ class SheerkaPromptCompleter(Completer):
return None return None
@staticmethod @staticmethod
def last_word(text, pos, left_strip=True): def get_last_word(text):
if pos == 0: if text is None or text.strip() == "":
return "" return ""
start = pos - 1 if text[pos - 1] == " " else pos return list(Tokenizer(text, yield_eof=False))[-1].str_value.strip()
if start < 0:
return ""
for i in range(start)[::-1]: @staticmethod
if text[i] == " ": def get_last_token(text):
return text[i:pos].lstrip() if left_strip else text[i:pos] if text is None or text.strip() == "":
return None
return text[:pos].lstrip() if left_strip else text[:pos] try:
return list(Tokenizer(text, yield_eof=False))[-1]
except Exception:
return None
@staticmethod @staticmethod
def get_param_number(text): def get_param_number(text):
@@ -234,70 +420,3 @@ class SheerkaPromptCompleter(Completer):
except Exception: except Exception:
# Workaround for: https://github.com/jonathanslenders/ptpython/issues/91 # Workaround for: https://github.com/jonathanslenders/ptpython/issues/91
return None return None
# def find_backwards(
# self,
# sub: str,
# in_current_line: bool = False,
# ignore_case: bool = False,
# count: int = 1,
# ) -> Optional[int]:
# """
# Find `text` before the cursor, return position relative to the cursor
# position. Return `None` if nothing was found.
# :param count: Find the n-th occurrence.
# """
# if in_current_line:
# before_cursor = self.current_line_before_cursor[::-1]
# else:
# before_cursor = self.text_before_cursor[::-1]
#
# flags = re.IGNORECASE if ignore_case else 0
# iterator = re.finditer(re.escape(sub[::-1]), before_cursor, flags)
#
# try:
# for i, match in enumerate(iterator):
# if i + 1 == count:
# return -match.start(0) - len(sub)
# except StopIteration:
# pass
# return None
# def find(
# self,
# sub: str,
# in_current_line: bool = False,
# include_current_position: bool = False,
# ignore_case: bool = False,
# count: int = 1,
# ) -> Optional[int]:
# """
# Find `text` after the cursor, return position relative to the cursor
# position. Return `None` if nothing was found.
# :param count: Find the n-th occurrence.
# """
# assert isinstance(ignore_case, bool)
#
# if in_current_line:
# text = self.current_line_after_cursor
# else:
# text = self.text_after_cursor
#
# if not include_current_position:
# if len(text) == 0:
# return None # (Otherwise, we always get a match for the empty string.)
# else:
# text = text[1:]
#
# flags = re.IGNORECASE if ignore_case else 0
# iterator = re.finditer(re.escape(sub), text, flags)
#
# try:
# for i, match in enumerate(iterator):
# if i + 1 == count:
# if include_current_position:
# return match.start(0)
# else:
# return match.start(0) + 1
# except StopIteration:
# pass
# return None
+35
View File
@@ -437,6 +437,10 @@ class SheerkaDataProvider:
ext = "_admin_result" if is_admin else "_result" ext = "_admin_result" if is_admin else "_result"
return self.io.get_obj_path(SheerkaDataProvider.EventFolder, digest) + ext return self.io.get_obj_path(SheerkaDataProvider.EventFolder, digest) + ext
def get_result_extra_info_file_path(self, digest):
ext = "_result_extra_info"
return self.io.get_obj_path(SheerkaDataProvider.EventFolder, digest) + ext
def has_result(self, digest, is_admin=False): def has_result(self, digest, is_admin=False):
""" """
Check is a result file was created for a specific event Check is a result file was created for a specific event
@@ -471,6 +475,23 @@ class SheerkaDataProvider:
self.log.debug(f"Saved execution context. message={message}, length={length}, elapsed={elapsed}") self.log.debug(f"Saved execution context. message={message}, length={length}, elapsed={elapsed}")
return digest return digest
def save_result_extra_info(self, digest, result_extra_info):
"""
On the top of execution context, also save some extra information like, status of the execution
:param digest:
:param result_extra_info:
:return:
"""
self.log.debug(f"Saving execution context extra information. digest={digest}")
target_path = self.get_result_extra_info_file_path(digest)
if self.io.exists(target_path):
return digest
context = SerializerContext(sheerka=self.sheerka)
length = self.io.write_binary(target_path, self.serializer.serialize(result_extra_info, context).read())
self.log.debug(f"Saved execution context. message={result_extra_info}, length={length}")
return digest
def load_result(self, digest, is_admin=False): def load_result(self, digest, is_admin=False):
""" """
Load and deserialize a result file Load and deserialize a result file
@@ -484,6 +505,20 @@ class SheerkaDataProvider:
context = SerializerContext(sheerka=self.sheerka) context = SerializerContext(sheerka=self.sheerka)
return self.serializer.deserialize(f, context) return self.serializer.deserialize(f, context)
def load_result_extra_info(self, digest):
"""
Load and deserialize a result extra file
:param digest:
:return:
:param digest:
:return:
"""
target_path = self.get_result_extra_info_file_path(digest)
with self.io.open(target_path, "rb") as f:
context = SerializerContext(sheerka=self.sheerka)
return self.serializer.deserialize(f, context)
def load_ref_if_needed(self, obj, load_origin=True): def load_ref_if_needed(self, obj, load_origin=True):
""" """
Make sure the real obj is returned Make sure the real obj is returned
+2 -1
View File
@@ -25,7 +25,8 @@ class TestUsingMemoryBasedSheerka(BaseTest):
sheerka.initialize("mem://", sheerka.initialize("mem://",
save_execution_context=False, save_execution_context=False,
enable_process_return_values=False, enable_process_return_values=False,
enable_process_rules=False) enable_process_rules=False,
enable_commands_backup=False)
return sheerka return sheerka
def get_sheerka(self, **kwargs) -> Sheerka: def get_sheerka(self, **kwargs) -> Sheerka:
+169 -1
View File
@@ -840,7 +840,8 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka):
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
assert sheerka.get_property(foo_instance, BuiltinConcepts.ASSOCIATIVITY) == SyaAssociativity.Left assert sheerka.get_property(foo_instance, BuiltinConcepts.ASSOCIATIVITY) == SyaAssociativity.Left
res = sheerka.set_property(context, foo_instance, sheerka.new(BuiltinConcepts.ASSOCIATIVITY), SyaAssociativity.Right) res = sheerka.set_property(context, foo_instance, sheerka.new(BuiltinConcepts.ASSOCIATIVITY),
SyaAssociativity.Right)
assert res.status assert res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS)
assert sheerka.get_property(foo_instance, BuiltinConcepts.ASSOCIATIVITY) == SyaAssociativity.Right assert sheerka.get_property(foo_instance, BuiltinConcepts.ASSOCIATIVITY) == SyaAssociativity.Right
@@ -1235,6 +1236,173 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka):
assert sheerka.chicken_and_eggs.get(two.id) == {one.id, two.id, three.id} assert sheerka.chicken_and_eggs.get(two.id) == {one.id, two.id, three.id}
assert sheerka.chicken_and_eggs.get(three.id) == {one.id, two.id, three.id} assert sheerka.chicken_and_eggs.get(three.id) == {one.id, two.id, three.id}
def test_i_can_smart_get_attr_when_the_value_is_known(self):
sheerka, context = self.init_concepts()
foo = Concept("foo")
prop = Concept("property")
bar = Concept("bar")
sheerka.set_attr(foo, prop, bar)
assert sheerka.smart_get_attr(foo, prop) == bar
def test_i_can_smart_get_attr_when_simple_isa(self):
sheerka, context, adjective, color, red, table = self.init_concepts("adjective",
"color",
"red",
"table",
create_new=True)
sheerka.set_isa(context, color, adjective)
sheerka.set_isa(context, red, color)
color_instance = sheerka.new(color, body=red)
adjective_instance = sheerka.new(adjective, body=color_instance)
table_instance = sheerka.new(table)
sheerka.set_attr(table_instance, adjective, adjective_instance)
assert sheerka.smart_get_attr(table_instance, color) == color_instance
assert sheerka.objvalue(sheerka.smart_get_attr(table_instance, color)) == red
def test_i_can_smart_get_when_multiple_levels_of_isa(self):
sheerka, context, adjective, color, reddish, red, table = self.init_concepts("adjective",
"color",
"reddish",
"red",
"table",
create_new=True)
sheerka.set_isa(context, color, adjective)
sheerka.set_isa(context, reddish, color)
sheerka.set_isa(context, red, reddish)
reddish_instance = sheerka.new(reddish, body=red)
color_instance = sheerka.new(color, body=reddish_instance)
adjective_instance = sheerka.new(adjective, body=color_instance)
table_instance = sheerka.new(table)
sheerka.set_attr(table_instance, adjective, adjective_instance)
assert sheerka.smart_get_attr(table_instance, reddish_instance) == reddish_instance
assert sheerka.objvalue(sheerka.smart_get_attr(table_instance, color)) == red
def test_i_can_smart_get_when_multiple_values(self):
sheerka, context, adjective, color, red, blue, table = self.init_concepts("adjective",
"color",
"red",
"blue",
"table",
create_new=True)
sheerka.set_isa(context, color, adjective)
sheerka.set_isa(context, red, color)
sheerka.set_isa(context, blue, color)
table_instance = sheerka.new(table)
# add red color
red_color_instance = sheerka.new(color, body=red)
red_adjective_instance = sheerka.new(adjective, body=red_color_instance)
sheerka.set_attr(table_instance, adjective, red_adjective_instance)
# add blue color
blue_color_instance = sheerka.new(color, body=blue)
blue_adjective_instance = sheerka.new(adjective, body=blue_color_instance)
sheerka.set_attr(table_instance, adjective, blue_adjective_instance)
res = sheerka.smart_get_attr(table_instance, color)
assert res == [red_color_instance, blue_color_instance]
def test_i_can_smart_get_when_ancestor_is_asked(self):
sheerka, context, adjective, color, red, table = self.init_concepts("adjective",
"color",
"red",
"table",
create_new=True)
sheerka.set_isa(context, color, adjective)
sheerka.set_isa(context, red, color)
color_instance = sheerka.new(color, body=red)
table_instance = sheerka.new(table)
sheerka.set_attr(table_instance, color, color_instance)
assert sheerka.smart_get_attr(table_instance, adjective) == color_instance
def test_i_can_smart_get_when_ancestor_is_asked_and_multiple_values(self):
sheerka, context, adjective, color, red, size, large, table = self.init_concepts("adjective",
"color",
"red",
"size",
"large",
"table",
create_new=True)
sheerka.set_isa(context, color, adjective)
sheerka.set_isa(context, red, color)
sheerka.set_isa(context, size, adjective)
sheerka.set_isa(context, large, size)
table_instance = sheerka.new(table)
color_instance = sheerka.new(color, body=red)
sheerka.set_attr(table_instance, color, color_instance)
size_instance = sheerka.new(size, body=large)
sheerka.set_attr(table_instance, size, size_instance)
assert sheerka.smart_get_attr(table_instance, adjective) == [color_instance, size_instance]
def test_i_can_smart_get_when_direct_value_takes_precedence_over_child_value(self):
sheerka, context, adjective, color, red, blue, table = self.init_concepts("adjective",
"color",
"red",
"blue",
"table",
create_new=True)
sheerka.set_isa(context, color, adjective)
sheerka.set_isa(context, red, color)
color_instance = sheerka.new(color, body=red)
adjective_instance = sheerka.new(adjective, body=color_instance)
table_instance = sheerka.new(table)
sheerka.set_attr(table_instance, adjective, adjective_instance)
sheerka.set_attr(table_instance, color, blue) # set direct color value
assert sheerka.smart_get_attr(table_instance, color) == blue
def test_i_can_smart_get_when_direct_value_takes_precedence_over_ancestor_value(self):
sheerka, context, adjective, color, red, blue, table = self.init_concepts("adjective",
"color",
"red",
"blue",
"table",
create_new=True)
sheerka.set_isa(context, color, adjective)
sheerka.set_isa(context, red, color)
color_instance = sheerka.new(color, body=red)
table_instance = sheerka.new(table)
sheerka.set_attr(table_instance, color, color_instance)
sheerka.set_attr(table_instance, adjective, blue) # set direct color value
assert sheerka.smart_get_attr(table_instance, adjective) == blue
def test_i_cannot_smart_get_attr_if_value_is_unknown_and_attribute_not_a_concept(self):
sheerka, context = self.init_concepts()
foo = Concept("foo")
res = sheerka.smart_get_attr(foo, "attribute")
assert sheerka.isinstance(res, BuiltinConcepts.NOT_FOUND)
def test_i_cannot_smart_get_attr_if_value_is_unknown_and_no_isa(self):
sheerka, context, adjective, color, red, table = self.init_concepts("adjective",
"color",
"red",
"table",
create_new=True)
# sheerka.set_isa(context, color, adjective) # color is not defined as an adjective
sheerka.set_isa(context, red, color)
color_instance = sheerka.new(color, body=red)
adjective_instance = sheerka.new(adjective, body=color_instance)
table_instance = sheerka.new(table)
sheerka.set_attr(table_instance, adjective, adjective_instance)
res = sheerka.smart_get_attr(table_instance, color)
assert sheerka.isinstance(res, BuiltinConcepts.NOT_FOUND)
class TestSheerkaConceptManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka): class TestSheerkaConceptManagerUsingFileBasedSheerka(TestUsingFileBasedSheerka):
def test_i_can_add_several_concepts(self): def test_i_can_add_several_concepts(self):
+12
View File
@@ -15,3 +15,15 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka):
# check that the definition of the concept has been updated # check that the definition of the concept has been updated
assert sheerka.hasa(sheerka.new("king"), kingdom) assert sheerka.hasa(sheerka.new("king"), kingdom)
def test_i_cannot_set_the_same_attribute_twice(self):
sheerka, context, king, kingdom = self.init_concepts("king", "kingdom")
sheerka.set_hasa(context, sheerka.new("king"), kingdom)
res = sheerka.set_hasa(context, sheerka.new("king"), kingdom)
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.PROPERTY_ALREADY_DEFINED)
assert res.body.property_name == BuiltinConcepts.HASA
assert res.body.property_value == kingdom
assert res.body.concept == sheerka.new("king")
+49
View File
@@ -3,6 +3,10 @@ import types
import pytest import pytest
from core.builtin_concepts import BuiltinConcepts from core.builtin_concepts import BuiltinConcepts
from core.concept import Concept
from core.global_symbols import EVENT_CONCEPT_CREATED, EVENT_CONCEPT_MODIFIED, EVENT_CONCEPT_PRECEDENCE_MODIFIED, \
EVENT_RULE_PRECEDENCE_MODIFIED
from core.rule import Rule, ACTION_TYPE_EXEC
from core.sheerka.ExecutionContext import ExecutionContext from core.sheerka.ExecutionContext import ExecutionContext
from core.sheerka.SheerkaOntologyManager import SheerkaOntologyManager from core.sheerka.SheerkaOntologyManager import SheerkaOntologyManager
from core.sheerka.services.SheerkaResultManager import SheerkaResultManager from core.sheerka.services.SheerkaResultManager import SheerkaResultManager
@@ -400,6 +404,51 @@ class TestSheerkaResultManager(TestUsingMemoryBasedSheerka):
assert service.last_error_event_id is None assert service.last_error_event_id is None
assert sheerka.get_last_error() == self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"query": "get_last_error"}) assert sheerka.get_last_error() == self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"query": "get_last_error"})
@pytest.mark.parametrize("event_id, event_args", [
(EVENT_CONCEPT_CREATED, Concept("foo")),
(EVENT_CONCEPT_MODIFIED, {"old": Concept("foo"), "new": Concept("bar")}),
(EVENT_CONCEPT_PRECEDENCE_MODIFIED, None),
(EVENT_RULE_PRECEDENCE_MODIFIED, None),
])
def test_i_can_detect_when_the_global_state_has_change(self, event_id, event_args):
sheerka, context = self.init_test().unpack()
assert not context.is_state_modified()
if event_args:
sheerka.publish(context, event_id, event_args)
else:
sheerka.publish(context, event_id)
assert context.is_state_modified()
def test_i_can_detect_when_the_global_state_has_change_when_a_concept_is_deleted(self):
sheerka, context, foo = self.init_concepts("foo", create_new=True)
del context.values["is_state_modified"]
assert not context.is_state_modified()
with context.push("Testing state", None) as sub_context:
sheerka.remove_concept(sub_context, foo)
assert context.is_state_modified()
def test_i_can_detect_when_the_global_state_has_change_when_a_rule_is_created(self):
sheerka, context = self.init_test().unpack()
rule = Rule(ACTION_TYPE_EXEC, "testing state has change", "True", "'hello world'")
with context.push("Testing state", None) as sub_context:
sheerka.create_new_rule(sub_context, rule)
assert context.is_state_modified()
def test_i_can_detect_when_the_global_state_has_change_when_a_rule_is_deleted(self):
sheerka, context, rule = self.init_exec_rules(("testing state has change", "True", "'hello world'"))
del context.values["is_state_modified"]
assert not context.is_state_modified()
with context.push("Testing state", None) as sub_context:
sheerka.remove_rule(sub_context, rule)
assert context.is_state_modified()
class TestSheerkaResultManagerFileBased(TestUsingFileBasedSheerka): class TestSheerkaResultManagerFileBased(TestUsingFileBasedSheerka):
@classmethod @classmethod
+13
View File
@@ -430,6 +430,19 @@ def test_i_can_deep_copy_a_custom_type():
assert core.utils.sheerka_deepcopy(Removed) is Removed assert core.utils.sheerka_deepcopy(Removed) is Removed
def test_i_can_deep_copy_concepts_than_look_the_same():
foo = Concept("foo")
foo2 = Concept().update_from(foo)
foo2.set_value("prop_name", "prop_value")
assert foo != foo2
copied_foo = core.utils.sheerka_deepcopy(foo)
copied_foo2 = core.utils.sheerka_deepcopy(foo2)
assert copied_foo != copied_foo2
assert len({copied_foo, copied_foo2}) == 2
@pytest.mark.parametrize("expression1, expression2, expected", [ @pytest.mark.parametrize("expression1, expression2, expected", [
("foo bar baz", "foo bar baz", True), ("foo bar baz", "foo bar baz", True),
("foo()", " foo ( ) ", True), ("foo()", " foo ( ) ", True),
+195 -34
View File
@@ -1,9 +1,10 @@
import pytest import pytest
from core.sheerka.services.SheerkaFunctionsParametersHistory import SheerkaFunctionsParametersHistory
from prompt_toolkit.completion import CompleteEvent from prompt_toolkit.completion import CompleteEvent
from prompt_toolkit.document import Document from prompt_toolkit.document import Document
from repl.SheerkaPromptCompleter import SheerkaPromptCompleter, FuncFound
from core.concept import Concept
from core.tokenizer import TokenKind
from repl.SheerkaPromptCompleter import SheerkaPromptCompleter, FuncFound, ConceptCompleterHelper
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
@@ -38,41 +39,180 @@ class TestSheerkaPromptCompleter(TestUsingMemoryBasedSheerka):
assert as_dict["quit"].display_text == "quit" assert as_dict["quit"].display_text == "quit"
assert as_dict["quit"].display_meta_text == "command" assert as_dict["quit"].display_meta_text == "command"
@pytest.mark.parametrize("text, expected", [ @pytest.mark.parametrize("concept_name", [
("func(", ["10", "20", "30"]), "foo",
("func(1", ["10"]), "a long name",
("func( 1", ["10"]),
("func( 10, ", ["'hello'"]),
("func( 10, v", []),
("func( 10, 'hel", ["'hello'"]),
('func( 10, "hel', []),
("func('hell,,', func2(2,4), 'w", ["'world'"]),
]) ])
def test_i_can_complete_function_parameters(self, text, expected): def test_i_can_complete_concept(self, concept_name):
sheerka = self.get_sheerka() sheerka, context, foo = self.init_concepts(Concept(concept_name), create_new=True)
context = self.get_context(sheerka) completer = SheerkaPromptCompleter(sheerka)
params_history_service = sheerka.services[SheerkaFunctionsParametersHistory.NAME] completer.complete_commands = False
params_history_service.record_function_parameter(context, "func", 0, "10") completer.complete_builtins = False
params_history_service.record_function_parameter(context, "func", 0, "20")
params_history_service.record_function_parameter(context, "func", 0, "30")
params_history_service.record_function_parameter(context, "func", 1, "'hello'")
params_history_service.record_function_parameter(context, "func", 2, "'world'")
document = Document(text) for i in range(1, len(concept_name)):
completions = SheerkaPromptCompleter(sheerka).get_completions(document, CompleteEvent()) before_cursor = concept_name[:i]
as_list = [c.display_text for c in completions] completions = list(completer.get_completions(Document(before_cursor), CompleteEvent()))
assert as_list == expected as_dict = {c.display_text: c for c in completions}
assert concept_name in as_dict, f"{i=}, {before_cursor=}"
@pytest.mark.parametrize("text, pos, expected", [ def test_i_can_complete_sya_concept_with_variable(self):
("", 0, ""), sheerka, context, foo, something, bar = self.init_concepts(
("foo", 3, "foo"), Concept("if true x then do y else do z end").def_var("x").def_var("y").def_var("z"),
("foo ", 4, "foo "), Concept("something"),
("foo", 2, "fo"), Concept("bar"),
("foo bar", 7, "bar"), create_new=True)
("foo bar", 4, "foo "), completer = SheerkaPromptCompleter(sheerka)
]) completer.complete_commands = False
def test_last_word(self, text, pos, expected): completer.complete_builtins = False
assert SheerkaPromptCompleter.last_word(text, pos) == expected
text = "if true some cond then do some thing else do something else end"
# i 0123456789012345678901234567890123456789012345678901234567890123
# 0 1 2 3 4 5 6
for i in range(1, len(text) + 1):
before_cursor = text[:i]
d, c = Document(before_cursor), CompleteEvent()
completions = list(completer.get_completions(d, c))
display_as_dict = {(c.display_text, c.start_position): c for c in completions}
if i == 0:
assert len(completer.concepts_candidates) == 3
assert (foo.name, 0) in display_as_dict
assert display_as_dict[(foo.name, 0)].text == "if true "
assert (something.name, 0) in display_as_dict
assert (bar.name, 0) in display_as_dict
if i in range(1, 8): # 'if true'
assert len(completer.concepts_candidates) == 1
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 0
assert display_as_dict[(foo.name, -i)].text == "if true "
assert len(display_as_dict) == 1
if i == 8: # 'if true '
assert len(completer.concepts_candidates) == 1
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 1
assert display_as_dict[(foo.name, -i)].text == "if true "
assert len(display_as_dict) == 1
elif i in range(9, 13): # 'some'
assert len(completer.concepts_candidates) == 2
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 1
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " then do "
assert (something.name, 8 - i) in display_as_dict
assert display_as_dict[(something.name, 8 - i)].text == "something"
elif i in range(13, 18): # ' cond'
assert len(completer.concepts_candidates) == 2, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 1
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " then do "
elif i == 18: # 'cond '
assert len(completer.concepts_candidates) == 2, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 1
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " then do "
elif i in range(19, 26): # 'then do'
assert len(completer.concepts_candidates) == 2, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 2
assert display_as_dict[(foo.name, -i)].text == "if true some cond then do "
elif i == 26: # 'then do '
assert len(completer.concepts_candidates) == 2, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 3
assert display_as_dict[(foo.name, -i)].text == "if true some cond then do "
elif i in range(27, 31): # 'some'
assert len(completer.concepts_candidates) == 3, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 3
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " else do "
assert (something.name, 26 - i) in display_as_dict
assert display_as_dict[(something.name, 26 - i)].text == "something"
elif i in range(31, 38): # ' thing '
assert len(completer.concepts_candidates) == 3, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 3
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " else do "
elif i in range(38, 45): # 'else do'
assert len(completer.concepts_candidates) == 3, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 4
assert display_as_dict[(foo.name, -i)].text == "if true some cond then do some thing else do "
elif i == 45: # 'else do '
assert len(completer.concepts_candidates) == 3, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 5
assert display_as_dict[(foo.name, -i)].text == "if true some cond then do some thing else do "
elif i in range(46, 54): # 'something'
assert len(completer.concepts_candidates) == 4, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 5
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " end"
assert (something.name, 45 - i) in display_as_dict
assert display_as_dict[(something.name, 45 - i)].text == "something"
elif i == 54: # 'g' from 'something'
assert len(completer.concepts_candidates) == 4, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 5
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " end"
elif i == 55: # ' ' before 'else '
assert len(completer.concepts_candidates) == 7, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 5
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " end"
elif i == 56: # 'e' from 'else '
assert len(completer.concepts_candidates) == 7, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 6
assert display_as_dict[(foo.name, -i)].text == "if true some cond then do some thing else do something end"
elif i in range(57, 60): # 'lse '
assert len(completer.concepts_candidates) == 7, f"{i=}, {before_cursor=}"
assert (foo.name, -i) in display_as_dict
assert completer.concepts_candidates[(foo.id, 1)].last_part_index == 5
assert display_as_dict[(foo.name, -i)].text == before_cursor.rstrip() + " end"
# @pytest.mark.parametrize("text, expected", [
# ("func(", ["10", "20", "30"]),
# ("func(1", ["10"]),
# ("func( 1", ["10"]),
# ("func( 10, ", ["'hello'"]),
# ("func( 10, v", []),
# ("func( 10, 'hel", ["'hello'"]),
# ('func( 10, "hel', []),
# ("func('hell,,', func2(2,4), 'w", ["'world'"]),
# ])
# def test_i_can_complete_function_parameters(self, text, expected):
# sheerka = self.get_sheerka()
# context = self.get_context(sheerka)
# params_history_service = sheerka.services[SheerkaFunctionsParametersHistory.NAME]
# params_history_service.record_function_parameter(context, "func", 0, "10")
# params_history_service.record_function_parameter(context, "func", 0, "20")
# params_history_service.record_function_parameter(context, "func", 0, "30")
# params_history_service.record_function_parameter(context, "func", 1, "'hello'")
# params_history_service.record_function_parameter(context, "func", 2, "'world'")
#
# document = Document(text)
# completions = SheerkaPromptCompleter(sheerka).get_completions(document, CompleteEvent())
# as_list = [c.display_text for c in completions]
# assert as_list == expected
@pytest.mark.parametrize("text, pos, expected", [ @pytest.mark.parametrize("text, pos, expected", [
("", 0, None), ("", 0, None),
@@ -104,6 +244,27 @@ class TestSheerkaPromptCompleter(TestUsingMemoryBasedSheerka):
def test_get_param_number(self, text, expected_param_number, expected_comma_index): def test_get_param_number(self, text, expected_param_number, expected_comma_index):
assert SheerkaPromptCompleter.get_param_number(text) == (expected_param_number, expected_comma_index) assert SheerkaPromptCompleter.get_param_number(text) == (expected_param_number, expected_comma_index)
@pytest.mark.parametrize("concept, expected_parts", [
(Concept("if true x then do y else do z end").def_var("x").def_var("y").def_var("z"), [
"if true ",
TokenKind.VAR_DEF,
"then do ",
TokenKind.VAR_DEF,
"else do ",
TokenKind.VAR_DEF,
"end"
]),
(Concept("x something y").def_var("x").def_var("y"), ["something ", TokenKind.VAR_DEF]),
(Concept("foo x y").def_var("x").def_var("y"), ["foo ", TokenKind.VAR_DEF]),
(Concept("foo x y bar").def_var("x").def_var("y"), ["foo ", TokenKind.VAR_DEF, "bar"]),
(Concept("x y foo").def_var("x").def_var("y"), ["foo"]),
])
def test_i_can_initialize_concept_parts(self, concept, expected_parts):
concept.init_key()
parts = ConceptCompleterHelper.initialize_parts(concept)
assert parts == expected_parts
# def test_jedi_infer(self): # def test_jedi_infer(self):
# sheerka = self.get_sheerka() # sheerka = self.get_sheerka()
# #
+5
View File
@@ -0,0 +1,5 @@
#compdef sheerka.rebuild.sh
_alternative \
'helps:helps:(-h)' \
'backups:available backups:($(find "$SHEERKA_BACKUP_FOLDER" -name "*.sb" -type f -printf "%f\n" | sed "s/\.[^.]*$//"))'
+5
View File
@@ -0,0 +1,5 @@
#compdef sheerka.reset.sh
_alternative \
'helps:helps:(-h)' \
"backups:available backups:($(find $HOME/.sheerka_* -maxdepth 0 -type d -printf '%f\n' | awk -F_ '{ print $2}'))"
+15
View File
@@ -0,0 +1,15 @@
To allow auto completion
* make sure the following lines are in the _.zshrc_ file
```bash
fpath=(~/my-completions $fpath)
autoload -Uz compinit
compinit
```
* create the folder _~/my-completions_
* copy the files __sheerka.rebuild_ and __sheerka.reset_ into the folder
* restart the shell
> :warning: **Environment variable SHEERKA_BACKUP_FOLDER must be set**
+31 -13
View File
@@ -1,42 +1,60 @@
#!/bin/sh #!/bin/bash
set -e set -e
BASEDIR=$(dirname "$0") BASEDIR=$(dirname "$0")
list_available() { list_available() {
available=$(ls "$BASEDIR"/../_concepts_*.txt | awk -F_ '{ print " "$3}' ) 2> /dev/null available=$(find "$SHEERKA_BACKUP_FOLDER"/*.sb -type f -printf "%f\n" | sed 's/\.[^.]*$//') 2> /dev/null
if [ "$available" = "" ]; then if [ "$available" = "" ]; then
echo "Error. No available environment !" >&2 echo "Error. No available backup !" >&2
else else
echo "Available environments are:" echo "Available backups are:"
echo "$available" for backup in ${available}; do
echo " ${backup}";
done
fi fi
} }
if [ "$#" -eq 0 ]; then usage() {
echo "Usage: $0 <environment>" echo "Usage: $0 <backup>"
echo "Creates a fresh install of Sheerka, using the provided backup file."
list_available list_available
exit 0 exit 0
}
if [ "$#" -eq 0 ] || [ "$1" = "-h" ]; then
usage
fi fi
env_file="$BASEDIR"/../_concepts_"$1".txt
env_folder="$HOME/.sheerka_$1"
if ! [ -e "$env_file" ]; then if [ -z "${SHEERKA_BACKUP_FOLDER+x}" ]; then
echo "$env_file not found" >&2 echo "SHEERKA_BACKUP_FOLDER is not set !"
exit 1
fi
backup_file="$SHEERKA_BACKUP_FOLDER"/"$1".sb
sheerka_root_folder="$HOME/.sheerka_$1"
if ! [ -e "$backup_file" ]; then
echo "$backup_file not found" >&2
list_available list_available
exit 1 exit 1
fi fi
echo "Rebuilding $1..." echo "Rebuilding $1..."
# backup current sheerka
if [ -e ~/.sheerka ]; then if [ -e ~/.sheerka ]; then
rm -rf ~/.sheerka.bak rm -rf ~/.sheerka.bak
mv ~/.sheerka ~/.sheerka.bak mv ~/.sheerka ~/.sheerka.bak
fi fi
# re-install sheerka, using the requested backup file
python "$BASEDIR"/../main.py "sheerka.restore('$1')" python "$BASEDIR"/../main.py "sheerka.restore('$1')"
rm -rf "$env_folder"
cp -R ~/.sheerka "$env_folder" # create a copy of the freshly installed sheerka, for future reset
rm -rf "$sheerka_root_folder"
cp -R ~/.sheerka "$sheerka_root_folder"
+30 -4
View File
@@ -1,5 +1,33 @@
#!/bin/sh #!/bin/bash
list_available() {
available=$(find $HOME/.sheerka_* -maxdepth 0 -type d | awk -F_ '{ print " "$2}') 2> /dev/null
if [ "$available" = "" ]; then
echo "Error. No available environment !" >&2
else
echo "Available environment are:"
for backup in ${available}; do
echo " ${backup}";
done
fi
}
usage() {
echo "Usage: $0 [environment]"
echo "Resetting Sheerka environment from a previously build."
echo "If no environment is set, create a fresh install of Sheerka from scratch."
list_available
exit 0
}
if [ "$1" = "-h" ]; then
usage
fi
# No environment provided.
# Create a new environment from scratch (by simply removing .sheerka folder)
if [ "$#" -eq 0 ]; then if [ "$#" -eq 0 ]; then
echo "Resetting Sheerka environment." echo "Resetting Sheerka environment."
rm -rf ~/.sheerka rm -rf ~/.sheerka
@@ -8,9 +36,7 @@ fi
if ! [ -e "$HOME/.sheerka_$1" ]; then if ! [ -e "$HOME/.sheerka_$1" ]; then
echo "$HOME/.sheerka_$1 not found" >&2 echo "$HOME/.sheerka_$1 not found" >&2
available=$(ls -d $HOME/.sheerka_* | awk -F_ '{ print " "$2}') list_available
echo "Available environments are:"
echo "$available"
exit 1 exit 1
fi fi