PEP8 cleanup of the entire codebase. Unchanged are many cases of too-long lines, partly because of the rewrite they would require but also because splitting many lines up would make the code harder to read. Also the third-party libraries (idmapper, prettytable etc) were not cleaned.

This commit is contained in:
Griatch 2013-11-14 19:31:17 +01:00
parent 30b7d2a405
commit 1ae17bcbe4
154 changed files with 5613 additions and 4054 deletions

View file

@ -59,6 +59,7 @@ ANSI_SPACE = " "
# Escapes
ANSI_ESCAPES = ("{{", "%%", "\\\\")
class ANSIParser(object):
"""
A class that parses ansi markup
@ -75,10 +76,11 @@ class ANSIParser(object):
# MUX-style mappings %cr %cn etc
self.mux_ansi_map = [
# commented out by default; they (especially blink) are potentially annoying
(r'%r', ANSI_RETURN),
(r'%t', ANSI_TAB),
(r'%b', ANSI_SPACE),
# commented out by default; they (especially blink) are
# potentially annoying
(r'%r', ANSI_RETURN),
(r'%t', ANSI_TAB),
(r'%b', ANSI_SPACE),
#(r'%cf', ANSI_BLINK),
#(r'%ci', ANSI_INVERSE),
(r'%cr', ANSI_RED),
@ -118,18 +120,18 @@ class ANSIParser(object):
(r'{M', normal + ANSI_MAGENTA),
(r'{c', hilite + ANSI_CYAN),
(r'{C', normal + ANSI_CYAN),
(r'{w', hilite + ANSI_WHITE), # pure white
(r'{W', normal + ANSI_WHITE), #light grey
(r'{x', hilite + ANSI_BLACK), #dark grey
(r'{X', normal + ANSI_BLACK), #pure black
(r'{n', normal) #reset
(r'{w', hilite + ANSI_WHITE), # pure white
(r'{W', normal + ANSI_WHITE), # light grey
(r'{x', hilite + ANSI_BLACK), # dark grey
(r'{X', normal + ANSI_BLACK), # pure black
(r'{n', normal) # reset
]
# xterm256 {123, %c134,
self.xterm256_map = [
(r'%c([0-5]{3})', self.parse_rgb), # %c123 - foreground colour
(r'%c(b[0-5]{3})', self.parse_rgb), # %cb123 - background colour
(r'%c(b[0-5]{3})', self.parse_rgb), # %cb123 - background colour
(r'{([0-5]{3})', self.parse_rgb), # {123 - foreground colour
(r'{(b[0-5]{3})', self.parse_rgb) # {b123 - background colour
]
@ -145,7 +147,8 @@ class ANSIParser(object):
# prepare matching ansi codes overall
self.ansi_regex = re.compile("\033\[[0-9;]+m")
# escapes - these double-chars will be replaced with a single instance of each
# escapes - these double-chars will be replaced with a single
# instance of each
self.ansi_escapes = re.compile(r"(%s)" % "|".join(ANSI_ESCAPES), re.DOTALL)
def parse_rgb(self, rgbmatch):
@ -174,37 +177,61 @@ class ANSIParser(object):
#print "ANSI convert:", red, green, blue
# xterm256 not supported, convert the rgb value to ansi instead
if red == green and red == blue and red < 2:
if background: return ANSI_BACK_BLACK
elif red >= 1: return ANSI_HILITE + ANSI_BLACK
else: return ANSI_NORMAL + ANSI_BLACK
if background:
return ANSI_BACK_BLACK
elif red >= 1:
return ANSI_HILITE + ANSI_BLACK
else:
return ANSI_NORMAL + ANSI_BLACK
elif red == green and red == blue:
if background: return ANSI_BACK_WHITE
elif red >= 4: return ANSI_HILITE + ANSI_WHITE
else: return ANSI_NORMAL + ANSI_WHITE
if background:
return ANSI_BACK_WHITE
elif red >= 4:
return ANSI_HILITE + ANSI_WHITE
else:
return ANSI_NORMAL + ANSI_WHITE
elif red > green and red > blue:
if background: return ANSI_BACK_RED
elif red >= 3: return ANSI_HILITE + ANSI_RED
else: return ANSI_NORMAL + ANSI_RED
if background:
return ANSI_BACK_RED
elif red >= 3:
return ANSI_HILITE + ANSI_RED
else:
return ANSI_NORMAL + ANSI_RED
elif red == green and red > blue:
if background: return ANSI_BACK_YELLOW
elif red >= 3: return ANSI_HILITE + ANSI_YELLOW
else: return ANSI_NORMAL + ANSI_YELLOW
if background:
return ANSI_BACK_YELLOW
elif red >= 3:
return ANSI_HILITE + ANSI_YELLOW
else:
return ANSI_NORMAL + ANSI_YELLOW
elif red == blue and red > green:
if background: return ANSI_BACK_MAGENTA
elif red >= 3: return ANSI_HILITE + ANSI_MAGENTA
else: return ANSI_NORMAL + ANSI_MAGENTA
if background:
return ANSI_BACK_MAGENTA
elif red >= 3:
return ANSI_HILITE + ANSI_MAGENTA
else:
return ANSI_NORMAL + ANSI_MAGENTA
elif green > blue:
if background: return ANSI_BACK_GREEN
elif green >= 3: return ANSI_HILITE + ANSI_GREEN
else: return ANSI_NORMAL + ANSI_GREEN
if background:
return ANSI_BACK_GREEN
elif green >= 3:
return ANSI_HILITE + ANSI_GREEN
else:
return ANSI_NORMAL + ANSI_GREEN
elif green == blue:
if background: return ANSI_BACK_CYAN
elif green >= 3: return ANSI_HILITE + ANSI_CYAN
else: return ANSI_NORMAL + ANSI_CYAN
if background:
return ANSI_BACK_CYAN
elif green >= 3:
return ANSI_HILITE + ANSI_CYAN
else:
return ANSI_NORMAL + ANSI_CYAN
else: # mostly blue
if background: return ANSI_BACK_BLUE
elif blue >= 3: return ANSI_HILITE + ANSI_BLUE
else: return ANSI_NORMAL + ANSI_BLUE
if background:
return ANSI_BACK_BLUE
elif blue >= 3:
return ANSI_HILITE + ANSI_BLUE
else:
return ANSI_NORMAL + ANSI_BLUE
def parse_ansi(self, string, strip_ansi=False, xterm256=False):
"""
@ -227,13 +254,14 @@ class ANSIParser(object):
part = sub[0].sub(sub[1], part)
string += "%s%s" % (part, sep[0].strip())
if strip_ansi:
# remove all ansi codes (including those manually inserted in string)
# remove all ansi codes (including those manually
# inserted in string)
string = self.ansi_regex.sub("", string)
return string
ANSI_PARSER = ANSIParser()
#
# Access function
#
@ -245,8 +273,9 @@ def parse_ansi(string, strip_ansi=False, parser=ANSI_PARSER, xterm256=False):
"""
return parser.parse_ansi(string, strip_ansi=strip_ansi, xterm256=xterm256)
def raw(string):
"""
Escapes a string into a form which won't be colorized by the ansi parser.
"""
return string.replace('{','{{').replace('%','%%')
return string.replace('{', '{{').replace('%', '%%')

View file

@ -167,16 +167,18 @@ script = create.create_script()
import re
import codecs
import traceback, sys
from traceback import format_exc
import traceback
import sys
#from traceback import format_exc
from django.conf import settings
from src.utils import logger
from src.utils import utils
from game import settings as settings_module
#from game import settings as settings_module
ENCODINGS = settings.ENCODINGS
CODE_INFO_HEADER = re.compile(r"\(.*?\)")
#------------------------------------------------------------
# Helper function
#------------------------------------------------------------
@ -216,7 +218,7 @@ def read_batchfile(pythonpath, file_ending='.py'):
continue
load_errors = []
err =None
err = None
# We have successfully found and opened the file. Now actually
# try to decode it using the given protocol.
try:
@ -246,6 +248,7 @@ def read_batchfile(pythonpath, file_ending='.py'):
else:
return lines
#------------------------------------------------------------
#
# Batch-command processor
@ -261,9 +264,9 @@ class BatchCommandProcessor(object):
"""
This parses the lines of a batchfile according to the following
rules:
1) # at the beginning of a line marks the end of the command before it.
It is also a comment and any number of # can exist on subsequent
lines (but not inside comments).
1) # at the beginning of a line marks the end of the command before
it. It is also a comment and any number of # can exist on
subsequent lines (but not inside comments).
2) #INSERT at the beginning of a line imports another
batch-cmd file file and pastes it into the batch file as if
it was written there.
@ -323,10 +326,10 @@ class BatchCommandProcessor(object):
curr_cmd = ""
filename = line.lstrip("#INSERT").strip()
insert_commands = self.parse_file(filename)
if insert_commands == None:
if insert_commands is None:
insert_commands = ["{rINSERT ERROR: %s{n" % filename]
commands.extend(insert_commands)
else: #comment
else: #comment
if curr_cmd:
commands.append(curr_cmd.strip())
curr_cmd = ""
@ -341,6 +344,7 @@ class BatchCommandProcessor(object):
commands = [c.strip('\r\n') for c in commands]
return commands
#------------------------------------------------------------
#
# Batch-code processor
@ -350,11 +354,14 @@ class BatchCommandProcessor(object):
def tb_filename(tb):
"Helper to get filename from traceback"
return tb.tb_frame.f_code.co_filename
def tb_iter(tb):
while tb is not None:
yield tb
tb = tb.tb_next
class BatchCodeProcessor(object):
"""
This implements a batch-code processor
@ -368,7 +375,8 @@ class BatchCodeProcessor(object):
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, ...
3) #CODE headers may be of the following form:
#CODE (info) objname, objname2, ...
4) Lines starting with #INSERT are on form #INSERT filename.
3) All lines outside blocks are stripped.
4) All excess whitespace beginning/ending a block is stripped.
@ -378,8 +386,8 @@ class BatchCodeProcessor(object):
# helper function
def parse_line(line):
"""
Identifies the line type: block command, comment, empty or normal code.
Identifies the line type:
block command, comment, empty or normal code.
"""
parseline = line.strip()
@ -432,7 +440,7 @@ class BatchCodeProcessor(object):
# are not checking for cyclic imports!
in_header = False
in_code = False
inserted_codes = self.parse_file(line) or [{'objs':"", 'info':line, 'code':""}]
inserted_codes = self.parse_file(line) or [{'objs': "", 'info': line, 'code': ""}]
for codedict in inserted_codes:
codedict["inserted"] = True
codes.extend(inserted_codes)
@ -444,7 +452,7 @@ class BatchCodeProcessor(object):
in_code = True
# the line is a list of object variable names
# (or an empty list) at this point.
codedict = {'objs':line, 'info':info, 'code':""}
codedict = {'objs': line, 'info': info, 'code': ""}
codes.append(codedict)
elif mode == 'comment' and in_header:
continue
@ -485,7 +493,7 @@ class BatchCodeProcessor(object):
"""
# define the execution environment
environ = "settings_module.configure()"
environdict = {"settings_module":settings}
environdict = {"settings_module": settings}
if extra_environ:
for key, value in extra_environ.items():
environdict[key] = value

View file

@ -43,10 +43,12 @@ _channelhandler = None
# limit symbol import from API
__all__ = ("create_object", "create_script", "create_help_entry", "create_message", "create_channel", "create_player")
__all__ = ("create_object", "create_script", "create_help_entry",
"create_message", "create_channel", "create_player")
_GA = object.__getattribute__
#
# Game Object creation
#
@ -105,7 +107,8 @@ def create_object(typeclass, key=None, location=None,
new_object = new_db_object.typeclass
if not _GA(new_object, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
# this will fail if we gave a typeclass as input and it still
# gave us a default
SharedMemoryModel.delete(new_db_object)
if report_to:
_GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_object.key, typeclass,
@ -122,14 +125,14 @@ def create_object(typeclass, key=None, location=None,
# call the hook method. This is where all at_creation
# customization happens as the typeclass stores custom
# things on its database object.
new_object.basetype_setup() # setup the basics of Exits, Characters etc.
new_object.basetype_setup() # setup the basics of Exits, Characters etc.
new_object.at_object_creation()
# custom-given perms/locks overwrite hooks
if permissions:
new_object.permissions.add(permissions)
if locks:
new_object.locks.add(locks)
new_object.locks.add(locks)
if aliases:
new_object.aliases.add(aliases)
@ -137,7 +140,7 @@ def create_object(typeclass, key=None, location=None,
if home:
new_object.home = home
else:
new_object.home = settings.CHARACTER_DEFAULT_HOME if not nohome else None
new_object.home = settings.CHARACTER_DEFAULT_HOME if not nohome else None
if location:
new_object.move_to(location, quiet=True)
@ -154,6 +157,7 @@ def create_object(typeclass, key=None, location=None,
#alias for create_object
object = create_object
#
# Script creation
#
@ -218,7 +222,8 @@ def create_script(typeclass, key=None, obj=None, locks=None,
new_script = new_db_script.typeclass
if not _GA(new_db_script, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
# this will fail if we gave a typeclass as input and it still
# gave us a default
SharedMemoryModel.delete(new_db_script)
if report_to:
_GA(report_to, "msg")("Error creating %s (%s): %s" % (new_db_script.key, typeclass,
@ -243,13 +248,13 @@ def create_script(typeclass, key=None, obj=None, locks=None,
new_script.key = key
if locks:
new_script.locks.add(locks)
if interval != None:
if interval is not None:
new_script.interval = interval
if start_delay != None:
if start_delay is not None:
new_script.start_delay = start_delay
if repeats != None:
if repeats is not None:
new_script.repeats = repeats
if persistent != None:
if persistent is not None:
new_script.persistent = persistent
# a new created script should usually be started.
@ -261,6 +266,7 @@ def create_script(typeclass, key=None, obj=None, locks=None,
#alias
script = create_script
#
# Help entry creation
#
@ -296,6 +302,7 @@ def create_help_entry(key, entrytext, category="General", locks=None):
# alias
help_entry = create_help_entry
#
# Comm system methods
#
@ -343,6 +350,7 @@ def create_message(senderobj, message, channels=None,
return new_message
message = create_message
def create_channel(key, aliases=None, desc=None,
locks=None, keep_log=True,
typeclass=None):
@ -389,6 +397,7 @@ def create_channel(key, aliases=None, desc=None,
channel = create_channel
#
# Player creation methods
#
@ -456,7 +465,8 @@ def create_player(key, email, password,
new_player = new_db_player.typeclass
if not _GA(new_db_player, "is_typeclass")(typeclass, exact=True):
# this will fail if we gave a typeclass as input and it still gave us a default
# this will fail if we gave a typeclass as input
# and it still gave us a default
SharedMemoryModel.delete(new_db_player)
if report_to:
_GA(report_to, "msg")("Error creating %s (%s):\n%s" % (new_db_player.key, typeclass,
@ -465,7 +475,7 @@ def create_player(key, email, password,
else:
raise Exception(_GA(new_db_player, "typeclass_last_errmsg"))
new_player.basetype_setup() # setup the basic locks and cmdset
new_player.basetype_setup() # setup the basic locks and cmdset
# call hook method (may override default permissions)
new_player.at_player_creation()

View file

@ -50,8 +50,12 @@ if uses_database("mysql") and ServerConfig.objects.get_mysql_db_version() < '5.6
else:
_DATESTRING = "%Y:%m:%d-%H:%M:%S:%f"
def _TO_DATESTRING(obj):
"this will only be called with valid database objects. Returns datestring on correct form."
"""
this will only be called with valid database objects. Returns datestring
on correct form.
"""
try:
return _GA(obj, "db_date_created").strftime(_DATESTRING)
except AttributeError:
@ -74,6 +78,7 @@ def _init_globals():
# SaverList, SaverDict, SaverSet - Attribute-specific helper classes and functions
#
def _save(method):
"method decorator that saves data to Attribute"
def save_wrapper(self, *args, **kwargs):
@ -83,6 +88,7 @@ def _save(method):
return ret
return update_wrapper(save_wrapper, method)
class _SaverMutable(object):
"""
Parent class for properly handling of nested mutables in
@ -95,6 +101,7 @@ class _SaverMutable(object):
self._parent = kwargs.pop("parent", None)
self._db_obj = kwargs.pop("db_obj", None)
self._data = None
def _save_tree(self):
"recursively traverse back up the tree, save when we reach the root"
if self._parent:
@ -103,6 +110,7 @@ class _SaverMutable(object):
self._db_obj.value = self
else:
logger.log_errmsg("_SaverMutable %s has no root Attribute to save to." % self)
def _convert_mutables(self, data):
"converts mutables to Saver* variants and assigns .parent property"
def process_tree(item, parent):
@ -127,19 +135,25 @@ class _SaverMutable(object):
def __repr__(self):
return self._data.__repr__()
def __len__(self):
return self._data.__len__()
def __iter__(self):
return self._data.__iter__()
def __getitem__(self, key):
return self._data.__getitem__(key)
@_save
def __setitem__(self, key, value):
self._data.__setitem__(key, self._convert_mutables(value))
@_save
def __delitem__(self, key):
self._data.__delitem__(key)
class _SaverList(_SaverMutable, MutableSequence):
"""
A list that saves itself to an Attribute when updated.
@ -147,14 +161,17 @@ class _SaverList(_SaverMutable, MutableSequence):
def __init__(self, *args, **kwargs):
super(_SaverList, self).__init__(*args, **kwargs)
self._data = list(*args)
@_save
def __add__(self, otherlist):
self._data = self._data.__add__(otherlist)
return self._data
@_save
def insert(self, index, value):
self._data.insert(index, self._convert_mutables(value))
class _SaverDict(_SaverMutable, MutableMapping):
"""
A dict that stores changes to an Attribute when updated
@ -163,6 +180,7 @@ class _SaverDict(_SaverMutable, MutableMapping):
super(_SaverDict, self).__init__(*args, **kwargs)
self._data = dict(*args)
class _SaverSet(_SaverMutable, MutableSet):
"""
A set that saves to an Attribute when updated
@ -170,11 +188,14 @@ class _SaverSet(_SaverMutable, MutableSet):
def __init__(self, *args, **kwargs):
super(_SaverSet, self).__init__(*args, **kwargs)
self._data = set(*args)
def __contains__(self, value):
return self._data.__contains__(value)
@_save
def add(self, value):
self._data.add(self._convert_mutables(value))
@_save
def discard(self, value):
self._data.discard(value)
@ -187,14 +208,18 @@ class _SaverSet(_SaverMutable, MutableSet):
def pack_dbobj(item):
"""
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)
This either returns the original input item or a tuple
("__packed_dbobj__", key, creation_time, id)
"""
_init_globals()
obj = hasattr(item, 'dbobj') and item.dbobj or item
obj = hasattr(item, 'dbobj') and item.dbobj or item
natural_key = _FROM_MODEL_MAP[hasattr(obj, "id") and hasattr(obj, "db_date_created") and
hasattr(obj, '__class__') and obj.__class__.__name__.lower()]
# build the internal representation as a tuple ("__packed_dbobj__", key, creation_time, id)
return natural_key and ('__packed_dbobj__', natural_key, _TO_DATESTRING(obj), _GA(obj, "id")) or item
# build the internal representation as a tuple
# ("__packed_dbobj__", key, creation_time, id)
return natural_key and ('__packed_dbobj__', natural_key,
_TO_DATESTRING(obj), _GA(obj, "id")) or item
def unpack_dbobj(item):
"""
@ -208,21 +233,26 @@ def unpack_dbobj(item):
obj = item[3] and _TO_TYPECLASS(_TO_MODEL_MAP[item[1]].objects.get(id=item[3]))
except ObjectDoesNotExist:
return None
# even if we got back a match, check the sanity of the date (some databases may 're-use' the id)
try: dbobj = obj.dbobj
except AttributeError: dbobj = obj
# even if we got back a match, check the sanity of the date (some
# databases may 're-use' the id)
try:
dbobj = obj.dbobj
except AttributeError:
dbobj = obj
return _TO_DATESTRING(dbobj) == item[2] and obj or None
#
# Access methods
#
def to_pickle(data):
"""
This prepares data on arbitrary form to be pickled. It handles any nested structure
and returns data on a form that is safe to pickle (including having converted any
database models to their internal representation). We also convert any Saver*-type
objects back to their normal representations, they are not pickle-safe.
This prepares data on arbitrary form to be pickled. It handles any nested
structure and returns data on a form that is safe to pickle (including
having converted any database models to their internal representation).
We also convert any Saver*-type objects back to their normal
representations, they are not pickle-safe.
"""
def process_item(item):
"Recursive processor and identification of data"
@ -246,20 +276,22 @@ def to_pickle(data):
return pack_dbobj(item)
return process_item(data)
@transaction.autocommit
def from_pickle(data, db_obj=None):
"""
This should be fed a just de-pickled data object. It will be converted back
to a form that may contain database objects again. Note that if a database
object was removed (or changed in-place) in the database, None will be returned.
object was removed (or changed in-place) in the database, None will be
returned.
db_obj - this is the model instance (normally 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.
db_obj - this is the model instance (normally 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
to their _SaverList, _SaverDict and _SaverSet counterparts.
"""
def process_item(item):
@ -278,7 +310,8 @@ def from_pickle(data, db_obj=None):
return set(process_item(val) for val in item)
elif hasattr(item, '__iter__'):
try:
# we try to conserve the iterable class if it accepts an iterator
# we try to conserve the iterable class if
# it accepts an iterator
return item.__class__(process_item(val) for val in item)
except (AttributeError, TypeError):
return [process_item(val) for val in item]
@ -300,7 +333,8 @@ def from_pickle(data, db_obj=None):
return dat
elif dtype == dict:
dat = _SaverDict(parent=parent)
dat._data.update(dict((key, process_tree(val, dat)) for key, val in item.items()))
dat._data.update(dict((key, process_tree(val, dat))
for key, val in item.items()))
return dat
elif dtype == set:
dat = _SaverSet(parent=parent)
@ -308,7 +342,8 @@ def from_pickle(data, db_obj=None):
return dat
elif hasattr(item, '__iter__'):
try:
# we try to conserve the iterable class if it accepts an iterator
# we try to conserve the iterable class if it
# accepts an iterator
return item.__class__(process_tree(val, parent) for val in item)
except (AttributeError, TypeError):
dat = _SaverList(parent=parent)
@ -326,7 +361,8 @@ def from_pickle(data, db_obj=None):
return dat
elif dtype == dict:
dat = _SaverDict(db_obj=db_obj)
dat._data.update((key, process_tree(val, parent=dat)) for key, val in data.items())
dat._data.update((key, process_tree(val, parent=dat))
for key, val in data.items())
return dat
elif dtype == set:
dat = _SaverSet(db_obj=db_obj)
@ -334,17 +370,22 @@ def from_pickle(data, db_obj=None):
return dat
return process_item(data)
def do_pickle(data):
"Perform pickle to string"
return to_str(dumps(data, protocol=PICKLE_PROTOCOL))
def do_unpickle(data):
"Retrieve pickle from pickled string"
return loads(to_str(data))
def dbserialize(data):
"Serialize to pickled form in one step"
return do_pickle(to_pickle(data))
def dbunserialize(data, db_obj=None):
"Un-serialize in one step. See from_pickle for help db_obj."
return do_unpickle(from_pickle(data, db_obj=db_obj))

View file

@ -24,8 +24,8 @@ TIMEFACTOR = settings.TIME_FACTOR
# Common real-life time measures, in seconds.
# You should not change these.
REAL_TICK = max(1.0, settings.TIME_TICK) #Smallest time unit (min 1s)
REAL_MIN = 60.0 # seconds per minute in real world
REAL_TICK = max(1.0, settings.TIME_TICK) # Smallest time unit (min 1s)
REAL_MIN = 60.0 # seconds per minute in real world
# Game-time units, in real-life seconds. These are supplied as
# a convenient measure for determining the current in-game time,
@ -40,6 +40,7 @@ WEEK = DAY * settings.TIME_DAY_PER_WEEK
MONTH = WEEK * settings.TIME_WEEK_PER_MONTH
YEAR = MONTH * settings.TIME_MONTH_PER_YEAR
class GameTime(Script):
"""
This sets up an script that keeps track of the
@ -51,12 +52,12 @@ class GameTime(Script):
"""
self.key = "sys_game_time"
self.desc = "Keeps track of the game time"
self.interval = REAL_MIN # update every minute
self.interval = REAL_MIN # update every minute
self.persistent = True
self.start_delay = True
self.attributes.add("game_time", 0.0) #IC time
self.attributes.add("run_time", 0.0) #OOC time
self.attributes.add("up_time", 0.0) #OOC time
self.attributes.add("game_time", 0.0) # IC time
self.attributes.add("run_time", 0.0) # OOC time
self.attributes.add("up_time", 0.0) # OOC time
def at_repeat(self):
"""
@ -77,6 +78,7 @@ class GameTime(Script):
"""
self.attributes.add("up_time", 0.0)
# Access routines
def gametime_format(seconds):
@ -94,27 +96,29 @@ def gametime_format(seconds):
# do this or we cancel the already counted
# timefactor in the timer script...
sec = int(seconds * TIMEFACTOR)
years, sec = sec/YEAR, sec % YEAR
months, sec = sec/MONTH, sec % MONTH
weeks, sec = sec/WEEK, sec % WEEK
days, sec = sec/DAY, sec % DAY
hours, sec = sec/HOUR, sec % HOUR
minutes, sec = sec/MIN, sec % MIN
years, sec = sec / YEAR, sec % YEAR
months, sec = sec / MONTH, sec % MONTH
weeks, sec = sec / WEEK, sec % WEEK
days, sec = sec / DAY, sec % DAY
hours, sec = sec / HOUR, sec % HOUR
minutes, sec = sec / MIN, sec % MIN
return (years, months, weeks, days, hours, minutes, sec)
def realtime_format(seconds):
"""
As gametime format, but with real time units
"""
sec = int(seconds)
years, sec = sec/29030400, sec % 29030400
months, sec = sec/2419200, sec % 2419200
weeks, sec = sec/604800, sec % 604800
days, sec = sec/86400, sec % 86400
hours, sec = sec/3600, sec % 3600
minutes, sec = sec/60, sec % 60
years, sec = sec / 29030400, sec % 29030400
months, sec = sec / 2419200, sec % 2419200
weeks, sec = sec / 604800, sec % 604800
days, sec = sec / 86400, sec % 86400
hours, sec = sec / 3600, sec % 3600
minutes, sec = sec / 60, sec % 60
return (years, months, weeks, days, hours, minutes, sec)
def gametime(format=False):
"""
Find the current in-game time (in seconds) since the start of the mud.
@ -136,6 +140,7 @@ def gametime(format=False):
return gametime_format(game_time)
return game_time
def runtime(format=False):
"""
Get the total actual time the server has been running (minus downtimes)
@ -151,6 +156,7 @@ def runtime(format=False):
return realtime_format(run_time)
return run_time
def uptime(format=False):
"""
Get the actual time the server has been running since last downtime.
@ -170,31 +176,33 @@ def uptime(format=False):
def gametime_to_realtime(secs=0, mins=0, hrs=0, days=0,
weeks=0, months=0, yrs=0):
"""
This method helps to figure out the real-world time it will take until a in-game time
has passed. E.g. if an event should take place a month later 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).
This method helps to figure out the real-world time it will take until an
in-game time has passed. E.g. if an event should take place a month later
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).
Example:
gametime_to_realtime(days=2) -> number of seconds in real life from now after which
2 in-game days will have passed.
gametime_to_realtime(days=2) -> number of seconds in real life from
now after which 2 in-game days will have passed.
"""
real_time = secs/TIMEFACTOR + mins*MIN + hrs*HOUR + \
days*DAY + weeks*WEEK + months*MONTH + yrs*YEAR
real_time = secs / TIMEFACTOR + mins * MIN + hrs * HOUR + \
days * DAY + weeks * WEEK + months * MONTH + yrs * YEAR
return real_time
def realtime_to_gametime(secs=0, mins=0, hrs=0, days=0,
weeks=0, months=0, yrs=0):
"""
This method calculates how large an in-game time a real-world time interval would
correspond to. This is usually a lot less interesting than the other way around.
This method calculates how large an in-game time a real-world time
interval would correspond to. This is usually a lot less interesting
than the other way around.
Example:
realtime_to_gametime(days=2) -> number of game-world seconds
corresponding to 2 real days.
"""
game_time = TIMEFACTOR * (secs + mins*60 + hrs*3600 + days*86400 + \
weeks*604800 + months*2419200 + yrs*29030400)
game_time = TIMEFACTOR * (secs + mins * 60 + hrs * 3600 + days * 86400 +
weeks * 604800 + months * 2419200 + yrs * 29030400)
return game_time

View file

@ -8,6 +8,7 @@ a higher layer module.
from traceback import format_exc
from twisted.python import log
def log_trace(errmsg=None):
"""
Log a traceback to the log. This should be called
@ -27,7 +28,8 @@ def log_trace(errmsg=None):
for line in errmsg.splitlines():
log.msg('[EE] %s' % line)
except Exception:
log.msg('[EE] %s' % errmsg )
log.msg('[EE] %s' % errmsg)
def log_errmsg(errmsg):
"""
@ -43,6 +45,7 @@ def log_errmsg(errmsg):
log.msg('[EE] %s' % line)
#log.err('ERROR: %s' % (errormsg,))
def log_warnmsg(warnmsg):
"""
Prints/logs any warnings that aren't critical but should be noted.
@ -57,6 +60,7 @@ def log_warnmsg(warnmsg):
log.msg('[WW] %s' % line)
#log.msg('WARNING: %s' % (warnmsg,))
def log_infomsg(infomsg):
"""
Prints any generic debugging/informative info that should appear in the log.
@ -70,6 +74,7 @@ def log_infomsg(infomsg):
for line in infomsg.splitlines():
log.msg('[..] %s' % line)
def log_depmsg(depmsg):
"""
Prints a deprecation message

View file

@ -30,7 +30,8 @@ Example: To reach the search method 'get_object_with_player'
from django.contrib.contenttypes.models import ContentType
# limit symbol import from API
__all__ = ("search_object", "search_player", "search_script", "search_message", "search_channel", "search_help_entry")
__all__ = ("search_object", "search_player", "search_script",
"search_message", "search_channel", "search_help_entry")
# import objects this way to avoid circular import problems
@ -54,25 +55,29 @@ HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_c
# candidates=None,
# exact=True):
#
# Search globally or in a list of candidates and return results. The result is always an Object.
# Always returns a list.
# Search globally or in a list of candidates and return results.
# The result is always a list of Objects (or the empty list)
#
# Arguments:
# ostring: (str) The string to compare names against. By default (if not attribute_name
# is set), this will search object.key and object.aliases in order. Can also
# be on the form #dbref, which will, if exact=True be matched against primary key.
# attribute_name: (str): Use this named ObjectAttribute to match ostring against, instead
# of the defaults.
# typeclass (str or TypeClass): restrict matches to objects having this typeclass. This will help
# speed up global searches.
# candidates (list obj ObjectDBs): If supplied, search will only be performed among the candidates
# in this list. A common list of candidates is the contents of the current location searched.
# exact (bool): Match names/aliases exactly or partially. Partial matching matches the
# beginning of words in the names/aliases, using a matching routine to separate
# multiple matches in names with multiple components (so "bi sw" will match
# "Big sword"). Since this is more expensive than exact matching, it is
# recommended to be used together with the objlist keyword to limit the number
# of possibilities. This value has no meaning if searching for attributes/properties.
# ostring: (str) The string to compare names against. By default (if
# not attribute_name is set), this will search object.key
# and object.aliases in order. Can also be on the form #dbref,
# which will, if exact=True be matched against primary key.
# attribute_name: (str): Use this named ObjectAttribute to match ostring
# against, instead of the defaults.
# typeclass (str or TypeClass): restrict matches to objects having
# this typeclass. This will help speed up global searches.
# candidates (list obj ObjectDBs): If supplied, search will only be
# performed among the candidates in this list. A common list
# of candidates is the contents of the current location.
# exact (bool): Match names/aliases exactly or partially. Partial
# matching matches the beginning of words in the names/aliases,
# using a matching routine to separate multiple matches in
# names with multiple components (so "bi sw" will match
# "Big sword"). Since this is more expensive than exact
# matching, it is recommended to be used together with
# the objlist keyword to limit the number of possibilities.
# This keyword has no meaning if attribute_name is set.
#
# Returns:
# A list of matching objects (or a list with one unique match)

View file

@ -1,11 +1,12 @@
"""
Test runner for Evennia test suite. Run with "game/manage.py test".
Test runner for Evennia test suite. Run with "game/manage.py test".
"""
from django.conf import settings
from django.test.simple import DjangoTestSuiteRunner
class EvenniaTestSuiteRunner(DjangoTestSuiteRunner):
"""
This test runner only runs tests on the apps specified in src/ and game/ to
@ -13,11 +14,11 @@ class EvenniaTestSuiteRunner(DjangoTestSuiteRunner):
"""
def build_suite(self, test_labels, extra_tests=None, **kwargs):
"""
Build a test suite for Evennia. test_labels is a list of apps to test.
Build a test suite for Evennia. test_labels is a list of apps to test.
If not given, a subset of settings.INSTALLED_APPS will be used.
"""
if not test_labels:
test_labels = [applabel.rsplit('.', 1)[1] for applabel in settings.INSTALLED_APPS
test_labels = [applabel.rsplit('.', 1)[1] for applabel in settings.INSTALLED_APPS
if (applabel.startswith('src.') or applabel.startswith('game.'))]
return super(EvenniaTestSuiteRunner, self).build_suite(test_labels, extra_tests=extra_tests, **kwargs)

View file

@ -13,6 +13,7 @@ import re
import cgi
from ansi import *
class TextToHTMLparser(object):
"""
This class describes a parser for converting from ansi to html.
@ -36,10 +37,10 @@ class TextToHTMLparser(object):
('purple', ANSI_MAGENTA),
('cyan', hilite + ANSI_CYAN),
('teal', ANSI_CYAN),
('white', hilite + ANSI_WHITE), # pure white
('gray', ANSI_WHITE), #light grey
('dimgray', hilite + ANSI_BLACK), #dark grey
('black', ANSI_BLACK), #pure black
('white', hilite + ANSI_WHITE), # pure white
('gray', ANSI_WHITE), # light grey
('dimgray', hilite + ANSI_BLACK), # dark grey
('black', ANSI_BLACK), # pure black
]
colorback = [
('bgred', hilite + ANSI_BACK_RED),
@ -61,8 +62,8 @@ class TextToHTMLparser(object):
]
# make sure to escape [
colorcodes = [(c, code.replace("[",r"\[")) for c, code in colorcodes]
colorback = [(c, code.replace("[",r"\[")) for c, code in colorback]
colorcodes = [(c, code.replace("[", r"\[")) for c, code in colorcodes]
colorback = [(c, code.replace("[", r"\[")) for c, code in colorback]
# create stop markers
fgstop = [("", c.replace("[", r"\[")) for c in (normal, hilite, underline)]
bgstop = [("", c.replace("[", r"\[")) for c in (normal,)]
@ -74,7 +75,7 @@ class TextToHTMLparser(object):
re_bgs = [(cname, re.compile("(?:%s)(.*?)(?=%s)" % (code, bgstop))) for cname, code in colorback]
re_normal = re.compile(normal.replace("[", r"\["))
re_hilite = re.compile("(?:%s)(.*)(?=%s)" % (hilite.replace("[", r"\["), fgstop))
re_uline = re.compile("(?:%s)(.*?)(?=%s)" % (ANSI_UNDERLINE.replace("[",r"\["), fgstop))
re_uline = re.compile("(?:%s)(.*?)(?=%s)" % (ANSI_UNDERLINE.replace("[", r"\["), fgstop))
re_string = re.compile(r'(?P<htmlchars>[<&>])|(?P<space>^[ \t]+)|(?P<lineend>\r\n|\r|\n)', re.S|re.M|re.I)
def re_color(self, text):
@ -126,9 +127,9 @@ class TextToHTMLparser(object):
if c['lineend']:
return '<br>'
elif c['space'] == '\t':
return ' '*self.tabstop
return ' ' * self.tabstop
elif c['space']:
t = m.group().replace('\t', '&nbsp;'*self.tabstop)
t = m.group().replace('\t', '&nbsp;' * self.tabstop)
t = t.replace(' ', '&nbsp;')
return t
@ -155,6 +156,7 @@ class TextToHTMLparser(object):
HTML_PARSER = TextToHTMLparser()
#
# Access function
#

View file

@ -6,12 +6,19 @@ be of use when designing your own game.
"""
import os, sys, imp, types, math, re
import textwrap, datetime, random, traceback, inspect
import os
import sys
import imp
import types
import math
import re
import textwrap
import datetime
import random
import traceback
from inspect import ismodule
from collections import defaultdict
from twisted.internet import threads, defer, reactor
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
try:
@ -24,6 +31,7 @@ _GA = object.__getattribute__
_SA = object.__setattr__
_DA = object.__delattr__
def is_iter(iterable):
"""
Checks if an object behaves iterably. However,
@ -39,10 +47,12 @@ def is_iter(iterable):
except AttributeError:
return False
def make_iter(obj):
"Makes sure that the object is always iterable."
return not hasattr(obj, '__iter__') and [obj] or obj
def fill(text, width=78, indent=0):
"""
Safely wrap text to a certain number of characters.
@ -69,7 +79,8 @@ def crop(text, width=78, suffix="[...]"):
return text
else:
lsuffix = len(suffix)
return "%s%s" % (text[:width-lsuffix], suffix)
return "%s%s" % (text[:width - lsuffix], suffix)
def dedent(text):
"""
@ -83,6 +94,7 @@ def dedent(text):
return ""
return textwrap.dedent(text)
def list_to_string(inlist, endsep="and", addquote=False):
"""
This pretty-formats a list as string output, adding
@ -108,6 +120,7 @@ def list_to_string(inlist, endsep="and", addquote=False):
return str(inlist[0])
return ", ".join(str(v) for v in inlist[:-1]) + " %s %s" % (endsep, inlist[-1])
def wildcard_to_regexp(instring):
"""
Converts a player-supplied string that may have wildcards in it to regular
@ -123,7 +136,7 @@ def wildcard_to_regexp(instring):
regexp_string += "^"
# Replace any occurances of * or ? with the appropriate groups.
regexp_string += instring.replace("*","(.*)").replace("?", "(.{1})")
regexp_string += instring.replace("*", "(.*)").replace("?", "(.{1})")
# If there's an asterisk at the end of the string, we can't impose the
# end of string ($) limiter.
@ -132,6 +145,7 @@ def wildcard_to_regexp(instring):
return regexp_string
def time_format(seconds, style=0):
"""
Function to return a 'prettified' version of a value in seconds.
@ -146,11 +160,11 @@ def time_format(seconds, style=0):
# We'll just use integer math, no need for decimal precision.
seconds = int(seconds)
days = seconds / 86400
days = seconds / 86400
seconds -= days * 86400
hours = seconds / 3600
hours = seconds / 3600
seconds -= hours * 3600
minutes = seconds / 60
minutes = seconds / 60
seconds -= minutes * 60
if style is 0:
@ -225,6 +239,7 @@ def time_format(seconds, style=0):
return retval
def datetime_format(dtobj):
"""
Takes a datetime object instance (e.g. from django's DateTimeField)
@ -250,6 +265,7 @@ def datetime_format(dtobj):
timestring = "%02i:%02i:%02i" % (hour, minute, second)
return timestring
def host_os_is(osname):
"""
Check to see if the host OS matches the query.
@ -258,6 +274,7 @@ def host_os_is(osname):
return True
return False
def get_evennia_version():
"""
Check for the evennia version info.
@ -268,6 +285,7 @@ def get_evennia_version():
except IOError:
return "Unknown version"
def pypath_to_realpath(python_path, file_ending='.py'):
"""
Converts a path on dot python form (e.g. 'src.objects.models') to
@ -284,11 +302,13 @@ def pypath_to_realpath(python_path, file_ending='.py'):
return "%s%s" % (path, file_ending)
return path
def dbref(dbref, reqhash=True):
"""
Converts/checks if input is a valid dbref.
If reqhash is set, only input strings on the form '#N', where N is an integer
is accepted. Otherwise strings '#N', 'N' and integers N are all accepted.
If reqhash is set, only input strings on the form '#N', where N is an
integer is accepted. Otherwise strings '#N', 'N' and integers N are all
accepted.
Output is the integer part.
"""
if reqhash:
@ -301,6 +321,7 @@ def dbref(dbref, reqhash=True):
return int(dbref) if dbref.isdigit() else None
return dbref if isinstance(dbref, int) else None
def to_unicode(obj, encoding='utf-8', force_string=False):
"""
This decodes a suitable object to the unicode format. Note that
@ -335,6 +356,7 @@ def to_unicode(obj, encoding='utf-8', force_string=False):
raise Exception("Error: '%s' contains invalid character(s) not in %s." % (obj, encoding))
return obj
def to_str(obj, encoding='utf-8', force_string=False):
"""
This encodes a unicode string back to byte-representation,
@ -366,6 +388,7 @@ def to_str(obj, encoding='utf-8', force_string=False):
raise Exception("Error: Unicode could not encode unicode string '%s'(%s) to a bytestring. " % (obj, encoding))
return obj
def validate_email_address(emailaddress):
"""
Checks if an email address is syntactically correct.
@ -382,18 +405,18 @@ def validate_email_address(emailaddress):
# Email address must be more than 7 characters in total.
if len(emailaddress) < 7:
return False # Address too short.
return False # Address too short.
# Split up email address into parts.
try:
localpart, domainname = emailaddress.rsplit('@', 1)
host, toplevel = domainname.rsplit('.', 1)
except ValueError:
return False # Address does not have enough parts.
return False # Address does not have enough parts.
# Check for Country code or Generic Domain.
if len(toplevel) != 2 and toplevel not in domains:
return False # Not a domain name.
return False # Not a domain name.
for i in '-_.%+.':
localpart = localpart.replace(i, "")
@ -401,9 +424,9 @@ def validate_email_address(emailaddress):
host = host.replace(i, "")
if localpart.isalnum() and host.isalnum():
return True # Email address is fine.
return True # Email address is fine.
else:
return False # Email address has funny characters.
return False # Email address has funny characters.
def inherits_from(obj, parent):
@ -447,6 +470,7 @@ def server_services():
del SESSIONS
return server
def uses_database(name="sqlite3"):
"""
Checks if the game is currently using a given database. This is a
@ -460,13 +484,15 @@ def uses_database(name="sqlite3"):
engine = settings.DATABASE_ENGINE
return engine == "django.db.backends.%s" % name
def delay(delay=2, retval=None, callback=None):
"""
Delay the return of a value.
Inputs:
delay (int) - the delay in seconds
retval (any) - this will be returned by this function after a delay
callback (func(retval)) - if given, this will be called with retval after delay seconds
callback (func(retval)) - if given, this will be called with retval
after delay seconds
Returns:
deferred that will fire with to_return after delay seconds
"""
@ -499,14 +525,15 @@ def clean_object_caches(obj):
pass
# on-object property cache
[_DA(obj, cname) for cname in obj.__dict__.keys() if cname.startswith("_cached_db_")]
[_DA(obj, cname) for cname in obj.__dict__.keys()
if cname.startswith("_cached_db_")]
try:
hashid = _GA(obj, "hashid")
hasid = obj.hashid
_TYPECLASSMODELS._ATTRIBUTE_CACHE[hashid] = {}
except AttributeError:
pass
_PPOOL = None
_PCMD = None
_PROC_ERR = "A process has ended with a probable error condition: process ended by signal 9."
@ -574,7 +601,6 @@ def run_async(to_execute, *args, **kwargs):
deferred.addCallback(callback, **callback_kwargs)
deferred.addErrback(errback, **errback_kwargs)
#
def check_evennia_dependencies():
"""
@ -631,7 +657,7 @@ def check_evennia_dependencies():
if settings.IRC_ENABLED:
try:
import twisted.words
twisted.words # set to avoid debug info about not-used import
twisted.words # set to avoid debug info about not-used import
except ImportError:
errstring += "\n ERROR: IRC is enabled, but twisted.words is not installed. Please install it."
errstring += "\n Linux Debian/Ubuntu users should install package 'python-twisted-words', others"
@ -642,6 +668,7 @@ def check_evennia_dependencies():
print "%s\n %s\n%s" % ("-"*78, errstring, '-'*78)
return no_error
def has_parent(basepath, obj):
"Checks if basepath is somewhere in objs parent tree."
try:
@ -652,17 +679,19 @@ def has_parent(basepath, obj):
# instance. Not sure if one should defend against this.
return False
def mod_import(module):
"""
A generic Python module loader.
Args:
module - this can be either a Python path (dot-notation like src.objects.models),
an absolute path (e.g. /home/eve/evennia/src/objects.models.py)
module - this can be either a Python path (dot-notation like
src.objects.models), an absolute path
(e.g. /home/eve/evennia/src/objects.models.py)
or an already import module object (e.g. models)
Returns:
an imported module. If the input argument was already a model, this is returned as-is,
otherwise the path is parsed and imported.
an imported module. If the input argument was already a model,
this is returned as-is, otherwise the path is parsed and imported.
Error:
returns None. The error is also logged.
"""
@ -691,7 +720,7 @@ def mod_import(module):
if not module:
return None
if type(module) == types.ModuleType:
if isinstance(module, types.ModuleType):
# if this is already a module, we are done
mod = module
else:
@ -699,8 +728,9 @@ def mod_import(module):
try:
mod = __import__(module, fromlist=["None"])
except ImportError, ex:
# check just where the ImportError happened (it could have been an erroneous
# import inside the module as well). This is the trivial way to do it ...
# check just where the ImportError happened (it could have been
# an erroneous import inside the module as well). This is the
# trivial way to do it ...
if str(ex) != "Import by filename is not supported.":
#log_trace("ImportError inside module '%s': '%s'" % (module, str(ex)))
raise
@ -726,29 +756,36 @@ def mod_import(module):
result[0].close()
return mod
def all_from_module(module):
"""
Return all global-level variables from a module as a dict
"""
mod = mod_import(module)
return dict((key, val) for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val)))
return dict((key, val) for key, val in mod.__dict__.items()
if not (key.startswith("_") or ismodule(val)))
def variable_from_module(module, variable=None, default=None):
"""
Retrieve a variable or list of variables from a module. The variable(s) must be defined
globally in the module. If no variable is given (or a list entry is None), a random variable
is extracted from the module.
Retrieve a variable or list of variables from a module. The variable(s)
must be defined globally in the module. If no variable is given (or a
list entry is None), a random variable is extracted from the module.
If module cannot be imported or given variable not found, default
is returned.
Args:
module (string or module)- python path, absolute path or a module
variable (string or iterable) - single variable name or iterable of variable names to extract
default (string) - default value to use if a variable fails to be extracted.
variable (string or iterable) - single variable name or iterable of
variable names to extract
default (string) - default value to use if a variable fails
to be extracted.
Returns:
a single value or a list of values depending on the type of 'variable' argument. Errors in lists
are replaced by the 'default' argument."""
a single value or a list of values depending on the type of
'variable' argument. Errors in lists are replaced by the
'default' argument.
"""
if not module:
return default
@ -761,12 +798,14 @@ def variable_from_module(module, variable=None, default=None):
result.append(mod.__dict__.get(var, default))
else:
# random selection
mvars = [val for key, val in mod.__dict__.items() if not (key.startswith("_") or ismodule(val))]
mvars = [val for key, val in mod.__dict__.items()
if not (key.startswith("_") or ismodule(val))]
result.append((mvars and random.choice(mvars)) or default)
if len(result) == 1:
return result[0]
return result
def string_from_module(module, variable=None, default=None):
"""
This is a wrapper for variable_from_module that requires return
@ -779,6 +818,7 @@ def string_from_module(module, variable=None, default=None):
return [(isinstance(v, basestring) and v or default) for v in val]
return default
def init_new_player(player):
"""
Helper method to call all hooks, set flags etc on a newly created
@ -790,41 +830,50 @@ def init_new_player(player):
# player.character.db.FIRST_LOGIN = True
player.db.FIRST_LOGIN = True
def string_similarity(string1, string2):
"""
This implements a "cosine-similarity" algorithm as described for example in
Proceedings of the 22nd International Conference on Computation Linguistics
(Coling 2008), pages 593-600, Manchester, August 2008
The measure-vectors used is simply a "bag of words" type histogram (but for letters).
Proceedings of the 22nd International Conference on Computation
Linguistics (Coling 2008), pages 593-600, Manchester, August 2008
The measure-vectors used is simply a "bag of words" type histogram
(but for letters).
The function returns a value 0...1 rating how similar the two strings are. The strings can
contain multiple words.
The function returns a value 0...1 rating how similar the two strings
are. The strings can contain multiple words.
"""
vocabulary = set(list(string1 + string2))
vec1 = [string1.count(v) for v in vocabulary]
vec2 = [string2.count(v) for v in vocabulary]
try:
return float(sum(vec1[i]*vec2[i] for i in range(len(vocabulary)))) / \
return float(sum(vec1[i] * vec2[i] for i in range(len(vocabulary)))) / \
(math.sqrt(sum(v1**2 for v1 in vec1)) * math.sqrt(sum(v2**2 for v2 in vec2)))
except ZeroDivisionError:
# can happen if empty-string cmdnames appear for some reason. This is a no-match.
# can happen if empty-string cmdnames appear for some reason.
# This is a no-match.
return 0
def string_suggestions(string, vocabulary, cutoff=0.6, maxnum=3):
"""
Given a string and a vocabulary, return a match or a list of suggestsion based on
string similarity.
Given a string and a vocabulary, return a match or a list of suggestsion
based on string similarity.
Args:
string (str)- a string to search for
vocabulary (iterable) - a list of available strings
cutoff (int, 0-1) - limit the similarity matches (higher, the more exact is required)
cutoff (int, 0-1) - limit the similarity matches (higher, the more
exact is required)
maxnum (int) - maximum number of suggestions to return
Returns:
list of suggestions from vocabulary (could be empty if there are no matches)
list of suggestions from vocabulary (could be empty if there are
no matches)
"""
return [tup[1] for tup in sorted([(string_similarity(string, sugg), sugg) for sugg in vocabulary],
key=lambda tup: tup[0], reverse=True) if tup[0] >= cutoff][:maxnum]
return [tup[1] for tup in sorted([(string_similarity(string, sugg), sugg)
for sugg in vocabulary],
key=lambda tup: tup[0], reverse=True)
if tup[0] >= cutoff][:maxnum]
def string_partial_matching(alternatives, inp, ret_index=True):
"""
@ -837,7 +886,8 @@ def string_partial_matching(alternatives, inp, ret_index=True):
Input:
alternatives (list of str) - list of possible strings to match
inp (str) - search criterion
ret_index (bool) - return list of indices (from alternatives array) or strings
ret_index (bool) - return list of indices (from alternatives
array) or strings
Returns:
list of matching indices or strings, or an empty list
@ -855,7 +905,8 @@ def string_partial_matching(alternatives, inp, ret_index=True):
# loop over parts, making sure only to visit each part once
# (this will invalidate input in the wrong word order)
submatch = [last_index + alt_num for alt_num, alt_word
in enumerate(alt_words[last_index:]) if alt_word.startswith(inp_word)]
in enumerate(alt_words[last_index:])
if alt_word.startswith(inp_word)]
if submatch:
last_index = min(submatch) + 1
score += 1
@ -871,6 +922,7 @@ def string_partial_matching(alternatives, inp, ret_index=True):
return matches[max(matches)]
return []
def format_table(table, extra_space=1):
"""
Note: src.utils.prettytable is more powerful than this, but this
@ -909,6 +961,7 @@ def format_table(table, extra_space=1):
for icol, col in enumerate(table)])
return ftable
def get_evennia_pids():
"""
Get the currently valids PIDs (Process IDs) of the Portal and Server