diff --git a/.gitignore b/.gitignore index c38cba9..4819659 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ tests/_concepts.txt tests/**/*result_test testingPython.ipynb profile*.txt -*.prof \ No newline at end of file +*.prof +new.sb \ No newline at end of file diff --git a/_concepts_adjectives.txt b/sheerka_backup/adjectives.sb similarity index 100% rename from _concepts_adjectives.txt rename to sheerka_backup/adjectives.sb diff --git a/_concepts_admin.txt b/sheerka_backup/admin.sb similarity index 100% rename from _concepts_admin.txt rename to sheerka_backup/admin.sb diff --git a/_concepts_default.txt b/sheerka_backup/default.sb similarity index 85% rename from _concepts_default.txt rename to sheerka_backup/default.sb index 5bc717f..4699f02 100644 --- a/_concepts_default.txt +++ b/sheerka_backup/default.sb @@ -4,6 +4,13 @@ def concept q from q ? as question(q) pre is_question() set_is_lesser(__PRECEDENCE, q, 'Sya') 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() # 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_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 def concept male @@ -67,4 +72,6 @@ def concept friday def concept saturday 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) diff --git a/_concepts_full.txt b/sheerka_backup/full.sb similarity index 100% rename from _concepts_full.txt rename to sheerka_backup/full.sb diff --git a/_concepts_lite.txt b/sheerka_backup/lite.sb similarity index 100% rename from _concepts_lite.txt rename to sheerka_backup/lite.sb diff --git a/_concepts_numbers.txt b/sheerka_backup/numbers.sb similarity index 100% rename from _concepts_numbers.txt rename to sheerka_backup/numbers.sb diff --git a/_concepts_python.txt b/sheerka_backup/python.sb similarity index 100% rename from _concepts_python.txt rename to sheerka_backup/python.sb diff --git a/_concepts_test-adjective.txt b/sheerka_backup/test-adjective.sb similarity index 100% rename from _concepts_test-adjective.txt rename to sheerka_backup/test-adjective.sb diff --git a/src/cache/CacheManager.py b/src/cache/CacheManager.py index 2f38fac..276f1d7 100644 --- a/src/cache/CacheManager.py +++ b/src/cache/CacheManager.py @@ -112,7 +112,6 @@ class CacheManager: old_key = cache_def.get_key(old) new_key = cache_def.get_key(new) - cache_def.cache.update(old_key, old, new_key, new, alt_sdp=alt_sdp) self.is_dirty = True diff --git a/src/core/builtin_concepts.py b/src/core/builtin_concepts.py index 395a53f..2109aef 100644 --- a/src/core/builtin_concepts.py +++ b/src/core/builtin_concepts.py @@ -1,6 +1,7 @@ from core.builtin_concepts_ids import BuiltinConcepts from core.concept import Concept, ConceptParts from core.global_symbols import ErrorObj +from core.utils import compute_hash class UserInputConcept(Concept): @@ -91,12 +92,7 @@ class ReturnValueConcept(Concept): self.value == other.value def __hash__(self): - if hasattr(self.value, "__iter__") and not isinstance(self.value, str): - value_hash = hash(tuple(self.value)) - else: - value_hash = hash(self.value) - - return hash((self.who, self.status, value_hash)) + return hash((self.who, self.status, compute_hash(self.value))) class UnknownPropertyConcept(Concept, ErrorObj): diff --git a/src/core/concept.py b/src/core/concept.py index 51b77fd..78ddae5 100644 --- a/src/core/concept.py +++ b/src/core/concept.py @@ -2,6 +2,7 @@ import hashlib from copy import deepcopy from dataclasses import dataclass from threading import RLock +from typing import List import core.utils 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() 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 + all_attributes: List[str] = None # list of instance attributes ALL_ATTRIBUTES = {} @@ -157,7 +159,6 @@ class Concept: self._original_definition_hash = None # concept hash before any alteration of the metadata self._format = None # how to print the concept self._hints = {} # extra processing information to help processing - self._all_attributes = None # instance attributes def __repr__(self): text = f"({self._metadata.id}){self._metadata.name}" @@ -209,7 +210,7 @@ class Concept: return hash(self._metadata.name) def get_all_attributes(self): - return self._all_attributes + return self._metadata.all_attributes 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 # Need to find a better way if name not in get_concept_attrs(self): - if self._all_attributes is None: - self._all_attributes = get_concept_attrs(self).copy() - self._all_attributes.append(name) + if self._metadata.all_attributes is None: + self._metadata.all_attributes = get_concept_attrs(self).copy() + self._metadata.all_attributes.append(name) except AttributeError: print(f"Cannot set {name}") diff --git a/src/core/global_symbols.py b/src/core/global_symbols.py index d05d8dc..c401799 100644 --- a/src/core/global_symbols.py +++ b/src/core/global_symbols.py @@ -6,6 +6,7 @@ EVENT_RULE_PRECEDENCE_MODIFIED = "evt_rp_m" EVENT_CONTEXT_DISPOSED = "evt_ctx_d" EVENT_USER_INPUT_EVALUATED = "evt_ui_e" EVENT_CONCEPT_CREATED = "evt_c_c" +EVENT_CONCEPT_MODIFIED = "evt_c_m" EVENT_CONCEPT_DELETED = "evt_c_d" EVENT_CONCEPT_ID_DELETED = "evt_c_id_d" EVENT_RULE_CREATED = "evt_r_c" @@ -79,3 +80,7 @@ class SyaAssociativity(Enum): def __repr__(self): return self.value + + +SHEERKA_BACKUP_FOLDER = "SHEERKA_BACKUP_FOLDER" +SHEERKA_BACKUP_FILE = "SHEERKA_BACKUP_FILE" diff --git a/src/core/sheerka/ExecutionContext.py b/src/core/sheerka/ExecutionContext.py index 2a35027..352e227 100644 --- a/src/core/sheerka/ExecutionContext.py +++ b/src/core/sheerka/ExecutionContext.py @@ -497,3 +497,17 @@ class ExecutionContext: 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 diff --git a/src/core/sheerka/Sheerka.py b/src/core/sheerka/Sheerka.py index fa24db1..b6abb77 100644 --- a/src/core/sheerka/Sheerka.py +++ b/src/core/sheerka/Sheerka.py @@ -106,6 +106,7 @@ class Sheerka(Concept): self.save_execution_context = True self.enable_process_return_values = 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.sheerka_methods = { @@ -158,8 +159,6 @@ class Sheerka(Concept): Loads the current configuration Notes that when it's the first time, it also create the needed working folders :param root_folder: root configuration folder - :param save_execution_context: - :param enable_process_return_values: :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) 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: self.during_initialisation = True @@ -370,7 +370,7 @@ class Sheerka(Concept): if self.enable_process_rules: 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.publish(execution_context, EVENT_USER_INPUT_EVALUATED) diff --git a/src/core/sheerka/services/SheerkaAdmin.py b/src/core/sheerka/services/SheerkaAdmin.py index fd90a4c..8b7646f 100644 --- a/src/core/sheerka/services/SheerkaAdmin.py +++ b/src/core/sheerka/services/SheerkaAdmin.py @@ -1,16 +1,16 @@ -import sys +import os import time from os import path from core.builtin_concepts_ids import BuiltinConcepts, BuiltinContainers from core.builtin_helpers import ensure_concept_or_rule from core.concept import Concept +from core.global_symbols import SHEERKA_BACKUP_FOLDER from core.sheerka.services.SheerkaHistoryManager import SheerkaHistoryManager from core.sheerka.services.SheerkaMemory import SheerkaMemory from core.sheerka.services.sheerka_service import BaseService -CONCEPTS_FILE_LITE = "_concepts_lite.txt" -CONCEPTS_FILE_FULL = "_concepts_full.txt" +CONCEPTS_FILE_FULL = "full.sb" CONCEPTS_FILE_TO_USE = CONCEPTS_FILE_FULL @@ -70,7 +70,7 @@ class SheerkaAdmin(BaseService): 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 :return: @@ -79,7 +79,7 @@ class SheerkaAdmin(BaseService): def restore_from_file(file_name): _nb_lines, _nb_instructions, _nb_lines_in_error = 0, 0, 0 _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): print(f"\u001b[31mFile '{file_path}' is not found !\u001b[0m") return 0, 0, 1 @@ -90,7 +90,7 @@ class SheerkaAdmin(BaseService): line = line.strip() if line.startswith("#import "): - to_import = "_concepts_" + line[8:] + ".txt" + to_import = line[8:] + ".sb" print(f" ==== Importing {to_import} ==== ") res = restore_from_file(to_import) _nb_lines += res[0] @@ -122,8 +122,12 @@ class SheerkaAdmin(BaseService): return _nb_lines, _nb_instructions, _nb_lines_in_error, _min_time, _max_time - if not concept_file.startswith("_concepts"): - concept_file = f"_concepts_{concept_file}.txt" + backup_folder = os.getenv(SHEERKA_BACKUP_FOLDER) + 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: start = time.time_ns() @@ -132,7 +136,7 @@ class SheerkaAdmin(BaseService): enable_process_return_values_previous_value = self.sheerka.enable_process_return_values 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.save_execution_context = True diff --git a/src/core/sheerka/services/SheerkaConceptManager.py b/src/core/sheerka/services/SheerkaConceptManager.py index 057deb4..5e07704 100644 --- a/src/core/sheerka/services/SheerkaConceptManager.py +++ b/src/core/sheerka/services/SheerkaConceptManager.py @@ -13,7 +13,8 @@ from core.builtin_concepts_ids import BuiltinConcepts, AllBuiltinConcepts, Built from core.builtin_helpers import ensure_concept, ensure_bnf from core.concept import Concept, DEFINITION_TYPE_DEF, DEFINITION_TYPE_BNF, freeze_concept_attrs, ConceptMetadata, \ 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.tokenizer import Tokenizer, TokenKind 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_attr, True) 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.get_property, False, as_name="get_prop") self.sheerka.bind_service_method(self.get_by_key, False, visible=False) @@ -346,7 +348,7 @@ class SheerkaConceptManager(BaseService): if modify_source: 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)) def remove_concept(self, context, concept): @@ -390,7 +392,6 @@ class SheerkaConceptManager(BaseService): def set_attr(self, concept, attribute, value): """ Modifies an attribute of a concept (concept.values) - :param context: :param concept: :param attribute: :param value: @@ -416,7 +417,6 @@ class SheerkaConceptManager(BaseService): def get_attr(self, concept, attribute): """ Returns the attribute of a concept - :param context: :param concept: :param attribute: :return: @@ -431,6 +431,72 @@ class SheerkaConceptManager(BaseService): return self.sheerka.new(BuiltinConcepts.NOT_FOUND, body={"#concept": concept, "#attr": attribute}) 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): """ Returns the value of a concept property diff --git a/src/core/sheerka/services/SheerkaDebugManager.py b/src/core/sheerka/services/SheerkaDebugManager.py index 42adf77..a6bef45 100644 --- a/src/core/sheerka/services/SheerkaDebugManager.py +++ b/src/core/sheerka/services/SheerkaDebugManager.py @@ -322,6 +322,7 @@ class SheerkaDebugManager(BaseService): def initialize(self): self.sheerka.bind_service_method(self.set_debug, True) 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.reset_debug, False) 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) + 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 def get_debug_repr(obj, **kwargs): if kwargs.get("as_bag", False): @@ -897,3 +918,4 @@ class SheerkaDebugManager(BaseService): del res["self"] return res + diff --git a/src/core/sheerka/services/SheerkaHasAManager.py b/src/core/sheerka/services/SheerkaHasAManager.py index 16d29e9..914533e 100644 --- a/src/core/sheerka/services/SheerkaHasAManager.py +++ b/src/core/sheerka/services/SheerkaHasAManager.py @@ -31,9 +31,9 @@ class SheerkaHasAManager(BaseService): return self.sheerka.ret( self.NAME, False, - self.sheerka.new(BuiltinConcepts.PropertyAlreadyDefined, - body=concept_b, - name=BuiltinConcepts.HASA, + self.sheerka.new(BuiltinConcepts.PROPERTY_ALREADY_DEFINED, + property_value=concept_b, + property_name=BuiltinConcepts.HASA, concept=concept_a)) merged_concepts = merge_sets(concept_a.get_prop(BuiltinConcepts.HASA), {concept_b}) diff --git a/src/core/sheerka/services/SheerkaHistoryManager.py b/src/core/sheerka/services/SheerkaHistoryManager.py index c9d05fb..65bf34e 100644 --- a/src/core/sheerka/services/SheerkaHistoryManager.py +++ b/src/core/sheerka/services/SheerkaHistoryManager.py @@ -1,5 +1,6 @@ from collections import namedtuple +from core.global_symbols import NotInit from core.sheerka.services.sheerka_service import BaseService from sdp.sheerkaDataProvider import Event @@ -7,14 +8,22 @@ hist = namedtuple("HistoryTest", "text status") # tests purposes only class History: - def __init__(self, event: Event, result): + def __init__(self, event: Event, result, extra_info): self.event = event self.result = result - self._status = None + self.extra_info = extra_info + self._status = NotInit + self._is_state_modified = NotInit self._format_instructions = None 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 if status is not None: msg += f" => {status}" @@ -40,12 +49,28 @@ class History: @property 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 self._status = self.result.get_status() if self.result else None 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): return self._format_instructions @@ -73,4 +98,10 @@ class SheerkaHistoryManager(BaseService): result = self.sheerka.om.current_sdp().load_result(event.get_digest()) except (IOError, KeyError): 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) diff --git a/src/core/sheerka/services/SheerkaResultManager.py b/src/core/sheerka/services/SheerkaResultManager.py index 0123dd2..df8b4ab 100644 --- a/src/core/sheerka/services/SheerkaResultManager.py +++ b/src/core/sheerka/services/SheerkaResultManager.py @@ -1,8 +1,11 @@ import ast +import os from cache.Cache import Cache 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.utils import CONSOLE_COLORS_MAP as CCM 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_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): self.restore_values(*self.state_vars) @@ -59,6 +70,11 @@ class SheerkaResultManager(BaseService): @staticmethod def get_predicate(**kwargs): + """ + Create a filtering predicate from a arguments + :param kwargs: + :return: + """ if len(kwargs) == 0: return None res = [] @@ -241,13 +257,25 @@ class SheerkaResultManager(BaseService): :param execution_context: :return: """ + extra_info = { + "status": execution_context.get_status(), + "is_state_modified": execution_context.is_state_modified(), + } if self.sheerka.save_execution_context: try: self.sheerka.om.current_sdp().save_result(execution_context) except Exception as ex: 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 - # 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.last_execution = execution_context @@ -298,11 +326,21 @@ class SheerkaResultManager(BaseService): return self.last_errors 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_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): if self.last_created_concept: return self.last_created_concept @@ -333,3 +371,24 @@ class SheerkaResultManager(BaseService): consumed = 0 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") + diff --git a/src/core/sheerka/services/SheerkaVariableManager.py b/src/core/sheerka/services/SheerkaVariableManager.py index e1ca65b..7e14cde 100644 --- a/src/core/sheerka/services/SheerkaVariableManager.py +++ b/src/core/sheerka/services/SheerkaVariableManager.py @@ -42,8 +42,14 @@ class SheerkaVariableManager(BaseService): def __init__(self, sheerka): super().__init__(sheerka, order=3) + + # Bound variables: + # automatically set services (including Sheerka) attributes at startup or at ontology creation / deletion 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): @@ -88,6 +94,7 @@ class SheerkaVariableManager(BaseService): 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] setattr(service, key, value) + print(f"{service=} is set") def load_var(self, who, key): variable = self.sheerka.om.get(self.VARIABLES_ENTRY, who + "|" + key) diff --git a/src/core/utils.py b/src/core/utils.py index d52b787..90e439b 100644 --- a/src/core/utils.py +++ b/src/core/utils.py @@ -777,6 +777,14 @@ def escape_str(x): return x +def new_array(size): + res = [] + for _ in range(size): + res.append(0) + + return res + + class NextIdManager: """ solely return the next integer @@ -788,3 +796,16 @@ class NextIdManager: def get_next_id(self): self.id += 1 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) diff --git a/src/evaluators/PythonEvaluator.py b/src/evaluators/PythonEvaluator.py index acf640f..36c6ac2 100644 --- a/src/evaluators/PythonEvaluator.py +++ b/src/evaluators/PythonEvaluator.py @@ -53,6 +53,7 @@ class Expando: @dataclass class PythonEvalError: error: Exception + source: str traceback: str = field(repr=False) concepts: dict = field(repr=False) @@ -64,6 +65,7 @@ class PythonEvalError: return False return isinstance(self.error, type(other.error)) and \ + self.source == other.source and \ self.traceback == other.traceback and \ self.concepts == other.concepts @@ -151,10 +153,12 @@ class PythonEvaluator(OneReturnValueEvaluator): if concepts_entries is None: concepts_entries = self.get_concepts_entries_from_globals(my_globals) eval_error = PythonEvalError(ex, + node.source, 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("source", eval_error.source, is_error=True) exception_debugger.debug_var("trace", eval_error.traceback, is_error=True) if evaluated == NotInit: diff --git a/src/repl/SheerkaPromptCompleter.py b/src/repl/SheerkaPromptCompleter.py index bc0b51e..b0492b8 100644 --- a/src/repl/SheerkaPromptCompleter.py +++ b/src/repl/SheerkaPromptCompleter.py @@ -4,10 +4,13 @@ import inspect import re 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.services.SheerkaConceptManager import SheerkaConceptManager from core.sheerka.services.SheerkaFunctionsParametersHistory import SheerkaFunctionsParametersHistory 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_]') @@ -24,9 +27,119 @@ class FuncFound: @dataclass class CompletionDesc: - text: str - display: str - meta_display: str + text: str # what to autocomplete + display: str # what to display in the list of proposals + 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): @@ -36,29 +149,40 @@ class SheerkaPromptCompleter(Completer): self.params_history_service = self.sheerka.services[SheerkaFunctionsParametersHistory.NAME] self.builtins = [] 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.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): - text = document.text_before_cursor + pos = document.cursor_position - 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 + last_token = self.get_last_token(text) + if self.last_token_index is None or last_token and last_token.index != self.last_token_index: + self.last_token_index = last_token.index if last_token else 0 + # new word or new symbol detected + self.update_concepts_candidates(last_token, pos) - 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.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): script = self.get_jedi_script_from_document(document, self.globals, self.globals) @@ -109,6 +233,39 @@ class SheerkaPromptCompleter(Completer): 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 def yield_completion_from_completion_desc(definitions, text): for completion_desc in definitions: @@ -123,8 +280,35 @@ class SheerkaPromptCompleter(Completer): display=completion_desc.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 - 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 + "(" signature = inspect.signature(function) @@ -166,19 +350,21 @@ class SheerkaPromptCompleter(Completer): return None @staticmethod - def last_word(text, pos, left_strip=True): - if pos == 0: + def get_last_word(text): + if text is None or text.strip() == "": return "" - start = pos - 1 if text[pos - 1] == " " else pos - if start < 0: - return "" + return list(Tokenizer(text, yield_eof=False))[-1].str_value.strip() - for i in range(start)[::-1]: - if text[i] == " ": - return text[i:pos].lstrip() if left_strip else text[i:pos] + @staticmethod + def get_last_token(text): + 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 def get_param_number(text): @@ -234,70 +420,3 @@ class SheerkaPromptCompleter(Completer): except Exception: # Workaround for: https://github.com/jonathanslenders/ptpython/issues/91 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 diff --git a/src/sdp/sheerkaDataProvider.py b/src/sdp/sheerkaDataProvider.py index 9f25c13..05fa215 100644 --- a/src/sdp/sheerkaDataProvider.py +++ b/src/sdp/sheerkaDataProvider.py @@ -437,6 +437,10 @@ class SheerkaDataProvider: ext = "_admin_result" if is_admin else "_result" 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): """ 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}") 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): """ Load and deserialize a result file @@ -484,6 +505,20 @@ class SheerkaDataProvider: context = SerializerContext(sheerka=self.sheerka) 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): """ Make sure the real obj is returned diff --git a/tests/TestUsingMemoryBasedSheerka.py b/tests/TestUsingMemoryBasedSheerka.py index 86eefa0..8c9487b 100644 --- a/tests/TestUsingMemoryBasedSheerka.py +++ b/tests/TestUsingMemoryBasedSheerka.py @@ -25,7 +25,8 @@ class TestUsingMemoryBasedSheerka(BaseTest): sheerka.initialize("mem://", save_execution_context=False, enable_process_return_values=False, - enable_process_rules=False) + enable_process_rules=False, + enable_commands_backup=False) return sheerka def get_sheerka(self, **kwargs) -> Sheerka: diff --git a/tests/core/test_SheerkaConceptManager.py b/tests/core/test_SheerkaConceptManager.py index 6bbcdc2..da9ba4a 100644 --- a/tests/core/test_SheerkaConceptManager.py +++ b/tests/core/test_SheerkaConceptManager.py @@ -840,7 +840,8 @@ class TestSheerkaConceptManager(TestUsingMemoryBasedSheerka): assert sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) 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 sheerka.isinstance(res.body, BuiltinConcepts.SUCCESS) 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(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): def test_i_can_add_several_concepts(self): diff --git a/tests/core/test_SheerkaHasAManager.py b/tests/core/test_SheerkaHasAManager.py index f8d62dc..8a31352 100644 --- a/tests/core/test_SheerkaHasAManager.py +++ b/tests/core/test_SheerkaHasAManager.py @@ -15,3 +15,15 @@ class TestSheerkaHasAManager(TestUsingMemoryBasedSheerka): # check that the definition of the concept has been updated 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") diff --git a/tests/core/test_sheerkaResultManager.py b/tests/core/test_sheerkaResultManager.py index 464f311..dfc9a49 100644 --- a/tests/core/test_sheerkaResultManager.py +++ b/tests/core/test_sheerkaResultManager.py @@ -3,6 +3,10 @@ import types import pytest 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.SheerkaOntologyManager import SheerkaOntologyManager from core.sheerka.services.SheerkaResultManager import SheerkaResultManager @@ -400,6 +404,51 @@ class TestSheerkaResultManager(TestUsingMemoryBasedSheerka): assert service.last_error_event_id is None 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): @classmethod diff --git a/tests/core/test_utils.py b/tests/core/test_utils.py index 1e91b24..e8a3f5d 100644 --- a/tests/core/test_utils.py +++ b/tests/core/test_utils.py @@ -430,6 +430,19 @@ def test_i_can_deep_copy_a_custom_type(): 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", [ ("foo bar baz", "foo bar baz", True), ("foo()", " foo ( ) ", True), diff --git a/tests/repl/test_SheerkaPromptCompleter.py b/tests/repl/test_SheerkaPromptCompleter.py index 40e358c..2d04490 100644 --- a/tests/repl/test_SheerkaPromptCompleter.py +++ b/tests/repl/test_SheerkaPromptCompleter.py @@ -1,9 +1,10 @@ import pytest -from core.sheerka.services.SheerkaFunctionsParametersHistory import SheerkaFunctionsParametersHistory from prompt_toolkit.completion import CompleteEvent 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 @@ -38,41 +39,180 @@ class TestSheerkaPromptCompleter(TestUsingMemoryBasedSheerka): assert as_dict["quit"].display_text == "quit" assert as_dict["quit"].display_meta_text == "command" - @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'"]), + @pytest.mark.parametrize("concept_name", [ + "foo", + "a long name", ]) - 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'") + def test_i_can_complete_concept(self, concept_name): + sheerka, context, foo = self.init_concepts(Concept(concept_name), create_new=True) + completer = SheerkaPromptCompleter(sheerka) + completer.complete_commands = False + completer.complete_builtins = False - document = Document(text) - completions = SheerkaPromptCompleter(sheerka).get_completions(document, CompleteEvent()) - as_list = [c.display_text for c in completions] - assert as_list == expected + for i in range(1, len(concept_name)): + before_cursor = concept_name[:i] + completions = list(completer.get_completions(Document(before_cursor), CompleteEvent())) + 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", [ - ("", 0, ""), - ("foo", 3, "foo"), - ("foo ", 4, "foo "), - ("foo", 2, "fo"), - ("foo bar", 7, "bar"), - ("foo bar", 4, "foo "), - ]) - def test_last_word(self, text, pos, expected): - assert SheerkaPromptCompleter.last_word(text, pos) == expected + def test_i_can_complete_sya_concept_with_variable(self): + sheerka, context, foo, something, bar = self.init_concepts( + Concept("if true x then do y else do z end").def_var("x").def_var("y").def_var("z"), + Concept("something"), + Concept("bar"), + create_new=True) + completer = SheerkaPromptCompleter(sheerka) + completer.complete_commands = False + completer.complete_builtins = False + + 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", [ ("", 0, None), @@ -104,6 +244,27 @@ class TestSheerkaPromptCompleter(TestUsingMemoryBasedSheerka): 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) + @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): # sheerka = self.get_sheerka() # diff --git a/utils/_sheerka.rebuild b/utils/_sheerka.rebuild new file mode 100644 index 0000000..2fc064c --- /dev/null +++ b/utils/_sheerka.rebuild @@ -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/\.[^.]*$//"))' \ No newline at end of file diff --git a/utils/_sheerka.reset b/utils/_sheerka.reset new file mode 100644 index 0000000..72cdd18 --- /dev/null +++ b/utils/_sheerka.reset @@ -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}'))" \ No newline at end of file diff --git a/utils/auto_completion.md b/utils/auto_completion.md new file mode 100644 index 0000000..2cebfbe --- /dev/null +++ b/utils/auto_completion.md @@ -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** \ No newline at end of file diff --git a/utils/sheerka.rebuild.sh b/utils/sheerka.rebuild.sh index e50f621..13c568b 100755 --- a/utils/sheerka.rebuild.sh +++ b/utils/sheerka.rebuild.sh @@ -1,42 +1,60 @@ -#!/bin/sh +#!/bin/bash set -e BASEDIR=$(dirname "$0") 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 - echo "Error. No available environment !" >&2 + echo "Error. No available backup !" >&2 else - echo "Available environments are:" - echo "$available" + echo "Available backups are:" + for backup in ${available}; do + echo " ${backup}"; + done fi } -if [ "$#" -eq 0 ]; then - echo "Usage: $0 " +usage() { + echo "Usage: $0 " + echo "Creates a fresh install of Sheerka, using the provided backup file." list_available exit 0 +} + +if [ "$#" -eq 0 ] || [ "$1" = "-h" ]; then + usage fi -env_file="$BASEDIR"/../_concepts_"$1".txt -env_folder="$HOME/.sheerka_$1" -if ! [ -e "$env_file" ]; then - echo "$env_file not found" >&2 +if [ -z "${SHEERKA_BACKUP_FOLDER+x}" ]; then + 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 exit 1 fi echo "Rebuilding $1..." +# backup current sheerka if [ -e ~/.sheerka ]; then rm -rf ~/.sheerka.bak mv ~/.sheerka ~/.sheerka.bak fi +# re-install sheerka, using the requested backup file 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" diff --git a/utils/sheerka.reset.sh b/utils/sheerka.reset.sh index 9c24059..3bc9ad4 100755 --- a/utils/sheerka.reset.sh +++ b/utils/sheerka.reset.sh @@ -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 echo "Resetting Sheerka environment." rm -rf ~/.sheerka @@ -8,9 +36,7 @@ fi if ! [ -e "$HOME/.sheerka_$1" ]; then echo "$HOME/.sheerka_$1 not found" >&2 - available=$(ls -d $HOME/.sheerka_* | awk -F_ '{ print " "$2}') - echo "Available environments are:" - echo "$available" + list_available exit 1 fi