Resolve merge conflicts

This commit is contained in:
Griatch 2020-07-18 15:56:47 +02:00
commit 862d2a5d06
9 changed files with 228 additions and 121 deletions

View file

@ -77,7 +77,9 @@ without arguments starts a full interactive Python console.
- `list_to_string` is now `iter_to_string` (but old name still works as legacy alias). It will - `list_to_string` is now `iter_to_string` (but old name still works as legacy alias). It will
now accept any input, including generators and single values. now accept any input, including generators and single values.
- EvTable should now correctly handle columns with wider asian-characters in them. - EvTable should now correctly handle columns with wider asian-characters in them.
- Update Twisted requirement to >=2.3.0 to close security vulnerability
- Add `$random` inlinefunc, supports minval,maxval arguments that can be ints and floats.
- Add `evennia.utils.inlinefuncs.raw(<str>)` as a helper to escape inlinefuncs in a string.
## Evennia 0.9 (2018-2019) ## Evennia 0.9 (2018-2019)

View file

@ -16,11 +16,13 @@ from evennia.utils.utils import (
dbref, dbref,
interactive, interactive,
list_to_string, list_to_string,
display_len,
) )
from evennia.utils.eveditor import EvEditor from evennia.utils.eveditor import EvEditor
from evennia.utils.evmore import EvMore from evennia.utils.evmore import EvMore
from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus from evennia.prototypes import spawner, prototypes as protlib, menus as olc_menus
from evennia.utils.ansi import raw from evennia.utils.ansi import raw as ansi_raw
from evennia.utils.inlinefuncs import raw as inlinefunc_raw
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -2357,26 +2359,37 @@ class CmdExamine(ObjManipCommand):
arg_regex = r"(/\w+?(\s|$))|\s|$" arg_regex = r"(/\w+?(\s|$))|\s|$"
account_mode = False account_mode = False
detail_color = "|c"
header_color = "|w"
quell_color = "|r"
separator = "-"
def list_attribute(self, crop, attr, category, value): def list_attribute(self, crop, attr, category, value):
""" """
Formats a single attribute line. Formats a single attribute line.
Args:
crop (bool): If output should be cropped if too long.
attr (str): Attribute key.
category (str): Attribute category.
value (any): Attribute value.
Returns:
""" """
if crop: if crop:
if not isinstance(value, str): if not isinstance(value, str):
value = utils.to_str(value) value = utils.to_str(value)
value = utils.crop(value) value = utils.crop(value)
value = inlinefunc_raw(ansi_raw(value))
if category: if category:
string = "\n %s[%s] = %s" % (attr, category, value) return f"{attr}[{category}] = {value}"
else: else:
string = "\n %s = %s" % (attr, value) return f"{attr} = {value}"
string = raw(string)
return string
def format_attributes(self, obj, attrname=None, crop=True): def format_attributes(self, obj, attrname=None, crop=True):
""" """
Helper function that returns info about attributes and/or Helper function that returns info about attributes and/or
non-persistent data stored on object non-persistent data stored on object
""" """
if attrname: if attrname:
@ -2391,81 +2404,108 @@ class CmdExamine(ObjManipCommand):
ndb_attr = obj.nattributes.all(return_tuples=True) ndb_attr = obj.nattributes.all(return_tuples=True)
except Exception: except Exception:
ndb_attr = None ndb_attr = None
string = "" output = {}
if db_attr and db_attr[0]: if db_attr and db_attr[0]:
string += "\n|wPersistent attributes|n:" output["Persistent attribute(s)"] = "\n " + "\n ".join(
for attr, value, category in db_attr: sorted(self.list_attribute(crop, attr, category, value)
string += self.list_attribute(crop, attr, category, value) for attr, value, category in db_attr)
)
if ndb_attr and ndb_attr[0]: if ndb_attr and ndb_attr[0]:
string += "\n|wNon-Persistent attributes|n:" output["Non-Persistent attribute(s)"] = " \n" + " \n".join(
for attr, value in ndb_attr: sorted(self.list_attribute(crop, attr, None, value)
string += self.list_attribute(crop, attr, None, value) for attr, value in ndb_attr)
return string )
return output
def format_output(self, obj, avail_cmdset): def format_output(self, obj, avail_cmdset):
""" """
Helper function that creates a nice report about an object. Helper function that creates a nice report about an object.
returns a string. Args:
obj (any): Object to analyze.
avail_cmdset (CmdSet): Current cmdset for object.
Returns:
str: The formatted string.
""" """
string = "\n|wName/key|n: |c%s|n (%s)" % (obj.name, obj.dbref) hclr = self.header_color
dclr = self.detail_color
qclr = self.quell_color
output = {}
# main key
output["Name/key"] = f"{dclr}{obj.name}|n ({obj.dbref})"
# aliases
if hasattr(obj, "aliases") and obj.aliases.all(): if hasattr(obj, "aliases") and obj.aliases.all():
string += "\n|wAliases|n: %s" % (", ".join(utils.make_iter(str(obj.aliases)))) output["Aliases"] = ", ".join(utils.make_iter(str(obj.aliases)))
# typeclass
output["Typeclass"] = f"{obj.typename} ({obj.typeclass_path})"
# sessions
if hasattr(obj, "sessions") and obj.sessions.all(): if hasattr(obj, "sessions") and obj.sessions.all():
string += "\n|wSession id(s)|n: %s" % ( output["Session id(s)"] = ", ".join(f"#{sess.sessid}" for sess in obj.sessions.all())
", ".join("#%i" % sess.sessid for sess in obj.sessions.all()) # email, if any
)
if hasattr(obj, "email") and obj.email: if hasattr(obj, "email") and obj.email:
string += "\n|wEmail|n: |c%s|n" % obj.email output["Email"] = f"{dclr}{obj.email}|n"
# account, for puppeted objects
if hasattr(obj, "has_account") and obj.has_account: if hasattr(obj, "has_account") and obj.has_account:
string += "\n|wAccount|n: |c%s|n" % obj.account.name output["Account"] = f"{dclr}{obj.account.name}|n ({obj.account.dbref})"
# account typeclass
output[" Account Typeclass"] = f"{obj.account.typename} ({obj.account.typeclass_path})"
# account permissions
perms = obj.account.permissions.all() perms = obj.account.permissions.all()
if obj.account.is_superuser: if obj.account.is_superuser:
perms = ["<Superuser>"] perms = ["<Superuser>"]
elif not perms: elif not perms:
perms = ["<None>"] perms = ["<None>"]
string += "\n|wAccount Perms|n: %s" % (", ".join(perms)) perms = ", ".join(perms)
if obj.account.attributes.has("_quell"): if obj.account.attributes.has("_quell"):
string += " |r(quelled)|n" perms += f" {qclr}(quelled)|n"
string += "\n|wTypeclass|n: %s (%s)" % (obj.typename, obj.typeclass_path) output[" Account Permissions"] = perms
# location
if hasattr(obj, "location"): if hasattr(obj, "location"):
string += "\n|wLocation|n: %s" % obj.location loc = str(obj.location)
if obj.location: if obj.location:
string += " (#%s)" % obj.location.id loc += f" (#{obj.location.id})"
output["Location"] = loc
# home
if hasattr(obj, "home"): if hasattr(obj, "home"):
string += "\n|wHome|n: %s" % obj.home home = str(obj.home)
if obj.home: if obj.home:
string += " (#%s)" % obj.home.id home += f" (#{obj.home.id})"
output["Home"] = home
# destination, for exits
if hasattr(obj, "destination") and obj.destination: if hasattr(obj, "destination") and obj.destination:
string += "\n|wDestination|n: %s" % obj.destination dest = str(obj.destination)
if obj.destination: if obj.destination:
string += " (#%s)" % obj.destination.id dest += f" (#{obj.destination.id})"
output["Destination"] = dest
# main permissions
perms = obj.permissions.all() perms = obj.permissions.all()
perms_string = ""
if perms: if perms:
perms_string = ", ".join(perms) perms_string = ", ".join(perms)
else:
perms_string = "<None>"
if obj.is_superuser: if obj.is_superuser:
perms_string += " [Superuser]" perms_string += " <Superuser>"
if perms_string:
string += "\n|wPermissions|n: %s" % perms_string output["Permissions"] = perms_string
# locks
locks = str(obj.locks) locks = str(obj.locks)
if locks: if locks:
locks_string = utils.fill("; ".join([lock for lock in locks.split(";")]), indent=6) locks_string = "\n" + utils.fill(
"; ".join([lock for lock in locks.split(";")]), indent=2)
else: else:
locks_string = " Default" locks_string = " Default"
string += "\n|wLocks|n:%s" % locks_string output["Locks"] = locks_string
# cmdsets
if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"): if not (len(obj.cmdset.all()) == 1 and obj.cmdset.current.key == "_EMPTY_CMDSET"):
# all() returns a 'stack', so make a copy to sort. # all() returns a 'stack', so make a copy to sort.
stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority, reverse=True) stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority,
string += "\n|wStored Cmdset(s)|n:\n %s" % ( reverse=True)
"\n ".join( output["Stored Cmdset(s)"] = (
"%s [%s] (%s, prio %s)" "\n " + "\n ".join(
% (cmdset.path, cmdset.key, cmdset.mergetype, cmdset.priority) f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority})"
for cmdset in stored_cmdsets for cmdset in stored_cmdsets if cmdset.key != "_EMPTY_CMDSET"
if cmdset.key != "_EMPTY_CMDSET"
) )
) )
@ -2500,40 +2540,32 @@ class CmdExamine(ObjManipCommand):
pass pass
all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()] all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()]
all_cmdsets.sort(key=lambda x: x.priority, reverse=True) all_cmdsets.sort(key=lambda x: x.priority, reverse=True)
string += "\n|wMerged Cmdset(s)|n:\n %s" % ( output["Merged Cmdset(s)"] = (
"\n ".join( "\n " + "\n ".join(
"%s [%s] (%s, prio %s)" f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype} prio {cmdset.priority})"
% (cmdset.path, cmdset.key, cmdset.mergetype, cmdset.priority)
for cmdset in all_cmdsets for cmdset in all_cmdsets
) )
) )
# list the commands available to this object # list the commands available to this object
avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")]) avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")])
cmdsetstr = utils.fill(", ".join(avail_cmdset), indent=2) cmdsetstr = "\n" + utils.fill(", ".join(avail_cmdset), indent=2)
string += "\n|wCommands available to %s (result of Merged CmdSets)|n:\n %s" % ( output[f"Commands available to {obj.key} (result of Merged CmdSets)"] = str(cmdsetstr)
obj.key, # scripts
cmdsetstr,
)
if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all(): if hasattr(obj, "scripts") and hasattr(obj.scripts, "all") and obj.scripts.all():
string += "\n|wScripts|n:\n %s" % obj.scripts output["Scripts"] = "\n " + f"{obj.scripts}"
# add the attributes # add the attributes
string += self.format_attributes(obj) output.update(self.format_attributes(obj))
# Tags
# display Tags tags = obj.tags.all(return_key_and_category=True)
tags_string = utils.fill( tags_string = "\n" + utils.fill(
", ".join( ", ".join(
"%s[%s]" % (tag, category) sorted(f"{tag}[{category}]" for tag, category in tags )),
for tag, category in obj.tags.all(return_key_and_category=True) indent=2,
),
indent=5,
) )
if tags_string: if tags:
string += "\n|wTags[category]|n: %s" % tags_string.strip() output["Tags[category]"] = tags_string
# Contents of object
# add the contents
exits = [] exits = []
pobjs = [] pobjs = []
things = [] things = []
@ -2546,24 +2578,23 @@ class CmdExamine(ObjManipCommand):
else: else:
things.append(content) things.append(content)
if exits: if exits:
string += "\n|wExits|n: %s" % ", ".join( output["Exits (has .destination)"] = ", ".join(f"{exit.name}({exit.dbref})" for exit in exits)
["%s(%s)" % (exit.name, exit.dbref) for exit in exits]
)
if pobjs: if pobjs:
string += "\n|wCharacters|n: %s" % ", ".join( output["Characters"] = ", ".join(f"{dclr}{pobj.name}|n({pobj.dbref})" for pobj in pobjs)
["|c%s|n(%s)" % (pobj.name, pobj.dbref) for pobj in pobjs]
)
if things: if things:
string += "\n|wContents|n: %s" % ", ".join( output["Contents"] = ", ".join(
[ f"{cont.name}({cont.dbref})"
"%s(%s)" % (cont.name, cont.dbref) for cont in obj.contents
for cont in obj.contents if cont not in exits and cont not in pobjs
if cont not in exits and cont not in pobjs
]
) )
separator = "-" * _DEFAULT_WIDTH # format output
# output info max_width = -1
return "%s\n%s\n%s" % (separator, string.strip(), separator) for block in output.values():
max_width = max(max_width, max(display_len(line) for line in block.split("\n")))
sep = self.separator * max_width
mainstr = "\n".join(f"{hclr}{header}|n: {block}" for (header, block) in output.items())
return f"{sep}\n{mainstr}\n{sep}"
def func(self): def func(self):
"""Process command""" """Process command"""
@ -2578,8 +2609,7 @@ class CmdExamine(ObjManipCommand):
that function finishes. Taking the resulting cmdset, we continue that function finishes. Taking the resulting cmdset, we continue
to format and output the result. to format and output the result.
""" """
string = self.format_output(obj, cmdset) self.msg(self.format_output(obj, cmdset).strip())
self.msg(string.strip())
if not self.args: if not self.args:
# If no arguments are provided, examine the invoker's location. # If no arguments are provided, examine the invoker's location.
@ -2633,7 +2663,10 @@ class CmdExamine(ObjManipCommand):
if obj_attrs: if obj_attrs:
for attrname in obj_attrs: for attrname in obj_attrs:
# we are only interested in specific attributes # we are only interested in specific attributes
caller.msg(self.format_attributes(obj, attrname, crop=False)) ret = "\n".join(
f"{self.header_color}{header}|n:{value}"
for header, value in self.format_attributes(obj, attrname, crop=False).items())
self.caller.msg(ret)
else: else:
session = None session = None
if obj.sessions.count(): if obj.sessions.count():

View file

@ -150,11 +150,17 @@ class CommandTest(EvenniaTest):
returned_msg = msg_sep.join( returned_msg = msg_sep.join(
_RE.sub("", ansi.parse_ansi(mess, strip_ansi=noansi)) for mess in stored_msg _RE.sub("", ansi.parse_ansi(mess, strip_ansi=noansi)) for mess in stored_msg
).strip() ).strip()
if msg == "" and returned_msg or not returned_msg.startswith(msg.strip()): msg = msg.strip()
if msg == "" and returned_msg or not returned_msg.startswith(msg):
prt = ""
for ic, char in enumerate(msg):
import re
prt += char
sep1 = "\n" + "=" * 30 + "Wanted message" + "=" * 34 + "\n" sep1 = "\n" + "=" * 30 + "Wanted message" + "=" * 34 + "\n"
sep2 = "\n" + "=" * 30 + "Returned message" + "=" * 32 + "\n" sep2 = "\n" + "=" * 30 + "Returned message" + "=" * 32 + "\n"
sep3 = "\n" + "=" * 78 sep3 = "\n" + "=" * 78
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3 retval = sep1 + msg + sep2 + returned_msg + sep3
raise AssertionError(retval) raise AssertionError(retval)
else: else:
returned_msg = "\n".join(str(msg) for msg in stored_msg) returned_msg = "\n".join(str(msg) for msg in stored_msg)
@ -470,10 +476,14 @@ class TestBuilding(CommandTest):
self.call(building.CmdExamine(), "*TestAccount", "Name/key: TestAccount") self.call(building.CmdExamine(), "*TestAccount", "Name/key: TestAccount")
self.char1.db.test = "testval" self.char1.db.test = "testval"
self.call(building.CmdExamine(), "self/test", "Persistent attributes:\n test = testval") self.call(building.CmdExamine(), "self/test", "Persistent attribute(s):\n test = testval")
self.call(building.CmdExamine(), "NotFound", "Could not find 'NotFound'.") self.call(building.CmdExamine(), "NotFound", "Could not find 'NotFound'.")
self.call(building.CmdExamine(), "out", "Name/key: out") self.call(building.CmdExamine(), "out", "Name/key: out")
# escape inlinefuncs
self.char1.db.test2 = "this is a $random() value."
self.call(building.CmdExamine(), "self/test2", "Persistent attribute(s):\n test2 = this is a \$random() value.")
self.room1.scripts.add(self.script.__class__) self.room1.scripts.add(self.script.__class__)
self.call(building.CmdExamine(), "") self.call(building.CmdExamine(), "")
self.account.scripts.add(self.script.__class__) self.account.scripts.add(self.script.__class__)

View file

@ -125,14 +125,14 @@ CURLY_COLOR_ANSI_EXTRA_MAP = [
(r"{c", _ANSI_HILITE + _ANSI_CYAN), (r"{c", _ANSI_HILITE + _ANSI_CYAN),
(r"{w", _ANSI_HILITE + _ANSI_WHITE), # pure white (r"{w", _ANSI_HILITE + _ANSI_WHITE), # pure white
(r"{x", _ANSI_HILITE + _ANSI_BLACK), # dark grey (r"{x", _ANSI_HILITE + _ANSI_BLACK), # dark grey
(r"{R", _ANSI_HILITE + _ANSI_RED), (r"{R", _ANSI_UNHILITE + _ANSI_RED),
(r"{G", _ANSI_HILITE + _ANSI_GREEN), (r"{G", _ANSI_UNHILITE + _ANSI_GREEN),
(r"{Y", _ANSI_HILITE + _ANSI_YELLOW), (r"{Y", _ANSI_UNHILITE + _ANSI_YELLOW),
(r"{B", _ANSI_HILITE + _ANSI_BLUE), (r"{B", _ANSI_UNHILITE + _ANSI_BLUE),
(r"{M", _ANSI_HILITE + _ANSI_MAGENTA), (r"{M", _ANSI_UNHILITE + _ANSI_MAGENTA),
(r"{C", _ANSI_HILITE + _ANSI_CYAN), (r"{C", _ANSI_UNHILITE + _ANSI_CYAN),
(r"{W", _ANSI_HILITE + _ANSI_WHITE), # light grey (r"{W", _ANSI_UNHILITE + _ANSI_WHITE), # light grey
(r"{X", _ANSI_HILITE + _ANSI_BLACK), # pure black (r"{X", _ANSI_UNHILITE + _ANSI_BLACK), # pure black
# hilight-able colors # hilight-able colors
(r"{h", _ANSI_HILITE), (r"{h", _ANSI_HILITE),
(r"{H", _ANSI_UNHILITE), (r"{H", _ANSI_UNHILITE),

View file

@ -540,7 +540,8 @@ class TBRangeTurnHandler(DefaultScript):
room as its object. room as its object.
Fights persist until only one participant is left with any HP or all Fights persist until only one participant is left with any HP or all
remaining participants choose to end the combat with the 'disengage' command. remaining participants choose to end the combat with the 'disengage'
command.
""" """
def at_script_creation(self): def at_script_creation(self):
@ -641,7 +642,6 @@ class TBRangeTurnHandler(DefaultScript):
Args: Args:
to_init (object): Object to initialize range field for. to_init (object): Object to initialize range field for.
Keyword args: Keyword args:
anchor_obj (object): Object to copy range values from, or None for a random object. anchor_obj (object): Object to copy range values from, or None for a random object.
add_distance (int): Distance to put between to_init object and anchor object. add_distance (int): Distance to put between to_init object and anchor object.

View file

@ -203,7 +203,9 @@ class SessionHandler(dict):
elif isinstance(data, (str, bytes)): elif isinstance(data, (str, bytes)):
data = _utf8(data) data = _utf8(data)
if _INLINEFUNC_ENABLED and not raw and isinstance(self, ServerSessionHandler): if (_INLINEFUNC_ENABLED
and not raw
and isinstance(self, ServerSessionHandler)):
# only parse inlinefuncs on the outgoing path (sessionhandler->) # only parse inlinefuncs on the outgoing path (sessionhandler->)
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session) data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)

View file

@ -65,6 +65,7 @@ never traceback.
import re import re
import fnmatch import fnmatch
import random as base_random
from django.conf import settings from django.conf import settings
from evennia.utils import utils, logger from evennia.utils import utils, logger
@ -72,6 +73,53 @@ from evennia.utils import utils, logger
# example/testing inline functions # example/testing inline functions
def random(*args, **kwargs):
"""
Inlinefunc. Returns a random number between
0 and 1, from 0 to a maximum value, or within a given range (inclusive).
Args:
minval (str, optional): Minimum value. If not given, assumed 0.
maxval (str, optional): Maximum value.
Keyword argumuents:
session (Session): Session getting the string.
Notes:
If either of the min/maxvalue has a '.' in it, a floating-point random
value will be returned. Otherwise it will be an integer value in the
given range.
Example:
`$random()`
`$random(5)`
`$random(5, 10)`
"""
nargs = len(args)
if nargs == 1:
# only maxval given
minval, maxval = '0', args[0]
elif nargs > 1:
minval, maxval = args[:2]
else:
minval, maxval = ('0', '1')
if "." in minval or "." in maxval:
# float mode
try:
minval, maxval = float(minval), float(maxval)
except ValueError:
minval, maxval = 0, 1
return "{:.2f}".format(minval + maxval * base_random.random())
else:
# int mode
try:
minval, maxval = int(minval), int(maxval)
except ValueError:
minval, maxval = 0, 1
return str(base_random.randint(minval, maxval))
def pad(*args, **kwargs): def pad(*args, **kwargs):
""" """
@ -82,7 +130,8 @@ def pad(*args, **kwargs):
width (str, optional): Will be converted to integer. Width width (str, optional): Will be converted to integer. Width
of padding. of padding.
align (str, optional): Alignment of padding; one of 'c', 'l' or 'r'. align (str, optional): Alignment of padding; one of 'c', 'l' or 'r'.
fillchar (str, optional): Character used for padding. Defaults to a space. fillchar (str, optional): Character used for padding. Defaults to a
space.
Keyword args: Keyword args:
session (Session): Session performing the pad. session (Session): Session performing the pad.
@ -468,6 +517,17 @@ def parse_inlinefunc(string, strip=False, available_funcs=None, stacktrace=False
return retval return retval
def raw(string):
"""
Escape all inlinefuncs in a string so they won't get parsed.
Args:
string (str): String with inlinefuncs to escape.
"""
def _escape(match):
return "\\" + match.group(0)
return _RE_STARTTOKEN.sub(_escape, string)
# #
# Nick templating # Nick templating
# #

View file

@ -2025,8 +2025,8 @@ def display_len(target):
strip MXP patterns. strip MXP patterns.
Args: Args:
target (string): A string with potential MXP components target (any): Something to measure the length of. If a string, it will be
to search. measured keeping asian-character and MXP links in mind.
Return: Return:
int: The visible width of the target. int: The visible width of the target.

View file

@ -3,7 +3,7 @@
# general # general
attrs >= 19.2.0 attrs >= 19.2.0
django >= 2.2.5, < 2.3 django >= 2.2.5, < 2.3
twisted >= 19.2.1, < 20.0.0 twisted >= 20.3.0, < 21.0.0
pytz pytz
djangorestframework >= 3.10.3, < 3.12 djangorestframework >= 3.10.3, < 3.12
django-filter >= 2.2.0, < 2.3 django-filter >= 2.2.0, < 2.3