From 982f9774294039ec2db2477433ac0eb4b97d7eeb Mon Sep 17 00:00:00 2001 From: Griatch Date: Sat, 22 Sep 2018 17:23:31 +0200 Subject: [PATCH] Continuing bug fixes --- CHANGELOG.md | 4 +-- evennia/prototypes/menus.py | 59 ++++++++++++++++++++++------------- evennia/prototypes/spawner.py | 16 ++++++++-- evennia/prototypes/tests.py | 31 ++++++++++++++++++ 4 files changed, 84 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13d13d63b3..c1ead4b367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -45,7 +45,7 @@ current node instead of failing. - Better error handling of in-node syntax errors. - Improve dedent of default text/helptext formatter. Right-strip whitespace. -- Add `debug` option when creating menu - this turns of persistence and makes the `menudebug` +- Add `debug` option when creating menu - this turns off persistence and makes the `menudebug` command available for examining the current menu state. @@ -76,7 +76,7 @@ in the system. This is used by the new `@typeclass/list` subcommand (useful for builders etc). - `evennia.utils.dbserialize.deserialize(obj)` is a new helper function to *completely* disconnect a mutable recovered from an Attribute from the database. This will convert all nested `_Saver*` - classes to their plain-Python counterparts. + classes to their plain-Python counterparts. ### General diff --git a/evennia/prototypes/menus.py b/evennia/prototypes/menus.py index 6e14993a1c..e124e4cb40 100644 --- a/evennia/prototypes/menus.py +++ b/evennia/prototypes/menus.py @@ -1944,21 +1944,26 @@ def _apply_diff(caller, **kwargs): def _keep_diff(caller, **kwargs): + """Change to KEEP setting for a given section of a diff""" + # from evennia import set_trace;set_trace(term_size=(182, 50)) path = kwargs['path'] diff = kwargs['diff'] tmp = diff for key in path[:-1]: - tmp = diff[key] - tmp[path[-1]] = "KEEP" + tmp = tmp[key] + tmp[path[-1]] = tuple(list(tmp[path[-1]][:-1]) + ["KEEP"]) -def _format_diff_text_and_options(diff): +def _format_diff_text_and_options(diff, **kwargs): """ Reformat the diff in a way suitable for the olc menu. Args: diff (dict): A diff as produced by `prototype_diff`. + Kwargs: + any (any): Forwarded into the generated options as arguments to the callable. + Returns: options (list): List of options dict. @@ -1968,49 +1973,60 @@ def _format_diff_text_and_options(diff): def _visualize(obj, rootname, get_name=False): if utils.is_iter(obj): if get_name: - return obj[0] + return obj[0] if obj[0] else "" if rootname == "attrs": return "{} |W=|n {} |W(category:|n {}|W, locks:|n {}|W)|n".format(*obj) elif rootname == "tags": return "{} |W(category:|n {}|W)|n".format(obj[0], obj[1]) return obj - def _parse_diffpart(diffpart, optnum, indent, *args): + def _parse_diffpart(diffpart, optnum, *args): typ = type(diffpart) texts = [] options = [] if typ == tuple and len(diffpart) == 3 and diffpart[2] in valid_instructions: + rootname = args[0] old, new, instruction = diffpart if instruction == 'KEEP': - texts.append("{old} |gKEEP|n".format(old=old)) + texts.append(" |gKEEP|W:|n {old}".format(old=old)) else: - texts.append("{indent}|c({num}) {inst}|W:|n {old} |W->|n {new}".format( - indent=" " * indent, - inst="|rREMOVE|n" if instruction == 'REMOVE' else "|y{}|n".format(instruction), - num=optnum, - old=_visualize(old, args[-1]), - new=_visualize(new, args[-1]))) + vold = _visualize(old, rootname) + vnew = _visualize(new, rootname) + vsep = "" if len(vold) < 78 else "\n" + vinst = "|rREMOVE|n" if instruction == 'REMOVE' else "|y{}|n".format(instruction) + texts.append(" |c[{num}] {inst}|W:|n {old} |W->|n{sep} {new}".format( + inst=vinst, num=optnum, old=vold, sep=vsep, new=vnew)) options.append({"key": str(optnum), - "desc": "|gKEEP|n {}".format( - _visualize(old, args[-1], get_name=True)), - "goto": (_keep_diff, {"path": args, "diff": diff})}) + "desc": "|gKEEP|n ({}) {}".format( + rootname, _visualize(old, args[-1], get_name=True)), + "goto": (_keep_diff, dict((("path", args), + ("diff", diff)), **kwargs))}) optnum += 1 else: for key, subdiffpart in diffpart.items(): text, option, optnum = _parse_diffpart( - subdiffpart, optnum, indent + 1, *(args + (key, ))) + subdiffpart, optnum, *(args + (key, ))) texts.extend(text) options.extend(option) - return text, options, optnum + return texts, options, optnum texts = [] options = [] # we use this to allow for skipping full KEEP instructions - flattened_diff = spawner.flatten_diff(diff) optnum = 1 - for root_key, diffpart in flattened_diff.items(): - text, option, optnum = _parse_diffpart(diffpart, optnum, 1, root_key) + for root_key in sorted(diff): + diffpart = diff[root_key] + text, option, optnum = _parse_diffpart(diffpart, optnum, root_key) + + heading = "- |w{}:|n ".format(root_key) + if root_key in ("attrs", "tags", "permissions"): + texts.append(heading) + elif text: + text = [heading + text[0]] + text[1:] + else: + text = [heading] + texts.extend(text) options.extend(option) @@ -2047,7 +2063,6 @@ def node_apply_diff(caller, **kwargs): # use one random object as a reference to calculate a diff base_obj = choice(update_objects) - # from evennia import set_trace diff, obj_prototype = spawner.prototype_diff_from_object(prototype, base_obj) helptext = """ @@ -2068,7 +2083,7 @@ def node_apply_diff(caller, **kwargs): if not custom_location: diff.pop("location", None) - txt, options = _format_diff_text_and_options(diff) + txt, options = _format_diff_text_and_options(diff, objects=update_objects, base_obj=base_obj) if options: text = ["Suggested changes to {} objects. ".format(len(update_objects)), diff --git a/evennia/prototypes/spawner.py b/evennia/prototypes/spawner.py index f46cbfcf3e..8981a015b4 100644 --- a/evennia/prototypes/spawner.py +++ b/evennia/prototypes/spawner.py @@ -299,19 +299,31 @@ def prototype_diff(prototype1, prototype2, maxdepth=2): if old_type != new_type: if old and not new: + if depth < maxdepth and old_type == dict: + return {key: (part, None, "REMOVE") for key, part in old.items()} + elif depth < maxdepth and is_iter(old): + return {part[0] if is_iter(part) else part: + (part, None, "REMOVE") for part in old} return (old, new, "REMOVE") elif not old and new: + if depth < maxdepth and new_type == dict: + return {key: (None, part, "ADD") for key, part in new.items()} + elif depth < maxdepth and is_iter(new): + return {part[0] if is_iter(part) else part: (None, part, "ADD") for part in new} return (old, new, "ADD") else: + # this condition should not occur in a standard diff return (old, new, "UPDATE") elif depth < maxdepth and new_type == dict: all_keys = set(old.keys() + new.keys()) - return {key: _recursive_diff(old.get(key), new.get(key), depth=depth + 1) for key in all_keys} + return {key: _recursive_diff(old.get(key), new.get(key), depth=depth + 1) + for key in all_keys} elif depth < maxdepth and is_iter(new): old_map = {part[0] if is_iter(part) else part: part for part in old} new_map = {part[0] if is_iter(part) else part: part for part in new} all_keys = set(old_map.keys() + new_map.keys()) - return {key: _recursive_diff(old_map.get(key), new_map.get(key), depth=depth + 1) for key in all_keys} + return {key: _recursive_diff(old_map.get(key), new_map.get(key), depth=depth + 1) + for key in all_keys} elif old != new: return (old, new, "UPDATE") else: diff --git a/evennia/prototypes/tests.py b/evennia/prototypes/tests.py index 91d81310d1..8f58f9f2a1 100644 --- a/evennia/prototypes/tests.py +++ b/evennia/prototypes/tests.py @@ -569,6 +569,37 @@ class TestMenuModule(EvenniaTest): self.assertEqual(olc_menus._prototype_load_select(caller, self.test_prot['prototype_key']), ('node_examine_entity', {'text': '|gLoaded prototype test_prot.|n', 'back': 'index'}) ) + # diff helpers + obj_diff = { + 'attrs': { + u'desc': ((u'desc', u'This is User #1.', None, ''), + (u'desc', u'This is User #1.', None, ''), + 'KEEP'), + u'foo': (None, + (u'foo', u'bar', None, ''), + 'ADD'), + u'prelogout_location': ((u'prelogout_location', "#2", None, ''), + (u'prelogout_location', "#2", None, ''), + 'KEEP')}, + 'home': ('#2', '#2', 'KEEP'), + 'key': (u'TestChar', u'TestChar', 'KEEP'), + 'locks': ('boot:false();call:false();control:perm(Developer);delete:false();' + 'edit:false();examine:perm(Developer);get:false();msg:all();' + 'puppet:false();tell:perm(Admin);view:all()', + 'boot:false();call:false();control:perm(Developer);delete:false();' + 'edit:false();examine:perm(Developer);get:false();msg:all();' + 'puppet:false();tell:perm(Admin);view:all()', + 'KEEP'), + 'permissions': {'developer': ('developer', 'developer', 'KEEP')}, + 'prototype_desc': ('Testobject build', None, 'REMOVE'), + 'prototype_key': ('TestDiffKey', 'TestDiffKey', 'KEEP'), + 'prototype_locks': ('spawn:all();edit:all()', 'spawn:all();edit:all()', 'KEEP'), + 'prototype_tags': {}, + 'tags': {'foo': (None, ('foo', None, ''), 'ADD')}, + 'typeclass': (u'typeclasses.characters.Character', + u'typeclasses.characters.Character', 'KEEP')} + self.assertEqual(olc_menus._format_diff_text_and_options(obj_diff), "") + @mock.patch("evennia.prototypes.menus.protlib.search_prototype", new=mock.MagicMock( return_value=[{"prototype_key": "TestPrototype",