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
now accept any input, including generators and single values.
- 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)

View file

@ -16,11 +16,13 @@ from evennia.utils.utils import (
dbref,
interactive,
list_to_string,
display_len,
)
from evennia.utils.eveditor import EvEditor
from evennia.utils.evmore import EvMore
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)
@ -2357,26 +2359,37 @@ class CmdExamine(ObjManipCommand):
arg_regex = r"(/\w+?(\s|$))|\s|$"
account_mode = False
detail_color = "|c"
header_color = "|w"
quell_color = "|r"
separator = "-"
def list_attribute(self, crop, attr, category, value):
"""
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 not isinstance(value, str):
value = utils.to_str(value)
value = utils.crop(value)
value = inlinefunc_raw(ansi_raw(value))
if category:
string = "\n %s[%s] = %s" % (attr, category, value)
return f"{attr}[{category}] = {value}"
else:
string = "\n %s = %s" % (attr, value)
string = raw(string)
return string
return f"{attr} = {value}"
def format_attributes(self, obj, attrname=None, crop=True):
"""
Helper function that returns info about attributes and/or
non-persistent data stored on object
"""
if attrname:
@ -2391,81 +2404,108 @@ class CmdExamine(ObjManipCommand):
ndb_attr = obj.nattributes.all(return_tuples=True)
except Exception:
ndb_attr = None
string = ""
output = {}
if db_attr and db_attr[0]:
string += "\n|wPersistent attributes|n:"
for attr, value, category in db_attr:
string += self.list_attribute(crop, attr, category, value)
output["Persistent attribute(s)"] = "\n " + "\n ".join(
sorted(self.list_attribute(crop, attr, category, value)
for attr, value, category in db_attr)
)
if ndb_attr and ndb_attr[0]:
string += "\n|wNon-Persistent attributes|n:"
for attr, value in ndb_attr:
string += self.list_attribute(crop, attr, None, value)
return string
output["Non-Persistent attribute(s)"] = " \n" + " \n".join(
sorted(self.list_attribute(crop, attr, None, value)
for attr, value in ndb_attr)
)
return output
def format_output(self, obj, avail_cmdset):
"""
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():
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():
string += "\n|wSession id(s)|n: %s" % (
", ".join("#%i" % sess.sessid for sess in obj.sessions.all())
)
output["Session id(s)"] = ", ".join(f"#{sess.sessid}" for sess in obj.sessions.all())
# email, if any
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:
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()
if obj.account.is_superuser:
perms = ["<Superuser>"]
elif not perms:
perms = ["<None>"]
string += "\n|wAccount Perms|n: %s" % (", ".join(perms))
perms = ", ".join(perms)
if obj.account.attributes.has("_quell"):
string += " |r(quelled)|n"
string += "\n|wTypeclass|n: %s (%s)" % (obj.typename, obj.typeclass_path)
perms += f" {qclr}(quelled)|n"
output[" Account Permissions"] = perms
# location
if hasattr(obj, "location"):
string += "\n|wLocation|n: %s" % obj.location
loc = str(obj.location)
if obj.location:
string += " (#%s)" % obj.location.id
loc += f" (#{obj.location.id})"
output["Location"] = loc
# home
if hasattr(obj, "home"):
string += "\n|wHome|n: %s" % obj.home
home = str(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:
string += "\n|wDestination|n: %s" % obj.destination
dest = str(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_string = ""
if perms:
perms_string = ", ".join(perms)
else:
perms_string = "<None>"
if obj.is_superuser:
perms_string += " [Superuser]"
string += "\n|wPermissions|n: %s" % perms_string
perms_string += " <Superuser>"
if perms_string:
output["Permissions"] = perms_string
# locks
locks = str(obj.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:
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"):
# all() returns a 'stack', so make a copy to sort.
stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority, reverse=True)
string += "\n|wStored Cmdset(s)|n:\n %s" % (
"\n ".join(
"%s [%s] (%s, prio %s)"
% (cmdset.path, cmdset.key, cmdset.mergetype, cmdset.priority)
for cmdset in stored_cmdsets
if cmdset.key != "_EMPTY_CMDSET"
stored_cmdsets = sorted(obj.cmdset.all(), key=lambda x: x.priority,
reverse=True)
output["Stored Cmdset(s)"] = (
"\n " + "\n ".join(
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype}, prio {cmdset.priority})"
for cmdset in stored_cmdsets if cmdset.key != "_EMPTY_CMDSET"
)
)
@ -2500,40 +2540,32 @@ class CmdExamine(ObjManipCommand):
pass
all_cmdsets = [cmdset for cmdset in dict(all_cmdsets).values()]
all_cmdsets.sort(key=lambda x: x.priority, reverse=True)
string += "\n|wMerged Cmdset(s)|n:\n %s" % (
"\n ".join(
"%s [%s] (%s, prio %s)"
% (cmdset.path, cmdset.key, cmdset.mergetype, cmdset.priority)
output["Merged Cmdset(s)"] = (
"\n " + "\n ".join(
f"{cmdset.path} [{cmdset.key}] ({cmdset.mergetype} prio {cmdset.priority})"
for cmdset in all_cmdsets
)
)
# list the commands available to this object
avail_cmdset = sorted([cmd.key for cmd in avail_cmdset if cmd.access(obj, "cmd")])
cmdsetstr = utils.fill(", ".join(avail_cmdset), indent=2)
string += "\n|wCommands available to %s (result of Merged CmdSets)|n:\n %s" % (
obj.key,
cmdsetstr,
)
cmdsetstr = "\n" + utils.fill(", ".join(avail_cmdset), indent=2)
output[f"Commands available to {obj.key} (result of Merged CmdSets)"] = str(cmdsetstr)
# scripts
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
string += self.format_attributes(obj)
# display Tags
tags_string = utils.fill(
output.update(self.format_attributes(obj))
# Tags
tags = obj.tags.all(return_key_and_category=True)
tags_string = "\n" + utils.fill(
", ".join(
"%s[%s]" % (tag, category)
for tag, category in obj.tags.all(return_key_and_category=True)
),
indent=5,
sorted(f"{tag}[{category}]" for tag, category in tags )),
indent=2,
)
if tags_string:
string += "\n|wTags[category]|n: %s" % tags_string.strip()
# add the contents
if tags:
output["Tags[category]"] = tags_string
# Contents of object
exits = []
pobjs = []
things = []
@ -2546,24 +2578,23 @@ class CmdExamine(ObjManipCommand):
else:
things.append(content)
if exits:
string += "\n|wExits|n: %s" % ", ".join(
["%s(%s)" % (exit.name, exit.dbref) for exit in exits]
)
output["Exits (has .destination)"] = ", ".join(f"{exit.name}({exit.dbref})" for exit in exits)
if pobjs:
string += "\n|wCharacters|n: %s" % ", ".join(
["|c%s|n(%s)" % (pobj.name, pobj.dbref) for pobj in pobjs]
)
output["Characters"] = ", ".join(f"{dclr}{pobj.name}|n({pobj.dbref})" for pobj in pobjs)
if things:
string += "\n|wContents|n: %s" % ", ".join(
[
"%s(%s)" % (cont.name, cont.dbref)
for cont in obj.contents
if cont not in exits and cont not in pobjs
]
output["Contents"] = ", ".join(
f"{cont.name}({cont.dbref})"
for cont in obj.contents
if cont not in exits and cont not in pobjs
)
separator = "-" * _DEFAULT_WIDTH
# output info
return "%s\n%s\n%s" % (separator, string.strip(), separator)
# format output
max_width = -1
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):
"""Process command"""
@ -2578,8 +2609,7 @@ class CmdExamine(ObjManipCommand):
that function finishes. Taking the resulting cmdset, we continue
to format and output the result.
"""
string = self.format_output(obj, cmdset)
self.msg(string.strip())
self.msg(self.format_output(obj, cmdset).strip())
if not self.args:
# If no arguments are provided, examine the invoker's location.
@ -2633,7 +2663,10 @@ class CmdExamine(ObjManipCommand):
if obj_attrs:
for attrname in obj_attrs:
# 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:
session = None
if obj.sessions.count():

View file

@ -150,11 +150,17 @@ class CommandTest(EvenniaTest):
returned_msg = msg_sep.join(
_RE.sub("", ansi.parse_ansi(mess, strip_ansi=noansi)) for mess in stored_msg
).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"
sep2 = "\n" + "=" * 30 + "Returned message" + "=" * 32 + "\n"
sep3 = "\n" + "=" * 78
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
retval = sep1 + msg + sep2 + returned_msg + sep3
raise AssertionError(retval)
else:
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.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(), "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.call(building.CmdExamine(), "")
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"{w", _ANSI_HILITE + _ANSI_WHITE), # pure white
(r"{x", _ANSI_HILITE + _ANSI_BLACK), # dark grey
(r"{R", _ANSI_HILITE + _ANSI_RED),
(r"{G", _ANSI_HILITE + _ANSI_GREEN),
(r"{Y", _ANSI_HILITE + _ANSI_YELLOW),
(r"{B", _ANSI_HILITE + _ANSI_BLUE),
(r"{M", _ANSI_HILITE + _ANSI_MAGENTA),
(r"{C", _ANSI_HILITE + _ANSI_CYAN),
(r"{W", _ANSI_HILITE + _ANSI_WHITE), # light grey
(r"{X", _ANSI_HILITE + _ANSI_BLACK), # pure black
(r"{R", _ANSI_UNHILITE + _ANSI_RED),
(r"{G", _ANSI_UNHILITE + _ANSI_GREEN),
(r"{Y", _ANSI_UNHILITE + _ANSI_YELLOW),
(r"{B", _ANSI_UNHILITE + _ANSI_BLUE),
(r"{M", _ANSI_UNHILITE + _ANSI_MAGENTA),
(r"{C", _ANSI_UNHILITE + _ANSI_CYAN),
(r"{W", _ANSI_UNHILITE + _ANSI_WHITE), # light grey
(r"{X", _ANSI_UNHILITE + _ANSI_BLACK), # pure black
# hilight-able colors
(r"{h", _ANSI_HILITE),
(r"{H", _ANSI_UNHILITE),

View file

@ -78,7 +78,7 @@ And change your game's character typeclass to inherit from TBRangeCharacter
instead of the default:
class Character(TBRangeCharacter):
Do the same thing in your game's objects.py module for TBRangeObject:
from evennia.contrib.turnbattle.tb_range import TBRangeObject
@ -246,10 +246,10 @@ def apply_damage(defender, damage):
def at_defeat(defeated):
"""
Announces the defeat of a fighter in combat.
Args:
defeated (obj): Fighter that's been defeated.
Notes:
All this does is announce a defeat message by default, but if you
want anything else to happen to defeated fighters (like putting them
@ -300,11 +300,11 @@ def resolve_attack(attacker, defender, attack_type, attack_value=None, defense_v
def get_range(obj1, obj2):
"""
Gets the combat range between two objects.
Args:
obj1 (obj): First object
obj2 (obj): Second object
Returns:
range (int or None): Distance between two objects or None if not applicable
"""
@ -324,7 +324,7 @@ def get_range(obj1, obj2):
def distance_inc(mover, target):
"""
Function that increases distance in range field between mover and target.
Args:
mover (obj): The object moving
target (obj): The object to be moved away from
@ -340,11 +340,11 @@ def distance_inc(mover, target):
def approach(mover, target):
"""
Manages a character's whole approach, including changes in ranges to other characters.
Args:
mover (obj): The object moving
target (obj): The object to be moved toward
Notes:
The mover will also automatically move toward any objects that are closer to the
target than the mover is. The mover will also move away from anything they started
@ -354,7 +354,7 @@ def approach(mover, target):
def distance_dec(mover, target):
"""
Helper function that decreases distance in range field between mover and target.
Args:
mover (obj): The object moving
target (obj): The object to be moved toward
@ -388,11 +388,11 @@ def approach(mover, target):
def withdraw(mover, target):
"""
Manages a character's whole withdrawal, including changes in ranges to other characters.
Args:
mover (obj): The object moving
target (obj): The object to be moved away from
Notes:
The mover will also automatically move away from objects that are close to the target
of their withdrawl. The mover will never inadvertently move toward anything else while
@ -540,7 +540,8 @@ class TBRangeTurnHandler(DefaultScript):
room as its object.
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):
@ -615,7 +616,7 @@ class TBRangeTurnHandler(DefaultScript):
def init_range(self, to_init):
"""
Initializes range values for an object at the start of a fight.
Args:
to_init (object): Object to initialize range field for.
"""
@ -638,14 +639,13 @@ class TBRangeTurnHandler(DefaultScript):
def join_rangefield(self, to_init, anchor_obj=None, add_distance=0):
"""
Adds a new object to the range field of a fight in progress.
Args:
to_init (object): Object to initialize range field for.
Keyword args:
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.
"""
# Get a list of room's contents without to_init object.
contents = self.obj.contents

View file

@ -203,7 +203,9 @@ class SessionHandler(dict):
elif isinstance(data, (str, bytes)):
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->)
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)

View file

@ -65,6 +65,7 @@ never traceback.
import re
import fnmatch
import random as base_random
from django.conf import settings
from evennia.utils import utils, logger
@ -72,6 +73,53 @@ from evennia.utils import utils, logger
# 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):
"""
@ -82,7 +130,8 @@ def pad(*args, **kwargs):
width (str, optional): Will be converted to integer. Width
of padding.
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:
session (Session): Session performing the pad.
@ -468,6 +517,17 @@ def parse_inlinefunc(string, strip=False, available_funcs=None, stacktrace=False
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
#

View file

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

View file

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