Converted a large part of utils/ folder to google code docstrings as per #709.

This commit is contained in:
Griatch 2015-09-27 19:51:54 +02:00
parent fc4beed9ca
commit eb2bd8d44c
17 changed files with 1107 additions and 406 deletions

View file

@ -472,10 +472,10 @@ def _create_player(session, playername, password, permissions, typeclass=None):
logger.log_trace() logger.log_trace()
return False return False
# This needs to be called so the engine knows this player is # This needs to be set so the engine knows this player is
# logging in for the first time. (so it knows to call the right # logging in for the first time. (so it knows to call the right
# hooks during login later) # hooks during login later)
utils.init_new_player(new_player) new_player.db.FIRST_LOGIN = True
# join the new player to the public channel # join the new player to the public channel
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])

View file

@ -213,10 +213,10 @@ its and @/./+/-/_ only.") # this echoes the restrictions made by django's auth m
logger.log_trace() logger.log_trace()
return return
# This needs to be called so the engine knows this player is # This needs to be set so the engine knows this player is
# logging in for the first time. (so it knows to call the right # logging in for the first time. (so it knows to call the right
# hooks during login later) # hooks during login later)
utils.init_new_player(new_player) new_player.db.FIRST_LOGIN = True
# join the new player to the public channel # join the new player to the public channel
pchanneldef = settings.CHANNEL_PUBLIC pchanneldef = settings.CHANNEL_PUBLIC

View file

@ -80,6 +80,13 @@ class ANSIParser(object):
""" """
Replacer used by `re.sub` to replace ANSI Replacer used by `re.sub` to replace ANSI
markers with correct ANSI sequences markers with correct ANSI sequences
Args:
ansimatch (re.matchobject): The match.
Returns:
processed (str): The processed match string.
""" """
return self.ansi_map.get(ansimatch.group(), "") return self.ansi_map.get(ansimatch.group(), "")
@ -87,6 +94,13 @@ class ANSIParser(object):
""" """
Replacer used by `re.sub` to replace ANSI Replacer used by `re.sub` to replace ANSI
bright background markers with Xterm256 replacement bright background markers with Xterm256 replacement
Args:
ansimatch (re.matchobject): The match.
Returns:
processed (str): The processed match string.
""" """
return self.ansi_bright_bgs.get(ansimatch.group(), "") return self.ansi_bright_bgs.get(ansimatch.group(), "")
@ -97,6 +111,14 @@ class ANSIParser(object):
It checks `self.do_xterm256` to determine if conversion It checks `self.do_xterm256` to determine if conversion
to standard ANSI should be done or not. to standard ANSI should be done or not.
Args:
rgbmatch (re.matchobject): The match.
convert (bool, optional): Convert 256-colors to 16.
Returns:
processed (str): The processed match string.
""" """
if not rgbmatch: if not rgbmatch:
return "" return ""
@ -177,21 +199,43 @@ class ANSIParser(object):
def strip_raw_codes(self, string): def strip_raw_codes(self, string):
""" """
Strips raw ANSI codes from a string. Strips raw ANSI codes from a string.
Args:
string (str): The string to strip.
Returns:
string (str): The processed string.
""" """
return self.ansi_regex.sub("", string) return self.ansi_regex.sub("", string)
def strip_mxp(self, string): def strip_mxp(self, string):
""" """
Strips all MXP codes from a string. Strips all MXP codes from a string.
Args:
string (str): The string to strip.
Returns:
string (str): The processed string.
""" """
return self.mxp_sub.sub(r'\2', string) return self.mxp_sub.sub(r'\2', string)
def parse_ansi(self, string, strip_ansi=False, xterm256=False, mxp=False): def parse_ansi(self, string, strip_ansi=False, xterm256=False, mxp=False):
""" """
Parses a string, subbing color codes according to Parses a string, subbing color codes according to the stored
the stored mapping. mapping.
strip_ansi flag instead removes all ANSI markup. Args:
string (str): The string to parse.
strip_ansi (boolean, optional): Strip all found ansi markup.
xterm256 (boolean, optional): If actually using xterm256 or if
these values should be converted to 16-color ANSI.
mxp (boolean, optional): Parse MXP commands in string.
Returns:
string (str): The parsed string.
""" """
if hasattr(string, '_raw_string'): if hasattr(string, '_raw_string'):
@ -351,27 +395,57 @@ def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER, xterm256=False, mxp
""" """
Parses a string, subbing color codes as needed. Parses a string, subbing color codes as needed.
Args:
string (str): The string to parse.
strip_ansi (bool, optional): Strip all ANSI sequences.
parser (ansi.AnsiParser, optional): A parser instance to use.
xterm256 (bool, optional): Support xterm256 or not.
mxp (bool, optional): Support MXP markup or not.
Returns:
string (str): The parsed string.
""" """
return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256, mxp=mxp) return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256, mxp=mxp)
def strip_ansi(string, parser=ANSI_PARSER): def strip_ansi(string, parser=ANSI_PARSER):
""" """
Strip all ansi from the string. Strip all ansi from the string. This handles the Evennia-specific
markup.
Args:
parser (ansi.AnsiParser, optional): The parser to use.
Returns:
string (str): The stripped string.
""" """
return parser.parse_ansi(string, strip_ansi=True) return parser.parse_ansi(string, strip_ansi=True)
def strip_raw_ansi(string, parser=ANSI_PARSER): def strip_raw_ansi(string, parser=ANSI_PARSER):
""" """
Remove raw ansi codes from string Remove raw ansi codes from string. This assumes pure
ANSI-bytecodes in the string.
Args:
string (str): The string to parse.
parser (bool, optional): The parser to use.
Returns:
string (str): the stripped string.
""" """
return parser.strip_raw_codes(string) return parser.strip_raw_codes(string)
def raw(string): def raw(string):
""" """
Escapes a string into a form which won't be colorized by the ansi parser. Escapes a string into a form which won't be colorized by the ansi
parser.
Returns:
string (str): The raw, escaped string.
""" """
return string.replace('{', '{{') return string.replace('{', '{{')
@ -385,8 +459,9 @@ def group(lst, n):
def _spacing_preflight(func): def _spacing_preflight(func):
""" """
This wrapper function is used to do some preflight checks on functions used This wrapper function is used to do some preflight checks on
for padding ANSIStrings. functions used for padding ANSIStrings.
""" """
def wrapped(self, width, fillchar=None): def wrapped(self, width, fillchar=None):
if fillchar is None: if fillchar is None:
@ -404,8 +479,9 @@ def _spacing_preflight(func):
def _query_super(func_name): def _query_super(func_name):
""" """
Have the string class handle this with the cleaned string instead of Have the string class handle this with the cleaned string instead
ANSIString. of ANSIString.
""" """
def wrapped(self, *args, **kwargs): def wrapped(self, *args, **kwargs):
return getattr(self.clean(), func_name)(*args, **kwargs) return getattr(self.clean(), func_name)(*args, **kwargs)
@ -415,6 +491,7 @@ def _query_super(func_name):
def _on_raw(func_name): def _on_raw(func_name):
""" """
Like query_super, but makes the operation run on the raw string. Like query_super, but makes the operation run on the raw string.
""" """
def wrapped(self, *args, **kwargs): def wrapped(self, *args, **kwargs):
args = list(args) args = list(args)
@ -439,6 +516,7 @@ def _transform(func_name):
return a string the same length as the original. This function return a string the same length as the original. This function
allows us to do the same, replacing all the non-coded characters allows us to do the same, replacing all the non-coded characters
with the resulting string. with the resulting string.
""" """
def wrapped(self, *args, **kwargs): def wrapped(self, *args, **kwargs):
replacement_string = _query_super(func_name)(self, *args, **kwargs) replacement_string = _query_super(func_name)(self, *args, **kwargs)
@ -461,6 +539,7 @@ class ANSIMeta(type):
""" """
Many functions on ANSIString are just light wrappers around the unicode Many functions on ANSIString are just light wrappers around the unicode
base class. We apply them here, as part of the classes construction. base class. We apply them here, as part of the classes construction.
""" """
def __init__(cls, *args, **kwargs): def __init__(cls, *args, **kwargs):
for func_name in [ for func_name in [
@ -493,6 +572,7 @@ class ANSIString(unicode):
Please refer to the Metaclass, ANSIMeta, which is used to apply wrappers Please refer to the Metaclass, ANSIMeta, which is used to apply wrappers
for several of the methods that need not be defined directly here. for several of the methods that need not be defined directly here.
""" """
__metaclass__ = ANSIMeta __metaclass__ = ANSIMeta
@ -506,6 +586,7 @@ class ANSIString(unicode):
Internally, ANSIString can also passes itself precached code/character Internally, ANSIString can also passes itself precached code/character
indexes and clean strings to avoid doing extra work when combining indexes and clean strings to avoid doing extra work when combining
ANSIStrings. ANSIStrings.
""" """
string = args[0] string = args[0]
if not isinstance(string, basestring): if not isinstance(string, basestring):
@ -554,9 +635,11 @@ class ANSIString(unicode):
def __unicode__(self): def __unicode__(self):
""" """
Unfortunately, this is not called during print() statements due to a Unfortunately, this is not called during print() statements
bug in the Python interpreter. You can always do unicode() or str() due to a bug in the Python interpreter. You can always do
around the resulting ANSIString and print that. unicode() or str() around the resulting ANSIString and print
that.
""" """
return self._raw_string return self._raw_string
@ -564,6 +647,7 @@ class ANSIString(unicode):
""" """
Let's make the repr the command that would actually be used to Let's make the repr the command that would actually be used to
construct this object, for convenience and reference. construct this object, for convenience and reference.
""" """
return "ANSIString(%s, decoded=True)" % repr(self._raw_string) return "ANSIString(%s, decoded=True)" % repr(self._raw_string)
@ -592,6 +676,7 @@ class ANSIString(unicode):
Finally, _code_indexes and _char_indexes are defined. These are lookup Finally, _code_indexes and _char_indexes are defined. These are lookup
tables for which characters in the raw string are related to ANSI tables for which characters in the raw string are related to ANSI
escapes, and which are for the readable text. escapes, and which are for the readable text.
""" """
self.parser = kwargs.pop('parser', ANSI_PARSER) self.parser = kwargs.pop('parser', ANSI_PARSER)
super(ANSIString, self).__init__() super(ANSIString, self).__init__()
@ -603,6 +688,7 @@ class ANSIString(unicode):
""" """
Takes a list of integers, and produces a new one incrementing all Takes a list of integers, and produces a new one incrementing all
by a number. by a number.
""" """
return [i + offset for i in iterable] return [i + offset for i in iterable]
@ -610,6 +696,7 @@ class ANSIString(unicode):
def _adder(cls, first, second): def _adder(cls, first, second):
""" """
Joins two ANSIStrings, preserving calculated info. Joins two ANSIStrings, preserving calculated info.
""" """
raw_string = first._raw_string + second._raw_string raw_string = first._raw_string + second._raw_string
@ -629,6 +716,7 @@ class ANSIString(unicode):
We have to be careful when adding two strings not to reprocess things We have to be careful when adding two strings not to reprocess things
that don't need to be reprocessed, lest we end up with escapes being that don't need to be reprocessed, lest we end up with escapes being
interpreted literally. interpreted literally.
""" """
if not isinstance(other, basestring): if not isinstance(other, basestring):
return NotImplemented return NotImplemented
@ -639,6 +727,7 @@ class ANSIString(unicode):
def __radd__(self, other): def __radd__(self, other):
""" """
Likewise, if we're on the other end. Likewise, if we're on the other end.
""" """
if not isinstance(other, basestring): if not isinstance(other, basestring):
return NotImplemented return NotImplemented
@ -650,6 +739,7 @@ class ANSIString(unicode):
""" """
This function is deprecated, so we just make it call the proper This function is deprecated, so we just make it call the proper
function. function.
""" """
return self.__getitem__(slice(i, j)) return self.__getitem__(slice(i, j))
@ -667,6 +757,7 @@ class ANSIString(unicode):
indexes that need slicing in the raw string. We can check between indexes that need slicing in the raw string. We can check between
those indexes to figure out what escape characters need to be those indexes to figure out what escape characters need to be
replayed. replayed.
""" """
slice_indexes = self._char_indexes[slc] slice_indexes = self._char_indexes[slc]
# If it's the end of the string, we need to append final color codes. # If it's the end of the string, we need to append final color codes.
@ -700,6 +791,7 @@ class ANSIString(unicode):
this is a regexable ANSIString, it will get the data from the raw this is a regexable ANSIString, it will get the data from the raw
string instead, bypassing ANSIString's intelligent escape skipping, string instead, bypassing ANSIString's intelligent escape skipping,
for reasons explained in the __new__ method's docstring. for reasons explained in the __new__ method's docstring.
""" """
if isinstance(item, slice): if isinstance(item, slice):
# Slices must be handled specially. # Slices must be handled specially.
@ -727,12 +819,14 @@ class ANSIString(unicode):
def clean(self): def clean(self):
""" """
Return a unicode object without the ANSI escapes. Return a unicode object without the ANSI escapes.
""" """
return self._clean_string return self._clean_string
def raw(self): def raw(self):
""" """
Return a unicode object with the ANSI escapes. Return a unicode object with the ANSI escapes.
""" """
return self._raw_string return self._raw_string
@ -746,6 +840,7 @@ class ANSIString(unicode):
We use the same techniques we used in split() to make sure each are We use the same techniques we used in split() to make sure each are
colored. colored.
""" """
if hasattr(sep, '_clean_string'): if hasattr(sep, '_clean_string'):
sep = sep.clean() sep = sep.clean()
@ -776,6 +871,7 @@ class ANSIString(unicode):
It's possible that only one of these tables is actually needed, the It's possible that only one of these tables is actually needed, the
other assumed to be what isn't in the first. other assumed to be what isn't in the first.
""" """
code_indexes = [] code_indexes = []
@ -792,6 +888,7 @@ class ANSIString(unicode):
""" """
Get the code characters from the given slice end to the next Get the code characters from the given slice end to the next
character. character.
""" """
try: try:
index = self._char_indexes[index - 1] index = self._char_indexes[index - 1]
@ -815,6 +912,7 @@ class ANSIString(unicode):
PyPy is distributed under the MIT licence. PyPy is distributed under the MIT licence.
http://opensource.org/licenses/MIT http://opensource.org/licenses/MIT
""" """
bylen = len(by) bylen = len(by)
if bylen == 0: if bylen == 0:
@ -837,6 +935,7 @@ class ANSIString(unicode):
def __mul__(self, other): def __mul__(self, other):
""" """
Multiplication method. Implemented for performance reasons. Multiplication method. Implemented for performance reasons.
""" """
if not isinstance(other, int): if not isinstance(other, int):
return NotImplemented return NotImplemented
@ -863,6 +962,7 @@ class ANSIString(unicode):
PyPy is distributed under the MIT licence. PyPy is distributed under the MIT licence.
http://opensource.org/licenses/MIT http://opensource.org/licenses/MIT
""" """
res = [] res = []
end = len(self) end = len(self)
@ -886,6 +986,7 @@ class ANSIString(unicode):
def join(self, iterable): def join(self, iterable):
""" """
Joins together strings in an iterable. Joins together strings in an iterable.
""" """
result = ANSIString('') result = ANSIString('')
last_item = None last_item = None
@ -902,6 +1003,7 @@ class ANSIString(unicode):
""" """
Generate a line of characters in a more efficient way than just adding Generate a line of characters in a more efficient way than just adding
ANSIStrings. ANSIStrings.
""" """
if not isinstance(char, ANSIString): if not isinstance(char, ANSIString):
line = char * amount line = char * amount
@ -929,6 +1031,7 @@ class ANSIString(unicode):
def center(self, width, fillchar, difference): def center(self, width, fillchar, difference):
""" """
Center some text with some spaces padding both sides. Center some text with some spaces padding both sides.
""" """
remainder = difference % 2 remainder = difference % 2
difference /= 2 difference /= 2
@ -940,6 +1043,7 @@ class ANSIString(unicode):
def ljust(self, width, fillchar, difference): def ljust(self, width, fillchar, difference):
""" """
Left justify some text. Left justify some text.
""" """
return self + self._filler(fillchar, difference) return self + self._filler(fillchar, difference)
@ -947,5 +1051,6 @@ class ANSIString(unicode):
def rjust(self, width, fillchar, difference): def rjust(self, width, fillchar, difference):
""" """
Right justify some text. Right justify some text.
""" """
return self._filler(fillchar, difference) + self return self._filler(fillchar, difference) + self

View file

@ -192,13 +192,23 @@ RE_CODE_SPLIT = re.compile(r"(^\#CODE.*?$|^\#HEADER)$", re.MULTILINE)
def read_batchfile(pythonpath, file_ending='.py'): def read_batchfile(pythonpath, file_ending='.py'):
""" """
This reads the contents of a batch-file. This reads the contents of a batch-file. Filename is considered
Filename is considered to be a python path to a batch file to be a python path to a batch file relative the directory
relative the directory specified in `settings.py`. specified in `settings.py`.
file_ending specify which batchfile ending should be assumed (.ev
or .py). The ending should not be included in the python path.
Args:
pythonpath (str): A dot-python path to a file.
file_ending (str): The file ending of this file (.ev or .py)
Returns:
text (str): The text content of the batch file.
Raises:
IOError: If problems reading file.
file_ending specify which batchfile ending should be
assumed (.ev or .py). The ending should not be included
in the python path.
""" """
# open the file # open the file
@ -293,6 +303,7 @@ def tb_filename(tb):
def tb_iter(tb): def tb_iter(tb):
"Traceback iterator."
while tb is not None: while tb is not None:
yield tb yield tb
tb = tb.tb_next tb = tb.tb_next
@ -309,13 +320,22 @@ class BatchCodeProcessor(object):
This parses the lines of a batchfile according to the following This parses the lines of a batchfile according to the following
rules: rules:
1) Lines starting with #HEADER starts a header block (ends other blocks) Args:
2) Lines starting with #CODE begins a code block (ends other blocks) pythonpath (str): The dot-python path to the file.
3) #CODE headers may be of the following form: debug (bool, optional): Insert delete-commands for
#CODE (info) objname, objname2, ... deleting created objects.
4) Lines starting with #INSERT are on form #INSERT filename.
5) All lines outside blocks are stripped. Returns:
6) All excess whitespace beginning/ending a block is stripped. codeblocks (list): A list of all #CODE blocks.
Notes:
1. Lines starting with #HEADER starts a header block (ends other blocks)
2. Lines starting with #CODE begins a code block (ends other blocks)
3. #CODE headers may be of the following form:
#CODE (info) objname, objname2, ...
4. Lines starting with #INSERT are on form #INSERT filename.
5. All lines outside blocks are stripped.
6. All excess whitespace beginning/ending a block is stripped.
""" """
@ -373,9 +393,17 @@ class BatchCodeProcessor(object):
def code_exec(self, code, extra_environ=None, debug=False): def code_exec(self, code, extra_environ=None, debug=False):
""" """
Execute a single code block, including imports and appending global vars Execute a single code block, including imports and appending
global vars.
Args:
code (str): Code to run.
extra_environ (dict): Environment variables to run with code.
debug (bool, optional): Insert delete statements for objects.
Returns:
err (str or None): An error code or None (ok).
extra_environ - dict with environment variables
""" """
# define the execution environment # define the execution environment
environdict = {"settings_module": settings} environdict = {"settings_module": settings}

View file

@ -59,24 +59,35 @@ def create_object(typeclass=None, key=None, location=None,
Create a new in-game object. Create a new in-game object.
keywords: Kwargs:
typeclass - class or python path to a typeclass typeclass (class or str): Class or python path to a typeclass.
key - name of the new object. If not set, a name of #dbref will be set. key (str): Name of the new object. If not set, a name of
home - obj or #dbref to use as the object's home location #dbref will be set.
permissions - a comma-separated string of permissions home (Object or str): Obj or #dbref to use as the object's
locks - one or more lockstrings, separated by semicolons home location.
aliases - a list of alternative keys permissions (str): A comma-separated string of permissions.
tags - a list of tag keys (using no category) locks (str): one or more lockstrings, separated by semicolons.
destination - obj or #dbref to use as an Exit's target aliases (list): A list of alternative keys.
tags (list): List of tag keys (using no category).
destination (Object or str): Obj or #dbref to use as an Exit's
target.
report_to (Object): The object to return error messages to.
nohome (bool): This allows the creation of objects without a
default home location; only used when creating the default
location itself or during unittests.
Returns:
object (Object): A newly created object of the given typeclass.
Raises:
ObjectDB.DoesNotExist: If trying to create an Object with
`location` or `home` that can't be found.
nohome - this allows the creation of objects without a default home location;
only used when creating the default location itself or during unittests
""" """
global _ObjectDB global _ObjectDB
if not _ObjectDB: if not _ObjectDB:
from evennia.objects.models import ObjectDB as _ObjectDB from evennia.objects.models import ObjectDB as _ObjectDB
typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS typeclass = typeclass if typeclass else settings.BASE_OBJECT_TYPECLASS
if isinstance(typeclass, basestring): if isinstance(typeclass, basestring):
@ -120,32 +131,42 @@ object = create_object
# Script creation # Script creation
# #
def create_script(typeclass, key=None, obj=None, player=None, locks=None, def create_script(typeclass=None, key=None, obj=None, player=None, locks=None,
interval=None, start_delay=None, repeats=None, interval=None, start_delay=None, repeats=None,
persistent=None, autostart=True, report_to=None): persistent=None, autostart=True, report_to=None):
""" """
Create a new script. All scripts are a combination Create a new script. All scripts are a combination of a database
of a database object that communicates with the object that communicates with the database, and an typeclass that
database, and an typeclass that 'decorates' the 'decorates' the database object into being different types of
database object into being different types of scripts. scripts. It's behaviour is similar to the game objects except
It's behaviour is similar to the game objects except scripts has a time component and are more limited in scope.
scripts has a time component and are more limited in
scope. Kwargs:
typeclass (class or str): Class or python path to a typeclass.
key (str): Name of the new object. If not set, a name of
#dbref will be set.
obj (Object): The entity on which this Script sits. If this
is `None`, we are creating a "global" script.
player (Player): The player on which this Script sits. It is
exclusiv to `obj`.
locks (str): one or more lockstrings, separated by semicolons.
interval (int): The triggering interval for this Script, in
seconds. If unset, the Script will not have a timing
component.
start_delay (bool): If `True`, will wait `interval` seconds
before triggering the first time.
repeats (int): The number of times to trigger before stopping.
If unset, will repeat indefinitely.
persistent (bool): If this Script survives a server shutdown
or not (all Scripts will survive a reload).
autostart (bool): If this Script will start immediately when
created or if the `start` method must be called explicitly.
report_to (Object): The object to return error messages to.
Argument 'typeclass' can be either an actual
typeclass object or a python path to such an object.
Only set key here if you want a unique name for this
particular script (set it in config to give
same key to all scripts of the same type). Set obj
to tie this script to a particular object.
See evennia.scripts.manager for methods to manipulate existing See evennia.scripts.manager for methods to manipulate existing
scripts in the database. scripts in the database.
report_to is an obtional object to receive error messages.
If report_to is not set, an Exception with the
error will be raised. If set, this method will
return None upon errors.
""" """
global _ScriptDB global _ScriptDB
if not _ScriptDB: if not _ScriptDB:
@ -194,10 +215,20 @@ script = create_script
def create_help_entry(key, entrytext, category="General", locks=None): def create_help_entry(key, entrytext, category="General", locks=None):
""" """
Create a static help entry in the help database. Note that Command Create a static help entry in the help database. Note that Command
help entries are dynamic and directly taken from the __doc__ entries help entries are dynamic and directly taken from the __doc__
of the command. The database-stored help entries are intended for more entries of the command. The database-stored help entries are
general help on the game, more extensive info, in-game setting information intended for more general help on the game, more extensive info,
and so on. in-game setting information and so on.
Args:
key (str): The name of the help entry.
entrytext (str): The body of te help entry
category (str, optional): The help category of the entry.
locks (str, optional): A lockstring to restrict access.
Returns:
help (HelpEntry): A newly created help entry.
""" """
global _HelpEntry global _HelpEntry
if not _HelpEntry: if not _HelpEntry:
@ -230,24 +261,28 @@ help_entry = create_help_entry
def create_message(senderobj, message, channels=None, def create_message(senderobj, message, channels=None,
receivers=None, locks=None, header=None): receivers=None, locks=None, header=None):
""" """
Create a new communication message. Msgs are used for all Create a new communication Msg. Msgs represent a unit of
player-to-player communication, both between individual players database-persistent communication between entites.
and over channels.
senderobj - the player sending the message. This must be the actual object. Args:
message - text with the message. Eventual headers, titles etc senderobj (Object or Player): The entity sending the Msg.
should all be included in this text string. Formatting message (str): Text with the message. Eventual headers, titles
will be retained. etc should all be included in this text string. Formatting
channels - a channel or a list of channels to send to. The channels will be retained.
may be actual channel objects or their unique key strings. channels (Channel, key or list): A channel or a list of channels to
receivers - a player to send to, or a list of them. May be Player objects send to. The channels may be actual channel objects or their
or playernames. unique key strings.
locks - lock definition string receivers (Object, Player, str or list): A Player/Object to send
header - mime-type or other optional information for the message to, or a list of them. May be Player objects or playernames.
locks (str): Lock definition string.
header (str): Mime-type or other optional information for the message
Notes:
The Comm system is created very open-ended, so it's fully possible
to let a message both go to several channels and to several
receivers at the same time, it's up to the command definitions to
limit this as desired.
The Comm system is created very open-ended, so it's fully possible
to let a message both go to several channels and to several receivers
at the same time, it's up to the command definitions to limit this as
desired.
""" """
global _Msg global _Msg
if not _Msg: if not _Msg:
@ -275,16 +310,27 @@ def create_channel(key, aliases=None, desc=None,
locks=None, keep_log=True, locks=None, keep_log=True,
typeclass=None): typeclass=None):
""" """
Create A communication Channel. A Channel serves as a central Create A communication Channel. A Channel serves as a central hub
hub for distributing Msgs to groups of people without for distributing Msgs to groups of people without specifying the
specifying the receivers explicitly. Instead players may receivers explicitly. Instead players may 'connect' to the channel
'connect' to the channel and follow the flow of messages. By and follow the flow of messages. By default the channel allows
default the channel allows access to all old messages, but access to all old messages, but this can be turned off with the
this can be turned off with the keep_log switch. keep_log switch.
Args:
key (str): This must be unique.
Kwargs:
aliases (list): List of alternative (likely shorter) keynames.
desc (str): A description of the channel, for use in listings.
locks (str): Lockstring.
keep_log (bool): Log channel throughput.
typeclass (str or class): The typeclass of the Channel (not
often used).
Returns:
channel (Channel): A newly created channel.
key - this must be unique.
aliases - list of alternative (likely shorter) keynames.
locks - lock string definitions
""" """
typeclass = typeclass if typeclass else settings.BASE_CHANNEL_TYPECLASS typeclass = typeclass if typeclass else settings.BASE_CHANNEL_TYPECLASS
@ -322,23 +368,28 @@ def create_player(key, email, password,
""" """
This creates a new player. This creates a new player.
key - the player's name. This should be unique. Args:
email - email on valid addr@addr.domain form. key (str): The player's name. This should be unique.
password - password in cleartext email (str): Email on valid addr@addr.domain form. This is
is_superuser - wether or not this player is to be a superuser technically required but if set to `None`, an email of
locks - lockstring `dummy@dummy.com` will be used as a placeholder.
permission - list of permissions password (str): Password in cleartext.
report_to - an object with a msg() method to report errors to. If
not given, errors will be logged.
Will return the Player-typeclass or None/raise Exception if the Kwargs:
Typeclass given failed to load. is_superuser (bool): Wether or not this player is to be a superuser
locks (str): Lockstring.
permission (list): List of permission strings.
report_to (Object): An object with a msg() method to report
errors to. If not given, errors will be logged.
Concerning is_superuser: Raises:
Usually only the server admin should need to be superuser, all ValueError: If `key` already exists in database.
other access levels can be handled with more fine-grained
permissions or groups. A superuser bypasses all lock checking Notes:
operations and is thus not suitable for play-testing the game. Usually only the server admin should need to be superuser, all
other access levels can be handled with more fine-grained
permissions or groups. A superuser bypasses all lock checking
operations and is thus not suitable for play-testing the game.
""" """
global _PlayerDB global _PlayerDB

View file

@ -25,7 +25,6 @@ try:
from cPickle import dumps, loads from cPickle import dumps, loads
except ImportError: except ImportError:
from pickle import dumps, loads from pickle import dumps, loads
from django.db import transaction
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from evennia.server.models import ServerConfig from evennia.server.models import ServerConfig
@ -52,8 +51,14 @@ else:
def _TO_DATESTRING(obj): def _TO_DATESTRING(obj):
""" """
this will only be called with valid database objects. Returns datestring Creates datestring hash.
on correct form.
Args:
obj (Object): Database object.
Returns:
datestring (str): A datestring hash.
""" """
try: try:
return _GA(obj, "db_date_created").strftime(_DATESTRING) return _GA(obj, "db_date_created").strftime(_DATESTRING)
@ -209,8 +214,14 @@ class _SaverSet(_SaverMutable, MutableSet):
def pack_dbobj(item): def pack_dbobj(item):
""" """
Check and convert django database objects to an internal representation. Check and convert django database objects to an internal representation.
This either returns the original input item or a tuple
("__packed_dbobj__", key, creation_time, id) Args:
item (any): A database entity to pack
Returns:
packed (any or tuple): Either returns the original input item
or the packing tuple `("__packed_dbobj__", key, creation_time, id)`.
""" """
_init_globals() _init_globals()
obj = item obj = item
@ -224,10 +235,18 @@ def pack_dbobj(item):
def unpack_dbobj(item): def unpack_dbobj(item):
""" """
Check and convert internal representations back to Django database models. Check and convert internal representations back to Django database
The fact that item is a packed dbobj should be checked before this call. models.
This either returns the original input or converts the internal store back
to a database representation (its typeclass is returned if applicable). Args:
item (packed_dbobj): The fact that item is a packed dbobj
should be checked before this call.
Returns:
unpacked (any): Either the original input or converts the
internal store back to a database representation (its
typeclass is returned if applicable).
""" """
_init_globals() _init_globals()
try: try:
@ -244,11 +263,18 @@ def unpack_dbobj(item):
def to_pickle(data): def to_pickle(data):
""" """
This prepares data on arbitrary form to be pickled. It handles any nested This prepares data on arbitrary form to be pickled. It handles any
structure and returns data on a form that is safe to pickle (including nested structure and returns data on a form that is safe to pickle
having converted any database models to their internal representation). (including having converted any database models to their internal
We also convert any Saver*-type objects back to their normal representation). We also convert any Saver*-type objects back to
representations, they are not pickle-safe. their normal representations, they are not pickle-safe.
Args:
data (any): Data to pickle.
Returns:
data (any): Pickled data.
""" """
def process_item(item): def process_item(item):
"Recursive processor and identification of data" "Recursive processor and identification of data"
@ -281,13 +307,18 @@ def from_pickle(data, db_obj=None):
object was removed (or changed in-place) in the database, None will be object was removed (or changed in-place) in the database, None will be
returned. returned.
db_obj - this is the model instance (normally an Attribute) that Args_
_Saver*-type iterables (_SaverList etc) will save to when they data (any): Pickled data to unpickle.
update. It must have a 'value' property that saves assigned data db_obj (Atribute, any): This is the model instance (normally
to the database. Skip if not serializing onto a given object. an Attribute) that _Saver*-type iterables (_SaverList etc)
will save to when they update. It must have a 'value' property
that saves assigned data to the database. Skip if not
serializing onto a given object. If db_obj is given, this
function will convert lists, dicts and sets to their
_SaverList, _SaverDict and _SaverSet counterparts.
If db_obj is given, this function will convert lists, dicts and sets Returns:
to their _SaverList, _SaverDict and _SaverSet counterparts. data (any): Unpickled data.
""" """
def process_item(item): def process_item(item):

View file

@ -631,7 +631,9 @@ class EvEditor(object):
def get_buffer(self): def get_buffer(self):
""" """
Return the current buffer Return:
buffer (str): The current buffer.
""" """
return self._buffer return self._buffer
@ -639,6 +641,10 @@ class EvEditor(object):
""" """
This should be called when the buffer has been changed This should be called when the buffer has been changed
somehow. It will handle unsaved flag and undo updating. somehow. It will handle unsaved flag and undo updating.
Args:
buf (str): The text to update the buffer with.
""" """
if is_iter(buf): if is_iter(buf):
buf = "\n".join(buf) buf = "\n".join(buf)
@ -651,6 +657,7 @@ class EvEditor(object):
def quit(self): def quit(self):
""" """
Cleanly exit the editor. Cleanly exit the editor.
""" """
try: try:
self._quitfunc(self._caller) self._quitfunc(self._caller)
@ -663,6 +670,7 @@ class EvEditor(object):
""" """
Saves the content of the buffer. The 'quitting' argument is a bool Saves the content of the buffer. The 'quitting' argument is a bool
indicating whether or not the editor intends to exit after saving. indicating whether or not the editor intends to exit after saving.
""" """
if self._unsaved: if self._unsaved:
try: try:
@ -680,6 +688,12 @@ class EvEditor(object):
""" """
This updates the undo position. This updates the undo position.
Args:
step (int, optional): The amount of steps
to progress the undo position to. This
may be a negative value for undo and
a positive value for redo.
""" """
if step and step < 0: if step and step < 0:
# undo # undo
@ -706,8 +720,15 @@ class EvEditor(object):
""" """
This displays the line editor buffer, or selected parts of it. This displays the line editor buffer, or selected parts of it.
If `buf` is set and is not the full buffer, `offset` should define Args:
the starting line number, to get the linenum display right. buf (str, optional): The buffer or part of buffer to display.
offset (int, optional): If `buf` is set and is not the full buffer,
`offset` should define the actual starting line number, to
get the linenum display right.
linenums (bool, optional): Show line numbers in buffer.
raw (bool, optional): Tell protocol to not parse
formatting information.
""" """
if buf == None: if buf == None:
buf = self._buffer buf = self._buffer
@ -733,6 +754,7 @@ class EvEditor(object):
def display_help(self): def display_help(self):
""" """
Shows the help entry for the editor. Shows the help entry for the editor.
""" """
string = self._sep * _DEFAULT_WIDTH + _HELP_TEXT + self._sep * _DEFAULT_WIDTH string = self._sep * _DEFAULT_WIDTH + _HELP_TEXT + self._sep * _DEFAULT_WIDTH
self._caller.msg(string) self._caller.msg(string)

View file

@ -168,17 +168,17 @@ class EvForm(object):
""" """
Initiate the form Initiate the form
keywords: Kwargs:
filename - path to template file filename (str): Path to template file.
form - dictionary of {"CELLCHAR":char, cells (dict): A dictionary mapping of {id:text}
"TABLECHAR":char, tables (dict): A dictionary mapping of {id:EvTable}.
"FORM":templatestring} form (dict): A dictionary of {"CELLCHAR":char,
"TABLECHAR":char,
"FORM":templatestring}
if this is given, filename is not read. if this is given, filename is not read.
cells - a dictionary mapping of {id:text} Notes:
tables - dictionary mapping of {id:EvTable} Other kwargs are fed as options to the EvCells and EvTables
(see `evtable.EvCell` and `evtable.EvTable` for more info).
other kwargs are fed as options to the EvCells and EvTables
(see `evtable.EvCell` and `evtable.EvTable` for more info).
""" """
self.filename = filename self.filename = filename
@ -204,8 +204,9 @@ class EvForm(object):
def _parse_rectangles(self, cellchar, tablechar, form, **kwargs): def _parse_rectangles(self, cellchar, tablechar, form, **kwargs):
""" """
Parse a form for rectangular formfields identified by Parse a form for rectangular formfields identified by formchar
formchar enclosing an identifier. enclosing an identifier.
""" """
# update options given at creation with new input - this # update options given at creation with new input - this
@ -337,6 +338,7 @@ class EvForm(object):
def _populate_form(self, raw_form, mapping): def _populate_form(self, raw_form, mapping):
""" """
Insert cell contents into form at given locations Insert cell contents into form at given locations
""" """
form = copy.copy(raw_form) form = copy.copy(raw_form)
for key, (iy0, ix0, width, height, cell_or_table) in mapping.items(): for key, (iy0, ix0, width, height, cell_or_table) in mapping.items():
@ -352,11 +354,13 @@ class EvForm(object):
""" """
Add mapping for form. Add mapping for form.
cells - a dictionary of {identifier:celltext} Args:
tables - a dictionary of {identifier:table} cells (dict): A dictionary of {identifier:celltext}
tables (dict): A dictionary of {identifier:table}
kwargs will be forwarded to tables/cells. See Notes:
evtable.EvCell and evtable.EvTable for info. kwargs will be forwarded to tables/cells. See
`evtable.EvCell` and `evtable.EvTable` for info.
""" """
# clean kwargs (these cannot be overridden) # clean kwargs (these cannot be overridden)
@ -373,7 +377,15 @@ class EvForm(object):
def reload(self, filename=None, form=None, **kwargs): def reload(self, filename=None, form=None, **kwargs):
""" """
Creates the form from a stored file name Creates the form from a stored file name.
Args:
filename (str): The file to read from.
form (dict): A mapping for the form.
Notes:
Kwargs are passed through to Cel creation.
""" """
# clean kwargs (these cannot be overridden) # clean kwargs (these cannot be overridden)
kwargs.pop("enforce_size", None) kwargs.pop("enforce_size", None)

View file

@ -65,21 +65,52 @@ def _format(seconds, *divisors) :
# Access functions # Access functions
def runtime(format=False): def runtime(format=False):
"Get the total runtime of the server since first start (minus downtimes)" """
Get the total runtime of the server since first start (minus
downtimes)
Args:
format (bool, optional): Format into a time representation.
Returns:
time (float or tuple): The runtime or the same time split up
into time units.
"""
runtime = SERVER_RUNTIME + (time() - SERVER_RUNTIME_LAST_UPDATED) runtime = SERVER_RUNTIME + (time() - SERVER_RUNTIME_LAST_UPDATED)
if format: if format:
return _format(runtime, 31536000, 2628000, 604800, 86400, 3600, 60) return _format(runtime, 31536000, 2628000, 604800, 86400, 3600, 60)
return runtime return runtime
def uptime(format=False): def uptime(format=False):
"Get the current uptime of the server since last reload" """
Get the current uptime of the server since last reload
Args:
format (bool, optional): Format into time representation.
Returns:
time (float or tuple): The uptime or the same time split up
into time units.
"""
uptime = time() - SERVER_START_TIME uptime = time() - SERVER_START_TIME
if format: if format:
return _format(uptime, 31536000, 2628000, 604800, 86400, 3600, 60) return _format(uptime, 31536000, 2628000, 604800, 86400, 3600, 60)
return uptime return uptime
def gametime(format=False): def gametime(format=False):
"Get the total gametime of the server since first start (minus downtimes)" """
Get the total gametime of the server since first start (minus downtimes)
Args:
format (bool, optional): Format into time representation.
Returns:
time (float or tuple): The gametime or the same time split up
into time units.
"""
gametime = runtime() * TIMEFACTOR gametime = runtime() * TIMEFACTOR
if format: if format:
return _format(gametime, YEAR, MONTH, WEEK, DAY, HOUR, MIN) return _format(gametime, YEAR, MONTH, WEEK, DAY, HOUR, MIN)
@ -94,9 +125,18 @@ def gametime_to_realtime(secs=0, mins=0, hrs=0, days=0,
in-game, you will be able to find the number of real-world seconds this in-game, you will be able to find the number of real-world seconds this
corresponds to (hint: Interval events deal with real life seconds). corresponds to (hint: Interval events deal with real life seconds).
Kwargs:
times (int): The various components of the time.
format (bool): Formatting the output.
Returns:
time (float or tuple): The realtime difference or the same
time split up into time units.
Example: Example:
gametime_to_realtime(days=2) -> number of seconds in real life from gametime_to_realtime(days=2) -> number of seconds in real life from
now after which 2 in-game days will have passed. now after which 2 in-game days will have passed.
""" """
realtime = (secs + mins * MIN + hrs * HOUR + days * DAY + weeks * WEEK + \ realtime = (secs + mins * MIN + hrs * HOUR + days * DAY + weeks * WEEK + \
months * MONTH + yrs * YEAR) / TIMEFACTOR months * MONTH + yrs * YEAR) / TIMEFACTOR
@ -109,12 +149,21 @@ def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0,
weeks=0, months=0, yrs=0, format=False): weeks=0, months=0, yrs=0, format=False):
""" """
This method calculates how much in-game time a real-world time This method calculates how much in-game time a real-world time
interval would correspond to. This is usually a lot less interesting interval would correspond to. This is usually a lot less
than the other way around. interesting than the other way around.
Kwargs:
times (int): The various components of the time.
format (bool): Formatting the output.
Returns:
time (float or tuple): The gametime difference or the same
time split up into time units.
Example: Example:
realtime_to_gametime(days=2) -> number of game-world seconds realtime_to_gametime(days=2) -> number of game-world seconds
corresponding to 2 real days. corresponding to 2 real days.
""" """
gametime = TIMEFACTOR * (secs + mins * 60 + hrs * 3600 + days * 86400 + gametime = TIMEFACTOR * (secs + mins * 60 + hrs * 3600 + days * 86400 +
weeks * 604800 + months * 2628000 + yrs * 31536000) weeks * 604800 + months * 2628000 + yrs * 31536000)

View file

@ -13,6 +13,9 @@ class SharedMemoryManager(Manager):
# still use the singleton cache, but the active model isn't required # still use the singleton cache, but the active model isn't required
# to be a SharedMemoryModel. # to be a SharedMemoryModel.
def get(self, **kwargs): def get(self, **kwargs):
"""
Data entity lookup.
"""
items = kwargs.keys() items = kwargs.keys()
inst = None inst = None
if len(items) == 1: if len(items) == 1:

View file

@ -17,7 +17,7 @@ from django.db.models.signals import post_save
from django.db.models.base import Model, ModelBase from django.db.models.base import Model, ModelBase
from django.db.models.signals import pre_delete, post_syncdb from django.db.models.signals import pre_delete, post_syncdb
from evennia.utils import logger from evennia.utils import logger
from evennia.utils.utils import dbref, get_evennia_pids, to_str,calledby from evennia.utils.utils import dbref, get_evennia_pids, to_str
from manager import SharedMemoryManager from manager import SharedMemoryManager
@ -53,6 +53,7 @@ class SharedMemoryModelBase(ModelBase):
or try to retrieve one from the class-wide cache by inferring the pk value from or try to retrieve one from the class-wide cache by inferring the pk value from
`args` and `kwargs`. If instance caching is enabled for this class, the cache is `args` and `kwargs`. If instance caching is enabled for this class, the cache is
populated whenever possible (ie when it is possible to infer the pk value). populated whenever possible (ie when it is possible to infer the pk value).
""" """
def new_instance(): def new_instance():
return super(SharedMemoryModelBase, cls).__call__(*args, **kwargs) return super(SharedMemoryModelBase, cls).__call__(*args, **kwargs)
@ -75,6 +76,7 @@ class SharedMemoryModelBase(ModelBase):
""" """
Prepare the cache, making sure that proxies of the same db base Prepare the cache, making sure that proxies of the same db base
share the same cache. share the same cache.
""" """
# the dbmodel is either the proxy base or ourselves # the dbmodel is either the proxy base or ourselves
dbmodel = cls._meta.proxy_for_model if cls._meta.proxy else cls dbmodel = cls._meta.proxy_for_model if cls._meta.proxy else cls
@ -89,15 +91,17 @@ class SharedMemoryModelBase(ModelBase):
""" """
Field shortcut creation: Field shortcut creation:
Takes field names `db_*` and creates property wrappers named without the Takes field names `db_*` and creates property wrappers named
`db_` prefix. So db_key -> key without the `db_` prefix. So db_key -> key
This wrapper happens on the class level, so there is no overhead when creating objects. This wrapper happens on the class level, so there is no
If a class already has a wrapper of the given name, the automatic creation is skipped. overhead when creating objects. If a class already has a
wrapper of the given name, the automatic creation is skipped.
Notes: Notes:
Remember to document this auto-wrapping in the class header, this could seem very Remember to document this auto-wrapping in the class
much like magic to the user otherwise. header, this could seem very much like magic to the user
otherwise.
""" """
attrs["typename"] = cls.__name__ attrs["typename"] = cls.__name__
@ -218,8 +222,10 @@ class SharedMemoryModel(Model):
@classmethod @classmethod
def _get_cache_key(cls, args, kwargs): def _get_cache_key(cls, args, kwargs):
""" """
This method is used by the caching subsystem to infer the PK value from the constructor arguments. This method is used by the caching subsystem to infer the PK
It is used to decide if an instance has to be built or is already in the cache. value from the constructor arguments. It is used to decide if
an instance has to be built or is already in the cache.
""" """
result = None result = None
# Quick hack for my composites work for now. # Quick hack for my composites work for now.
@ -248,9 +254,11 @@ class SharedMemoryModel(Model):
@classmethod @classmethod
def get_cached_instance(cls, id): def get_cached_instance(cls, id):
""" """
Method to retrieve a cached instance by pk value. Returns None when not found Method to retrieve a cached instance by pk value. Returns None
(which will always be the case when caching is disabled for this class). Please when not found (which will always be the case when caching is
note that the lookup will be done even when instance caching is disabled. disabled for this class). Please note that the lookup will be
done even when instance caching is disabled.
""" """
return cls.__dbclass__.__instance_cache__.get(id) return cls.__dbclass__.__instance_cache__.get(id)
@ -261,9 +269,9 @@ class SharedMemoryModel(Model):
Args: Args:
instance (Class instance): the instance to cache. instance (Class instance): the instance to cache.
new (bool, optional): this is the first time this new (bool, optional): this is the first time this instance is
instance is cached (i.e. this is not an update cached (i.e. this is not an update operation like after a
operation like after a db save). db save).
""" """
pk = instance._get_pk_val() pk = instance._get_pk_val()
@ -281,6 +289,7 @@ class SharedMemoryModel(Model):
def get_all_cached_instances(cls): def get_all_cached_instances(cls):
""" """
Return the objects so far cached by idmapper for this class. Return the objects so far cached by idmapper for this class.
""" """
return cls.__dbclass__.__instance_cache__.values() return cls.__dbclass__.__instance_cache__.values()
@ -288,6 +297,7 @@ class SharedMemoryModel(Model):
def _flush_cached_by_key(cls, key, force=True): def _flush_cached_by_key(cls, key, force=True):
""" """
Remove the cached reference. Remove the cached reference.
""" """
try: try:
if force or not cls._idmapper_recache_protection: if force or not cls._idmapper_recache_protection:
@ -312,6 +322,7 @@ class SharedMemoryModel(Model):
""" """
This will clean safe objects from the cache. Use `force` This will clean safe objects from the cache. Use `force`
keyword to remove all objects, safe or not. keyword to remove all objects, safe or not.
""" """
if force: if force:
cls.__dbclass__.__instance_cache__ = {} cls.__dbclass__.__instance_cache__ = {}
@ -326,6 +337,7 @@ class SharedMemoryModel(Model):
""" """
Flush this instance from the instance cache. Use Flush this instance from the instance cache. Use
`force` to override recache_protection for the object. `force` to override recache_protection for the object.
""" """
pk = self._get_pk_val() pk = self._get_pk_val()
if pk and (force or not self._idmapper_recache_protection): if pk and (force or not self._idmapper_recache_protection):
@ -334,12 +346,14 @@ class SharedMemoryModel(Model):
def set_recache_protection(self, mode=True): def set_recache_protection(self, mode=True):
""" """
Set if this instance should be allowed to be recached. Set if this instance should be allowed to be recached.
""" """
self._idmapper_recache_protection = bool(mode) self._idmapper_recache_protection = bool(mode)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
""" """
Delete the object, clearing cache. Delete the object, clearing cache.
""" """
self.flush_from_cache() self.flush_from_cache()
self._is_deleted = True self._is_deleted = True
@ -349,12 +363,11 @@ class SharedMemoryModel(Model):
""" """
Central database save operation. Central database save operation.
Arguments as per Django documentation Notes:
Arguments as per Django documentation.
Calls: Calls `self.at_<fieldname>_postsave(new)`
self.at_<fieldname>_postsave(new) (this is a wrapper set by oobhandler:
# this is a wrapper set by oobhandler: self._oob_at_<fieldname>_postsave())
self._oob_at_<fieldname>_postsave()
""" """
@ -400,6 +413,7 @@ class SharedMemoryModel(Model):
class WeakSharedMemoryModelBase(SharedMemoryModelBase): class WeakSharedMemoryModelBase(SharedMemoryModelBase):
""" """
Uses a WeakValue dictionary for caching instead of a regular one. Uses a WeakValue dictionary for caching instead of a regular one.
""" """
def _prepare(cls): def _prepare(cls):
super(WeakSharedMemoryModelBase, cls)._prepare() super(WeakSharedMemoryModelBase, cls)._prepare()
@ -410,6 +424,7 @@ class WeakSharedMemoryModelBase(SharedMemoryModelBase):
class WeakSharedMemoryModel(SharedMemoryModel): class WeakSharedMemoryModel(SharedMemoryModel):
""" """
Uses a WeakValue dictionary for caching instead of a regular one Uses a WeakValue dictionary for caching instead of a regular one
""" """
__metaclass__ = WeakSharedMemoryModelBase __metaclass__ = WeakSharedMemoryModelBase
class Meta: class Meta:
@ -424,6 +439,7 @@ def flush_cache(**kwargs):
is `True`. is `True`.
Uses a signal so we make sure to catch cascades. Uses a signal so we make sure to catch cascades.
""" """
def class_hierarchy(clslist): def class_hierarchy(clslist):
"""Recursively yield a class hierarchy""" """Recursively yield a class hierarchy"""
@ -448,6 +464,7 @@ post_syncdb.connect(flush_cache)
def flush_cached_instance(sender, instance, **kwargs): def flush_cached_instance(sender, instance, **kwargs):
""" """
Flush the idmapper cache only for a given instance. Flush the idmapper cache only for a given instance.
""" """
# XXX: Is this the best way to make sure we can flush? # XXX: Is this the best way to make sure we can flush?
if not hasattr(instance, 'flush_cached_instance'): if not hasattr(instance, 'flush_cached_instance'):
@ -459,6 +476,7 @@ pre_delete.connect(flush_cached_instance)
def update_cached_instance(sender, instance, **kwargs): def update_cached_instance(sender, instance, **kwargs):
""" """
Re-cache the given instance in the idmapper cache. Re-cache the given instance in the idmapper cache.
""" """
if not hasattr(instance, 'cache_instance'): if not hasattr(instance, 'cache_instance'):
return return
@ -481,6 +499,7 @@ def conditional_flush(max_rmem, force=False):
cache is flushed. cache is flushed.
force (bool, optional): forces a flush, regardless of timeout. force (bool, optional): forces a flush, regardless of timeout.
Defaults to `False`. Defaults to `False`.
""" """
global LAST_FLUSH global LAST_FLUSH
@ -546,6 +565,7 @@ def cache_size(mb=True):
Returns: Returns:
total_num, {objclass:total_num, ...} total_num, {objclass:total_num, ...}
""" """
numtotal = [0] # use mutable to keep reference through recursion numtotal = [0] # use mutable to keep reference through recursion
classdict = {} classdict = {}

View file

@ -1,8 +1,8 @@
""" """
Inlinefunc Inlinefunc
This is a simple inline text language for use to custom-format text This is a simple inline text language for use to custom-format text in
in Evennia. It is applied BEFORE ANSI/MUX parsing is applied. Evennia. It is applied BEFORE ANSI/MUX parsing is applied.
To activate Inlinefunc, settings.INLINEFUNC_ENABLED must be set. To activate Inlinefunc, settings.INLINEFUNC_ENABLED must be set.
@ -52,7 +52,10 @@ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
# inline functions # inline functions
def pad(text, *args, **kwargs): def pad(text, *args, **kwargs):
"Pad to width. pad(text, width=78, align='c', fillchar=' ')" """
Pad to width. pad(text, width=78, align='c', fillchar=' ')
"""
width = _DEFAULT_WIDTH width = _DEFAULT_WIDTH
align = 'c' align = 'c'
fillchar = ' ' fillchar = ' '
@ -67,8 +70,12 @@ def pad(text, *args, **kwargs):
break break
return utils.pad(text, width=width, align=align, fillchar=fillchar) return utils.pad(text, width=width, align=align, fillchar=fillchar)
def crop(text, *args, **kwargs): def crop(text, *args, **kwargs):
"Crop to width. crop(text, width=78, suffix='[...]')" """
Crop to width. crop(text, width=78, suffix='[...]')
"""
width = _DEFAULT_WIDTH width = _DEFAULT_WIDTH
suffix = "[...]" suffix = "[...]"
for iarg, arg in enumerate(args): for iarg, arg in enumerate(args):
@ -80,8 +87,12 @@ def crop(text, *args, **kwargs):
break break
return utils.crop(text, width=width, suffix=suffix) return utils.crop(text, width=width, suffix=suffix)
def wrap(text, *args, **kwargs): def wrap(text, *args, **kwargs):
"Wrap/Fill text to width. fill(text, width=78, indent=0)" """
Wrap/Fill text to width. fill(text, width=78, indent=0)
"""
width = _DEFAULT_WIDTH width = _DEFAULT_WIDTH
indent = 0 indent = 0
for iarg, arg in enumerate(args): for iarg, arg in enumerate(args):
@ -91,16 +102,24 @@ def wrap(text, *args, **kwargs):
indent = int(arg) if arg.isdigit() else indent indent = int(arg) if arg.isdigit() else indent
return utils.wrap(text, width=width, indent=indent) return utils.wrap(text, width=width, indent=indent)
def time(text, *args, **kwargs): def time(text, *args, **kwargs):
"Inserts current time" """
Inserts current time.
"""
import time import time
strformat = "%h %d, %H:%M" strformat = "%h %d, %H:%M"
if args and args[0]: if args and args[0]:
strformat = str(args[0]) strformat = str(args[0])
return time.strftime(strformat) return time.strftime(strformat)
def you(text, *args, **kwargs): def you(text, *args, **kwargs):
"Inserts your name" """
Inserts your name.
"""
name = "You" name = "You"
sess = kwargs.get("session") sess = kwargs.get("session")
if sess and sess.puppet: if sess and sess.puppet:
@ -139,23 +158,40 @@ def _execute_inline_function(funcname, text, session):
Get the enclosed text between {funcname(...) and {/funcname Get the enclosed text between {funcname(...) and {/funcname
and execute the inline function to replace the whole block and execute the inline function to replace the whole block
with the result. with the result.
Note that this lookup is "dumb" - we just grab the first end
tag we find. So to work correctly this function must be called Args:
"inside out" on a nested function tree, so each call only works funcname (str): Inlinefunction identifier.
on a "flat" tag. text (str): Text to process.
session (Session): Session object.
Notes:
This lookup is "dumb" - we just grab the first end tag we find. So
to work correctly this function must be called "inside out" on a
nested function tree, so each call only works on a "flat" tag.
""" """
def subfunc(match): def subfunc(match):
"replace the entire block with the result of the function call" """
replace the entire block with the result of the function call
"""
args = [part.strip() for part in match.group(1).split(",")] args = [part.strip() for part in match.group(1).split(",")]
intext = match.group(2) intext = match.group(2)
kwargs = {"session":session} kwargs = {"session":session}
return _INLINE_FUNCS[funcname][0](intext, *args, **kwargs) return _INLINE_FUNCS[funcname][0](intext, *args, **kwargs)
return _INLINE_FUNCS[funcname][1].sub(subfunc, text) return _INLINE_FUNCS[funcname][1].sub(subfunc, text)
def _execute_inline_single_function(funcname, text, session): def _execute_inline_single_function(funcname, text, session):
""" """
Get the arguments of a single function call (no matching end tag) Get the arguments of a single function call (no matching end tag)
and execute it with an empty text input. and execute it with an empty text input.
Args:
funcname (str): Function identifier.
text (str): String to process.
session (Session): Session id.
""" """
def subfunc(match): def subfunc(match):
"replace the single call with the result of the function call" "replace the single call with the result of the function call"
@ -164,12 +200,20 @@ def _execute_inline_single_function(funcname, text, session):
return _INLINE_FUNCS[funcname][0]("", *args, **kwargs) return _INLINE_FUNCS[funcname][0]("", *args, **kwargs)
return _INLINE_FUNCS[funcname][2].sub(subfunc, text) return _INLINE_FUNCS[funcname][2].sub(subfunc, text)
def parse_inlinefunc(text, strip=False, session=None): def parse_inlinefunc(text, strip=False, session=None):
""" """
Parse inline function-replacement. Parse inline function-replacement.
strip - remove all supported inlinefuncs from text Args:
session - session calling for the parsing text (str): Text to parse.
strip (bool, optional): Remove all supported inlinefuncs from text.
session (bool): Session calling for the parsing.
Returns:
text (str): Parsed text with processed results of
inlinefuncs.
""" """
if strip: if strip:
@ -204,6 +248,7 @@ def parse_inlinefunc(text, strip=False, session=None):
return "".join(outstack) return "".join(outstack)
def _test(): def _test():
# this should all be handled # this should all be handled
s = "This is a text with a{pad(78,c,-)text {pad(5)of{/pad {pad(30)nice{/pad size{/pad inside {pad(4,l)it{/pad." s = "This is a text with a{pad(78,c,-)text {pad(5)of{/pad {pad(30)nice{/pad size{/pad inside {pad(4,l)it{/pad."

View file

@ -24,9 +24,13 @@ _TIMEZONE = None
def log_trace(errmsg=None): def log_trace(errmsg=None):
""" """
Log a traceback to the log. This should be called Log a traceback to the log. This should be called from within an
from within an exception. errmsg is optional and exception.
adds an extra line with added info.
Args:
errmsg (str, optional): Adds an extra line with added info
at the end of the traceback in the log.
""" """
tracestring = format_exc() tracestring = format_exc()
try: try:
@ -49,7 +53,9 @@ def log_err(errmsg):
""" """
Prints/logs an error message to the server log. Prints/logs an error message to the server log.
errormsg: (string) The message to be logged. Args:
errormsg (str): The message to be logged.
""" """
try: try:
errmsg = str(errmsg) errmsg = str(errmsg)
@ -65,7 +71,9 @@ def log_warn(warnmsg):
""" """
Prints/logs any warnings that aren't critical but should be noted. Prints/logs any warnings that aren't critical but should be noted.
warnmsg: (string) The message to be logged. Args:
warnmsg (str): The message to be logged.
""" """
try: try:
warnmsg = str(warnmsg) warnmsg = str(warnmsg)
@ -94,7 +102,10 @@ log_infomsg = log_info
def log_dep(depmsg): def log_dep(depmsg):
""" """
Prints a deprecation message Prints a deprecation message.
Args:
depmsg (str): The deprecation message to log.
""" """
try: try:
depmsg = str(depmsg) depmsg = str(depmsg)
@ -111,9 +122,13 @@ LOG_FILE_HANDLES = {} # holds open log handles
def log_file(msg, filename="game.log"): def log_file(msg, filename="game.log"):
""" """
Arbitrary file logger using threads. Filename defaults to Arbitrary file logger using threads.
'game.log'. All logs will appear in the logs directory and log
entries will start on new lines following datetime info. Args:
filename (str, optional): Defaults to 'game.log'. All logs
will appear in the logs directory and log entries will start
on new lines following datetime info.
""" """
global LOG_FILE_HANDLES, _LOGDIR, _TIMEZONE global LOG_FILE_HANDLES, _LOGDIR, _TIMEZONE
@ -131,6 +146,7 @@ def log_file(msg, filename="game.log"):
# manually or log file won't be written to until the # manually or log file won't be written to until the
# write buffer is full. # write buffer is full.
filehandle.flush() filehandle.flush()
def errback(failure): def errback(failure):
"Catching errors to normal log" "Catching errors to normal log"
log_trace() log_trace()

View file

@ -92,7 +92,10 @@ _handle_dbref = lambda inp: dbid_to_obj(inp, ObjectDB)
def _validate_prototype(key, prototype, protparents, visited): def _validate_prototype(key, prototype, protparents, visited):
"Run validation on a prototype, checking for inifinite regress" """
Run validation on a prototype, checking for inifinite regress.
"""
assert isinstance(prototype, dict) assert isinstance(prototype, dict)
if id(prototype) in visited: if id(prototype) in visited:
raise RuntimeError("%s has infinite nesting of prototypes." % key or prototype) raise RuntimeError("%s has infinite nesting of prototypes." % key or prototype)
@ -110,9 +113,10 @@ def _validate_prototype(key, prototype, protparents, visited):
def _get_prototype(dic, prot, protparents): def _get_prototype(dic, prot, protparents):
""" """
Recursively traverse a prototype dictionary, Recursively traverse a prototype dictionary, including multiple
including multiple inheritance. Use _validate_prototype inheritance. Use _validate_prototype before this, we don't check
before this, we don't check for infinite recursion here. for infinite recursion here.
""" """
if "prototype" in dic: if "prototype" in dic:
# move backwards through the inheritance # move backwards through the inheritance
@ -130,10 +134,11 @@ def _batch_create_object(*objparams):
optimized for speed. It does NOT check and convert various input optimized for speed. It does NOT check and convert various input
so make sure the spawned Typeclass works before using this! so make sure the spawned Typeclass works before using this!
Input: Args:
objsparams - each argument should be a tuple of arguments for the respective objsparams (any): Aach argument should be a tuple of arguments
creation/add handlers in the following order: for the respective creation/add handlers in the following
(create, permissions, locks, aliases, nattributes, attributes) order: (create, permissions, locks, aliases, nattributes,
attributes)
Returns: Returns:
objects (list): A list of created objects objects (list): A list of created objects
@ -168,16 +173,17 @@ def spawn(*prototypes, **kwargs):
Spawn a number of prototyped objects. Each argument should be a Spawn a number of prototyped objects. Each argument should be a
prototype dictionary. prototype dictionary.
keyword args: Kwargs:
prototype_modules - a python-path to a prototype_modules (str or list): A python-path to a prototype
prototype module, or a list of such paths. These will be used module, or a list of such paths. These will be used to build
to build the global protparents dictionary accessible by the the global protparents dictionary accessible by the input
input prototypes. If not given, it will instead look for modules prototypes. If not given, it will instead look for modules
defined by settings.PROTOTYPE_MODULES. defined by settings.PROTOTYPE_MODULES.
prototype_parents - a dictionary holding a custom prototype-parent dictionary. Will prototype_parents (dict): A dictionary holding a custom
overload same-named prototypes from prototype_modules. prototype-parent dictionary. Will overload same-named
return_prototypes - only return a list of the prototype-parents prototypes from prototype_modules.
(no object creation happens) return_prototypes (bool): Only return a list of the
prototype-parents (no object creation happens)
""" """
protparents = {} protparents = {}

View file

@ -82,8 +82,16 @@ class TextToHTMLparser(object):
def re_color(self, text): def re_color(self, text):
""" """
Replace ansi colors with html color class names. Replace ansi colors with html color class names. Let the
Let the client choose how it will display colors, if it wishes to. """ client choose how it will display colors, if it wishes to.
Args:
text (str): the string with color to replace.
Returns:
text (str): Re-colored text.
"""
for colorname, regex in self.re_fgs: for colorname, regex in self.re_fgs:
text = regex.sub(r'''<span class="%s">\1</span>''' % colorname, text) text = regex.sub(r'''<span class="%s">\1</span>''' % colorname, text)
for bgname, regex in self.re_bgs: for bgname, regex in self.re_bgs:
@ -91,19 +99,56 @@ class TextToHTMLparser(object):
return self.re_normal.sub("", text) return self.re_normal.sub("", text)
def re_bold(self, text): def re_bold(self, text):
"Clean out superfluous hilights rather than set <strong>to make it match the look of telnet." """
Clean out superfluous hilights rather than set <strong>to make
it match the look of telnet.
Args:
text (str): Text to process.
Returns:
text (str): Processed text.
"""
return self.re_hilite.sub(r'<strong>\1</strong>', text) return self.re_hilite.sub(r'<strong>\1</strong>', text)
def re_underline(self, text): def re_underline(self, text):
"Replace ansi underline with html underline class name." """
Replace ansi underline with html underline class name.
Args:
text (str): Text to process.
Returns:
text (str): Processed text.
"""
return self.re_uline.sub(r'<span class="underline">\1</span>', text) return self.re_uline.sub(r'<span class="underline">\1</span>', text)
def remove_bells(self, text): def remove_bells(self, text):
"Remove ansi specials" """
Remove ansi specials
Args:
text (str): Text to process.
Returns:
text (str): Processed text.
"""
return text.replace('\07', '') return text.replace('\07', '')
def remove_backspaces(self, text): def remove_backspaces(self, text):
"Removes special escape sequences" """
Removes special escape sequences
Args:
text (str): Text to process.
Returns:
text (str): Processed text.
"""
backspace_or_eol = r'(.\010)|(\033\[K)' backspace_or_eol = r'(.\010)|(\033\[K)'
n = 1 n = 1
while n > 0: while n > 0:
@ -111,11 +156,29 @@ class TextToHTMLparser(object):
return text return text
def convert_linebreaks(self, text): def convert_linebreaks(self, text):
"Extra method for cleaning linebreaks" """
Extra method for cleaning linebreaks
Args:
text (str): Text to process.
Returns:
text (str): Processed text.
"""
return text.replace(r'\n', r'<br>') return text.replace(r'\n', r'<br>')
def convert_urls(self, text): def convert_urls(self, text):
"Replace urls (http://...) by valid HTML" """
Replace urls (http://...) by valid HTML.
Args:
text (str): Text to process.
Returns:
text (str): Processed text.
"""
regexp = r"((ftp|www|http)(\W+\S+[^).,:;?\]\}(\<span\>) \r\n$\"\']+))" regexp = r"((ftp|www|http)(\W+\S+[^).,:;?\]\}(\<span\>) \r\n$\"\']+))"
# -> added target to output prevent the web browser from attempting to # -> added target to output prevent the web browser from attempting to
# change pages (and losing our webclient session). # change pages (and losing our webclient session).
@ -123,14 +186,31 @@ class TextToHTMLparser(object):
def convert_links(self, text): def convert_links(self, text):
""" """
Replaces links with HTML code Replaces links with HTML code.
Args:
text (str): Text to process.
Returns:
text (str): Processed text.
""" """
html = "<a href='#' onclick='websocket.send(\"CMD\\1\"); return false;'>\\2</a>" html = "<a href='#' onclick='websocket.send(\"CMD\\1\"); return false;'>\\2</a>"
repl = self.re_link.sub(html, text) repl = self.re_link.sub(html, text)
return repl return repl
def do_sub(self, m): def do_sub(self, match):
"Helper method to be passed to re.sub." """
Helper method to be passed to re.sub,
for handling all substitutions.
Args:
match (re.Matchobject): Match for substitution.
Returns:
text (str): Processed text.
"""
c = m.groupdict() c = m.groupdict()
if c['htmlchars']: if c['htmlchars']:
return cgi.escape(c['htmlchars']) return cgi.escape(c['htmlchars'])
@ -145,8 +225,15 @@ class TextToHTMLparser(object):
def parse(self, text, strip_ansi=False): def parse(self, text, strip_ansi=False):
""" """
Main access function, converts a text containing Main access function, converts a text containing ANSI codes
ANSI codes into html statements. into html statements.
Args:
text (str): Text to process.
strip_ansi (bool, optional):
Returns:
text (str): Parsed text.
""" """
# parse everything to ansi first # parse everything to ansi first
text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=False, mxp=True) text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=False, mxp=True)

View file

@ -228,7 +228,7 @@ def make_hybi07_frame_dwim(buf):
""" """
Make a HyBi-07 frame with binary or text data according to the type of buf. Make a HyBi-07 frame with binary or text data according to the type of buf.
""" """
# TODO: eliminate magic numbers. # TODO: eliminate magic numbers.
if isinstance(buf, str): if isinstance(buf, str):
return make_hybi07_frame(buf, opcode=0x2) return make_hybi07_frame(buf, opcode=0x2)
@ -349,7 +349,7 @@ class WebSocketProtocol(ProtocolWrapper):
def setBinaryMode(self, mode): def setBinaryMode(self, mode):
""" """
If True, send str as binary and unicode as text. If True, send str as binary and unicode as text.
Defaults to false for backwards compatibility. Defaults to false for backwards compatibility.
""" """
self.do_binary_frames = bool(mode) self.do_binary_frames = bool(mode)

View file

@ -40,22 +40,34 @@ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
def is_iter(iterable): def is_iter(iterable):
""" """
Checks if an object behaves iterably. However, Checks if an object behaves iterably.
strings are not accepted as iterable (although
they are actually iterable), since string iterations
are usually not what we want to do with a string.
"""
# use a try..except here to avoid a property
# lookup when using this from a typeclassed entity
try:
_GA(iterable, '__iter__')
return True
except AttributeError:
return False
Args:
iterable (any): Entity to check for iterability.
Returns:
is_iterable (bool): If `iterable` is iterable or not.
Notes:
Strings are *not* accepted as iterable (although they are
actually iterable), since string iterations are usually not
what we want to do with a string.
"""
return hasattr(iterable, '__iter__')
def make_iter(obj): def make_iter(obj):
"Makes sure that the object is always iterable." """
Makes sure that the object is always iterable.
Args:
obj (any): Object to make iterable.
Returns:
iterable (list or iterable): The same object
passed-through or made iterable.
"""
return not hasattr(obj, '__iter__') and [obj] or obj return not hasattr(obj, '__iter__') and [obj] or obj
@ -63,10 +75,15 @@ def wrap(text, width=_DEFAULT_WIDTH, indent=0):
""" """
Safely wrap text to a certain number of characters. Safely wrap text to a certain number of characters.
text: (str) The text to wrap. Args:
width: (int) The number of characters to wrap to. text (str): The text to wrap.
indent: (int) How much to indent new lines (the first line width (int, optional): The number of characters to wrap to.
will not be indented) indent (int): How much to indent new lines (the first line
will not be indented)
Returns:
text (str): Properly wrapped text.
""" """
if not text: if not text:
return "" return ""
@ -76,10 +93,21 @@ def wrap(text, width=_DEFAULT_WIDTH, indent=0):
# alias - fill # alias - fill
fill = wrap fill = wrap
def pad(text, width=_DEFAULT_WIDTH, align="c", fillchar=" "): def pad(text, width=_DEFAULT_WIDTH, align="c", fillchar=" "):
""" """
Pads to a given width, align is one of c,l,r Pads to a given width.
and fillchar defaults to the space character.
Args:
text (str): Text to pad.
width (int, optional): The width to pad to, in characters.
align (str, optional): This is one of 'c', 'l' or 'r' (center,
left or right).
fillchar (str, optional): The character to fill with.
Returns:
text (str): The padded text.
""" """
align = align if align in ('c', 'l', 'r') else 'c' align = align if align in ('c', 'l', 'r') else 'c'
fillchar = fillchar[0] if fillchar else " " fillchar = fillchar[0] if fillchar else " "
@ -90,12 +118,24 @@ def pad(text, width=_DEFAULT_WIDTH, align="c", fillchar=" "):
else: else:
return text.center(width, fillchar) return text.center(width, fillchar)
def crop(text, width=_DEFAULT_WIDTH, suffix="[...]"): def crop(text, width=_DEFAULT_WIDTH, suffix="[...]"):
""" """
Crop text to a certain width, adding `suffix` to show that the line Crop text to a certain width, throwing away text from too-long
continues. Cropping will be done so that the suffix will also fit lines.
within the given width. If width is too small to fit both crop
and suffix, crop without the suffix. Args:
text (str): Text to crop.
width (int, optional): Width of line to crop, in characters.
suffix (str, optional): This is appended to the end of cropped
lines to show that the line actually continues. Cropping
will be done so that the suffix will also fit within the
given width. If width is too small to fit both crop and
suffix, the suffix will be dropped.
Returns:
text (str): The cropped text.
""" """
utext = to_unicode(text) utext = to_unicode(text)
@ -110,11 +150,19 @@ def crop(text, width=_DEFAULT_WIDTH, suffix="[...]"):
def dedent(text): def dedent(text):
""" """
Safely clean all whitespace at the left Safely clean all whitespace at the left of a paragraph.
of a paragraph. This is useful for preserving
triple-quoted string indentation while still Args:
shifting it all to be next to the left edge of text (str): The text to dedent.
the display.
Returns:
text (str): Dedented string.
Notes:
This is useful for preserving triple-quoted string indentation
while still shifting it all to be next to the left edge of the
display.
""" """
if not text: if not text:
return "" return ""
@ -123,19 +171,31 @@ def dedent(text):
def list_to_string(inlist, endsep="and", addquote=False): def list_to_string(inlist, endsep="and", addquote=False):
""" """
This pretty-formats a list as string output, adding This pretty-formats a list as string output, adding an optional
an optional alternative separator to the second to last entry. alternative separator to the second to last entry. If `addquote`
If `addquote` is `True`, the outgoing strings will be surrounded by quotes. is `True`, the outgoing strings will be surrounded by quotes.
Args:
inlist (list): The list to print.
endsep (str, optional): If set, the last item separator will
be replaced with this value.
addquote (bool, optional): This will surround all outgoing
values with double quotes.
Returns:
liststr (str): The list represented as a string.
Examples: Examples:
```
no endsep: ```python
[1,2,3] -> '1, 2, 3' # no endsep:
with endsep=='and': [1,2,3] -> '1, 2, 3'
[1,2,3] -> '1, 2 and 3' # with endsep=='and':
with addquote and endsep [1,2,3] -> '1, 2 and 3'
[1,2,3] -> '"1", "2" and "3"' # with addquote and endsep
``` [1,2,3] -> '"1", "2" and "3"'
```
""" """
if not endsep: if not endsep:
endsep = "," endsep = ","
@ -155,10 +215,17 @@ def list_to_string(inlist, endsep="and", addquote=False):
def wildcard_to_regexp(instring): def wildcard_to_regexp(instring):
""" """
Converts a player-supplied string that may have wildcards in it to regular Converts a player-supplied string that may have wildcards in it to
expressions. This is useful for name matching. regular expressions. This is useful for name matching.
Args:
instring (string): A string that may potentially contain
wildcards (`*` or `?`).
Returns:
regex (str): A string where wildcards were replaced with
regular expressions.
instring: (string) A string that may potentially contain wildcards (`*` or `?`).
""" """
regexp_string = "" regexp_string = ""
@ -182,10 +249,13 @@ def time_format(seconds, style=0):
""" """
Function to return a 'prettified' version of a value in seconds. Function to return a 'prettified' version of a value in seconds.
Style 0: 1d 08:30 Args:
Style 1: 1d seconds (int): Number if seconds to format.
Style 2: 1 day, 8 hours, 30 minutes style (int): One of the following styles:
Style 3: 1 day, 8 hours, 30 minutes, 10 seconds 0. "1d 08:30"
1. "1d"
2. "1 day, 8 hours, 30 minutes"
3. "1 day, 8 hours, 30 minutes, 10 seconds"
""" """
if seconds < 0: if seconds < 0:
seconds = 0 seconds = 0
@ -208,8 +278,8 @@ def time_format(seconds, style=0):
retval = '%id %02i:%02i' % (days, hours, minutes,) retval = '%id %02i:%02i' % (days, hours, minutes,)
else: else:
retval = '%02i:%02i' % (hours, minutes,) retval = '%02i:%02i' % (hours, minutes,)
return retval return retval
elif style is 1: elif style is 1:
""" """
Simple, abbreviated form that only shows the highest time amount. Simple, abbreviated form that only shows the highest time amount.
@ -275,8 +345,15 @@ def time_format(seconds, style=0):
def datetime_format(dtobj): def datetime_format(dtobj):
""" """
Takes a datetime object instance (e.g. from Django's `DateTimeField`) Pretty-prints the time since a given time.
and returns a string describing how long ago that date was.
Args:
dtobj (datetime): An datetime object, e.g. from Django's
`DateTimeField`.
Returns:
deltatime (str): A string describing how long ago `dtobj`
took place.
""" """
@ -302,16 +379,26 @@ def datetime_format(dtobj):
def host_os_is(osname): def host_os_is(osname):
""" """
Check to see if the host OS matches the query. Check to see if the host OS matches the query.
Common osnames are
posix Args:
nt osname (str): Common names are "posix" (linux/unix/mac) and
"nt" (windows).
Args:
is_os (bool): If the os matches or not.
""" """
if os.name == osname: return os.name == osname
return True
return False
def get_evennia_version(): def get_evennia_version():
"""
Helper method for getting the current evennia version.
Returns:
version (str): The version string.
"""
import evennia import evennia
return evennia.__version__ return evennia.__version__
@ -345,11 +432,17 @@ def pypath_to_realpath(python_path, file_ending='.py'):
def dbref(dbref, reqhash=True): def dbref(dbref, reqhash=True):
""" """
Converts/checks if input is a valid dbref. If `reqhash` is set, Converts/checks if input is a valid dbref.
only input strings on the form '#N', where N is an integer is
accepted. Otherwise strings '#N', 'N' and integers N are all Args:
accepted. dbref (int or str): A datbase ref on the form N or #N.
Output is the integer part. reqhash (bool, optional): Require the #N form to accept
input as a valid dbref.
Returns:
dbref (int or None): The integer part of the dbref or `None`
if input was not a valid dbref.
""" """
if reqhash: if reqhash:
num = (int(dbref.lstrip('#')) if (isinstance(dbref, basestring) and num = (int(dbref.lstrip('#')) if (isinstance(dbref, basestring) and
@ -366,10 +459,22 @@ def dbref(dbref, reqhash=True):
def dbid_to_obj(inp, objclass, raise_errors=True): def dbid_to_obj(inp, objclass, raise_errors=True):
""" """
Convert a #dbid to a valid object of `objclass`. `objclass` Convert a #dbid to a valid object.
should be a valid object class to filter against (`objclass.filter` ...)
If not `raise_errors` is set, this will swallow errors of non-existing Args:
objects. inp (str or int): A valid dbref.
objclass (class): A valid django model to filter against.
raise_errors (bool, optional): Whether to raise errors
or return `None` on errors.
Returns:
obj (Object or None): An entity loaded from the dbref.
Raises:
Exception: If `raise_errors` is `True` and
`objclass.objects.get(id=dbref)` did not return a valid
object.
""" """
dbid = dbref(inp) dbid = dbref(inp)
if not dbid: if not dbid:
@ -389,13 +494,28 @@ def dbid_to_obj(inp, objclass, raise_errors=True):
raise raise
return inp return inp
def to_unicode(obj, encoding='utf-8', force_string=False): def to_unicode(obj, encoding='utf-8', force_string=False):
""" """
This decodes a suitable object to the unicode format. Note that This decodes a suitable object to the unicode format.
one needs to encode it back to utf-8 before writing to disk or
printing. Note that non-string objects are let through without Args:
conversion - this is important for e.g. Attributes. Use obj (any): Object to decode to unicode.
`force_string` to enforce conversion of objects to string. encoding (str, optional): The encoding type to use for the
dedoding.
force_string (bool, optional): Always convert to string, no
matter what type `obj` is initially.
Returns:
result (unicode or any): Will return a unicode object if input
was a string. If input was not a string, the original will be
returned unchanged unless `force_string` is also set.
Notes:
One needs to encode the obj back to utf-8 before writing to disk
or printing. That non-string objects are let through without
conversion is important for e.g. Attributes.
""" """
if force_string and not isinstance(obj, basestring): if force_string and not isinstance(obj, basestring):
@ -427,10 +547,20 @@ def to_unicode(obj, encoding='utf-8', force_string=False):
def to_str(obj, encoding='utf-8', force_string=False): def to_str(obj, encoding='utf-8', force_string=False):
""" """
This encodes a unicode string back to byte-representation, This encodes a unicode string back to byte-representation,
for printing, writing to disk etc. Note that non-string for printing, writing to disk etc.
objects are let through without modification - this is
required e.g. for Attributes. Use `force_string` to force Args:
conversion of objects to strings. obj (any): Object to encode to bytecode.
encoding (str, optional): The encoding type to use for the
encoding.
force_string (bool, optional): Always convert to string, no
matter what type `obj` is initially.
Notes:
Non-string objects are let through without modification - this
is required e.g. for Attributes. Use `force_string` to force
conversion of objects to strings.
""" """
if force_string and not isinstance(obj, basestring): if force_string and not isinstance(obj, basestring):
@ -460,8 +590,16 @@ def validate_email_address(emailaddress):
""" """
Checks if an email address is syntactically correct. Checks if an email address is syntactically correct.
(This snippet was adapted from Args:
http://commandline.org.uk/python/email-syntax-check.) emailaddress (str): Email address to validate.
Returns:
is_valid (bool): If this is a valid email or not.
Notes.
(This snippet was adapted from
http://commandline.org.uk/python/email-syntax-check.)
""" """
emailaddress = r"%s" % emailaddress emailaddress = r"%s" % emailaddress
@ -498,11 +636,23 @@ def validate_email_address(emailaddress):
def inherits_from(obj, parent): def inherits_from(obj, parent):
""" """
Takes an object and tries to determine if it inherits at any distance Takes an object and tries to determine if it inherits at *any*
from parent. What differs this function from e.g. `isinstance()` distance from parent.
is that `obj` may be both an instance and a class, and parent
may be an instance, a class, or the python path to a class (counting Args:
from the evennia root directory). obj (any): Object to analyze. This may be either an instance
or a class.
parent (any): Can be either instance, class or python path to class.
Returns:
inherits_from (bool): If `parent` is a parent to `obj` or not.
Notes:
What differs this function from e.g. `isinstance()` is that `obj`
may be both an instance and a class, and parent may be an
instance, a class, or the python path to a class (counting from
the evennia root directory).
""" """
if callable(obj): if callable(obj):
@ -524,9 +674,13 @@ def inherits_from(obj, parent):
def server_services(): def server_services():
""" """
Lists all services active on the Server. Observe that Lists all services active on the Server. Observe that since
since services are launched in memory, this function will services are launched in memory, this function will only return
only return any results if called from inside the game. any results if called from inside the game.
Returns:
services (dict): A dict of available services.
""" """
from evennia.server.sessionhandler import SESSIONS from evennia.server.sessionhandler import SESSIONS
if hasattr(SESSIONS, "server") and hasattr(SESSIONS.server, "services"): if hasattr(SESSIONS, "server") and hasattr(SESSIONS.server, "services"):
@ -543,7 +697,13 @@ def uses_database(name="sqlite3"):
Checks if the game is currently using a given database. This is a Checks if the game is currently using a given database. This is a
shortcut to having to use the full backend name. shortcut to having to use the full backend name.
name - one of 'sqlite3', 'mysql', 'postgresql_psycopg2' or 'oracle' Args:
name (str): One of 'sqlite3', 'mysql', 'postgresql_psycopg2'
or 'oracle'.
Returns:
uses (bool): If the given database is used or not.
""" """
try: try:
engine = settings.DATABASES["default"]["ENGINE"] engine = settings.DATABASES["default"]["ENGINE"]
@ -555,17 +715,21 @@ def uses_database(name="sqlite3"):
def delay(delay=2, callback=None, retval=None): def delay(delay=2, callback=None, retval=None):
""" """
Delay the return of a value. Delay the return of a value.
Inputs:
delay (int) - the delay in seconds Args:
callback (func() or func(retval)) - if given, will be called without delay (int): The delay in seconds
arguments or with `retval` after delay seconds. callback (callable, optional): Will be called without arguments
retval (any) - this will be returned by this function after a delay, or with `retval` after delay seconds.
or as input to callback. retval (any, optional): Whis will be returned by this function
after a delay, or as input to callback.
Returns: Returns:
deferred that will fire with callback after `delay` seconds. Note that deferred (deferred): Will fire fire with callback after
if `delay()` is used in the commandhandler callback chain, the callback `delay` seconds. Note that if `delay()` is used in the
chain can be defined directly in the command body and don't need to be commandhandler callback chain, the callback chain can be
specified here. defined directly in the command body and don't need to be
specified here.
""" """
callb = callback or defer.Deferred().callback callb = callback or defer.Deferred().callback
if retval is not None: if retval is not None:
@ -579,14 +743,18 @@ _OBJECTMODELS = None
def clean_object_caches(obj): def clean_object_caches(obj):
""" """
Clean all object caches on the given object. Clean all object caches on the given object.
Args:
obj (Object instace): An object whose caches to clean.
Notes:
This is only the contents cache these days.
""" """
global _TYPECLASSMODELS, _OBJECTMODELS global _TYPECLASSMODELS, _OBJECTMODELS
if not _TYPECLASSMODELS: if not _TYPECLASSMODELS:
from evennia.typeclasses import models as _TYPECLASSMODELS from evennia.typeclasses import models as _TYPECLASSMODELS
#if not _OBJECTMODELS:
# from evennia.objects import models as _OBJECTMODELS
#print "recaching:", obj
if not obj: if not obj:
return return
# contents cache # contents cache
@ -612,45 +780,39 @@ def run_async(to_execute, *args, **kwargs):
""" """
Runs a function or executes a code snippet asynchronously. Runs a function or executes a code snippet asynchronously.
Inputs: Args:
to_execute (callable) - if this is a callable, it will to_execute (callable): If this is a callable, it will be
be executed with *args and non-reserved *kwargs as executed with *args and non-reserved *kwargs as arguments.
arguments. The callable will be executed using ProcPool, or in a thread
The callable will be executed using ProcPool, or in if ProcPool is not available.
a thread if ProcPool is not available.
reserved kwargs: Kwargs:
'at_return' -should point to a callable with one argument. at_return (callable): Should point to a callable with one
It will be called with the return value from argument. It will be called with the return value from
to_execute. to_execute.
'at_return_kwargs' - this dictionary which be used as keyword at_return_kwargs (dict): This dictionary will be used as
arguments to the at_return callback. keyword arguments to the at_return callback.
'at_err' - this will be called with a Failure instance if at_err (callable): This will be called with a Failure instance
there is an error in to_execute. if there is an error in to_execute.
'at_err_kwargs' - this dictionary will be used as keyword at_err_kwargs (dict): This dictionary will be used as keyword
arguments to the at_err errback. arguments to the at_err errback.
*args - these args will be used Notes:
as arguments for that function. If to_execute is a string All other `*args` and `**kwargs` will be passed on to
*args are not used. `to_execute`. Run_async will relay executed code to a thread
*kwargs - these kwargs will be used or procpool.
as keyword arguments in that function. If a string, they
instead are used to define the executable environment
that should be available to execute the code in to_execute.
run_async will relay executed code to a thread or procpool. Use this function with restrain and only for features/commands
that you know has no influence on the cause-and-effect order of your
game (commands given after the async function might be executed before
it has finished). Accessing the same property from different threads
can lead to unpredicted behaviour if you are not careful (this is called a
"race condition").
Use this function with restrain and only for features/commands Also note that some databases, notably sqlite3, don't support access from
that you know has no influence on the cause-and-effect order of your multiple threads simultaneously, so if you do heavy database access from
game (commands given after the async function might be executed before your `to_execute` under sqlite3 you will probably run very slow or even get
it has finished). Accessing the same property from different threads tracebacks.
can lead to unpredicted behaviour if you are not careful (this is called a
"race condition").
Also note that some databases, notably sqlite3, don't support access from
multiple threads simultaneously, so if you do heavy database access from
your `to_execute` under sqlite3 you will probably run very slow or even get
tracebacks.
""" """
@ -676,9 +838,12 @@ def run_async(to_execute, *args, **kwargs):
def check_evennia_dependencies(): def check_evennia_dependencies():
""" """
Checks the versions of Evennia's dependencies including making Checks the versions of Evennia's dependencies including making
some checks for runtime libraries some checks for runtime libraries.
Returns:
result (bool): `False` if a show-stopping version mismatch is
found.
Returns False if a show-stopping version mismatch is found.
""" """
# check main dependencies # check main dependencies
@ -711,6 +876,14 @@ def check_evennia_dependencies():
def has_parent(basepath, obj): def has_parent(basepath, obj):
""" """
Checks if `basepath` is somewhere in `obj`s parent tree. Checks if `basepath` is somewhere in `obj`s parent tree.
Args:
basepath (str): Python dotpath to compare against obj path.
obj (any): Object whose path is to be checked.
Returns:
has_parent (bool): If the check was successful or not.
""" """
try: try:
return any(cls for cls in obj.__class__.mro() return any(cls for cls in obj.__class__.mro()
@ -726,15 +899,15 @@ def mod_import(module):
A generic Python module loader. A generic Python module loader.
Args: Args:
module - this can be either a Python path (dot-notation like module (str, module): This can be either a Python path
`evennia.objects.models`), an absolute path (dot-notation like `evennia.objects.models`), an absolute path
(e.g. `/home/eve/evennia/evennia/objects.models.py`) (e.g. `/home/eve/evennia/evennia/objects.models.py`) or an
or an already imported module object (e.g. `models`) already imported module object (e.g. `models`)
Returns: Returns:
an imported module. If the input argument was already a model, module (module or None): An imported module. If the input argument was
this is returned as-is, otherwise the path is parsed and imported. already a module, this is returned as-is, otherwise the path is
Error: parsed and imported. Returns `None` and logs error if import failed.
returns `None`. The error is also logged.
""" """
def log_trace(errmsg=None): def log_trace(errmsg=None):
@ -800,8 +973,21 @@ def mod_import(module):
def all_from_module(module): def all_from_module(module):
""" """
Return all global-level variables from a module as a dict. Return all global-level variables from a module.
Ignores modules and variable names starting with an underscore.
Args:
module (str, module): This can be either a Python path
(dot-notation like `evennia.objects.models`), an absolute path
(e.g. `/home/eve/evennia/evennia/objects.models.py`) or an
already imported module object (e.g. `models`)
Returns:
variables (dict): A dict of {variablename: variable} for all
variables in the given module.
Notes:
Ignores modules and variable names starting with an underscore.
""" """
mod = mod_import(module) mod = mod_import(module)
if not mod: if not mod:
@ -812,23 +998,24 @@ def all_from_module(module):
def variable_from_module(module, variable=None, default=None): def variable_from_module(module, variable=None, default=None):
""" """
Retrieve a variable or list of variables from a module. The variable(s) Retrieve a variable or list of variables from a module. The
must be defined globally in the module. If no variable is given (or a variable(s) must be defined globally in the module. If no variable
list entry is `None`), all global variables are extracted from the module. is given (or a list entry is `None`), all global variables are
extracted from the module.
If `module` cannot be imported or a given `variable` not found, `default`
is returned.
Args: Args:
module (string or module)- python path, absolute path or a module. module (string or module): Python path, absolute path or a module.
variable (string or iterable) - single variable name or iterable of variable (string or iterable, optional): Single variable name or iterable
variable names to extract. of variable names to extract. If not given, all variables in
default (string) - default value to use if a variable fails the module will be returned.
to be extracted. Ignored if `variable` is not given. default (string, optional): Default value to use if a variable fails to
be extracted. Ignored if `variable` is not given.
Returns: Returns:
a single value or a list of values depending on the type of variables (value or list): A single value or a list of values
`variable` argument. Errors in lists are replaced by the depending on if `variable` is given or not. Errors in lists
`default` argument. are replaced by the `default` argument.
""" """
if not module: if not module:
@ -856,8 +1043,20 @@ def string_from_module(module, variable=None, default=None):
""" """
This is a wrapper for `variable_from_module` that requires return This is a wrapper for `variable_from_module` that requires return
value to be a string to pass. It's primarily used by login screen. value to be a string to pass. It's primarily used by login screen.
if `variable` is not set, returns a list of all string variables in
`module`. Args:
module (string or module): Python path, absolute path or a module.
variable (string or iterable, optional): Single variable name or iterable
of variable names to extract. If not given, all variables in
the module will be returned.
default (string, optional): Default value to use if a variable fails to
be extracted. Ignored if `variable` is not given.
Returns:
variables (value or list): A single (string) value or a list of values
depending on if `variable` is given or not. Errors in lists (such
as the value not being a string) are replaced by the `default` argument.
""" """
val = variable_from_module(module, variable=variable, default=default) val = variable_from_module(module, variable=variable, default=default)
if val: if val:
@ -868,23 +1067,38 @@ def string_from_module(module, variable=None, default=None):
return result if result else default return result if result else default
return default return default
def random_string_from_module(module): def random_string_from_module(module):
""" """
Returns a random global string from a module. Returns a random global string from a module.
Args:
module (string or module): Python path, absolute path or a module.
Returns:
random (string): A random stribg variable from `module`.
""" """
return random.choice(string_from_module(module)) return random.choice(string_from_module(module))
def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None): def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None):
""" """
Import a variable based on a fuzzy path. First the literal Import a variable based on a fuzzy path. First the literal
`path` will be tried, then all given `defaultpaths` will be `path` will be tried, then all given `defaultpaths` will be
prepended to see a match is found. prepended to see a match is found.
path - full or partial python path. Args:
variable - name of variable to import from module. path (str): Full or partial python path.
defaultpaths - an iterable of python paths to attempt variable (str): Name of variable to import from module.
in order if importing directly from default (string, optional): Default value to use if a variable fails to
`path` doesn't work. be extracted. Ignored if `variable` is not given.
defaultpaths (iterable, options): Python paths to attempt in order if
importing directly from `path` doesn't work.
Returns:
value (any): The variable imported from the module, or `default`, if
not found.
""" """
paths = [path] + make_iter(defaultpaths) paths = [path] + make_iter(defaultpaths)
for modpath in paths: for modpath in paths:
@ -898,13 +1112,23 @@ def fuzzy_import_from_module(path, variable, default=None, defaultpaths=None):
return getattr(mod, variable, default) return getattr(mod, variable, default)
return default return default
def class_from_module(path, defaultpaths=None): def class_from_module(path, defaultpaths=None):
""" """
Return a class from a module, given the module's path. This is Return a class from a module, given the module's path. This is
primarily used to convert db_typeclass_path:s to classes. primarily used to convert db_typeclass_path:s to classes.
if a list of `defaultpaths` is given, try subsequent runs by Args:
prepending those paths to the given `path`. path (str): Full Python dot-path to module.
defaultpaths (iterable, optional): If a direc import from `path` fails,
try subsequent imports by prepending those paths to `path`.
Returns:
class (Class): An uninstatiated class recovered from path.
Raises:
ImportError: If all loading failed.
""" """
cls = None cls = None
if defaultpaths: if defaultpaths:
@ -949,14 +1173,10 @@ object_from_module = class_from_module
def init_new_player(player): def init_new_player(player):
""" """
Helper method to call all hooks, set flags etc on a newly created Deprecated.
player (and potentially their character, if it exists already).
""" """
# the FIRST_LOGIN flags are necessary for the system to call from evennia.utils import logger
# the relevant first-login hooks. logger.log_depmsg("evennia.utils.utils.init_new_player is DEPRECATED and should not be used.")
#if player.character:
# player.character.db.FIRST_LOGIN = True
player.db.FIRST_LOGIN = True
def string_similarity(string1, string2): def string_similarity(string1, string2):
@ -967,8 +1187,14 @@ def string_similarity(string1, string2):
The measure-vectors used is simply a "bag of words" type histogram The measure-vectors used is simply a "bag of words" type histogram
(but for letters). (but for letters).
The function returns a value 0...1 rating how similar the two strings Args:
are. The strings can contain multiple words. string1 (str): String to compare (may contain any number of words).
string2 (str): Second string to compare (any number of words).
Returns:
similarity (float): A value 0...1 rating how similar the two
strings are.
""" """
vocabulary = set(list(string1 + string2)) vocabulary = set(list(string1 + string2))
vec1 = [string1.count(v) for v in vocabulary] vec1 = [string1.count(v) for v in vocabulary]