Merge conflicts against master, including cmdhandler support for direct cmdobject input together with prefix-ignore mechanism from devel.

This commit is contained in:
Griatch 2017-04-01 16:08:23 +02:00
commit a648433db8
69 changed files with 2617 additions and 1771 deletions

View file

@ -1,4 +1,23 @@
# Evennia Changelog
## Feb 2017:
New devel branch created, to lead up to Evennia 0.7.
## Dec 2016:
Lots of bugfixes and considerable uptick in contributors. Unittest coverage
and PEP8 adoption and refactoring.
## May 2016:
Evennia 0.6 with completely reworked Out-of-band system, making
the message path completely flexible and built around input/outputfuncs.
A completely new webclient, split into the evennia.js library and a
gui library, making it easier to customize.
## Feb 2016:
Added the new EvMenu and EvMore utilities, updated EvEdit and cleaned up
a lot of the batchcommand functionality. Started work on new Devel branch.
## Sept 2015:
Evennia 0.5. Merged devel branch, full library format implemented.
## Feb 2015:
Development currently in devel/ branch. Moved typeclasses to use

View file

@ -124,7 +124,7 @@ likely file a bug report with the Evennia project.
""")
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
"reached for '{raw_string}' ({cmdclass})."
"reached for '{raw_cmdname}' ({cmdclass})."
def _msg_err(receiver, stringtuple):
@ -235,7 +235,7 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
for lobj in local_objlist:
try:
# call hook in case we need to do dynamic changing to cmdset
_GA(lobj, "at_cmdset_get")()
_GA(lobj, "at_cmdset_get")(caller=caller)
except Exception:
logger.log_trace()
# the call-type lock is checked here, it makes sure a player
@ -391,7 +391,8 @@ def get_and_merge_cmdsets(caller, session, player, obj, callertype, raw_string):
@inlineCallbacks
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None, **kwargs):
def cmdhandler(called_by, raw_string, _testing=False, callertype="session", session=None,
cmdobj=None, cmdobj_key=None, **kwargs):
"""
This is the main mechanism that handles any string sent to the engine.
@ -413,6 +414,15 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
precendence for same-name and same-prio commands.
session (Session, optional): Relevant if callertype is "player" - the session will help
retrieve the correct cmdsets from puppeted objects.
cmdobj (Command, optional): If given a command instance, this will be executed using
`called_by` as the caller, `raw_string` representing its arguments and (optionally)
`cmdobj_key` as its input command name. No cmdset lookup will be performed but
all other options apply as normal. This allows for running a specific Command
within the command system mechanism.
cmdobj_key (string, optional): Used together with `cmdobj` keyword to specify
which cmdname should be assigned when calling the specified Command instance. This
is made available as `self.cmdstring` when the Command runs.
If not given, the command will be assumed to be called as `cmdobj.key`.
Kwargs:
kwargs (any): other keyword arguments will be assigned as named variables on the
@ -428,7 +438,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
"""
@inlineCallbacks
def _run_command(cmd, cmdname, raw_cmdname, args):
def _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, player):
"""
Helper function: This initializes and runs the Command
instance once the parser has identified it as either a normal
@ -437,10 +447,13 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
Args:
cmd (Command): Command object
cmdname (str): Name of command
args (str): extra text entered after the identified command
raw_cmdname (str): Name of Command, unaffected by eventual
prefix-stripping (if no prefix-stripping, this is the same
as cmdname).
args (str): extra text entered after the identified command
cmdset (CmdSet): Command sert the command belongs to (if any)..
session (Session): Session of caller (if any).
player (Player): Player of caller (if any).
Returns:
deferred (Deferred): this will fire with the return of the
@ -482,7 +495,7 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
_COMMAND_NESTING[called_by] += 1
if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT:
err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_COMMAND_RECURSION_LIMIT,
raw_string=unformatted_raw_string,
raw_cmdname=raw_cmdname,
cmdclass=cmd.__class__)
raise RuntimeError(err)
@ -543,78 +556,89 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
try: # catch bugs in cmdhandler itself
try: # catch special-type commands
if cmdobj:
# the command object is already given
cmd = cmdobj() if callable(cmdobj) else cmdobj
cmdname = cmdobj_key if cmdobj_key else cmd.key
args = raw_string
unformatted_raw_string = "%s%s" % (cmdname, args)
cmdset = None
session = session
player = player
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
callertype, raw_string)
if not cmdset:
# this is bad and shouldn't happen.
raise NoCmdSets
# store the completely unmodified raw string - including
# whitespace and eventual prefixes-to-be-stripped.
unformatted_raw_string = raw_string
raw_string = raw_string.strip()
if not raw_string:
# Empty input. Test for system command instead.
syscmd = yield cmdset.get(CMD_NOINPUT)
sysarg = ""
raise ExecSystemCommand(syscmd, sysarg)
# Parse the input string and match to available cmdset.
# This also checks for permissions, so all commands in match
# are commands the caller is allowed to call.
matches = yield _COMMAND_PARSER(raw_string, cmdset, caller)
else:
# no explicit cmdobject given, figure it out
cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
callertype, raw_string)
if not cmdset:
# this is bad and shouldn't happen.
raise NoCmdSets
# store the completely unmodified raw string - including
# whitespace and eventual prefixes-to-be-stripped.
unformatted_raw_string = raw_string
raw_string = raw_string.strip()
if not raw_string:
# Empty input. Test for system command instead.
syscmd = yield cmdset.get(CMD_NOINPUT)
sysarg = ""
raise ExecSystemCommand(syscmd, sysarg)
# Parse the input string and match to available cmdset.
# This also checks for permissions, so all commands in match
# are commands the caller is allowed to call.
matches = yield _COMMAND_PARSER(raw_string, cmdset, caller)
# Deal with matches
# Deal with matches
if len(matches) > 1:
# We have a multiple-match
syscmd = yield cmdset.get(CMD_MULTIMATCH)
sysarg = _("There were multiple matches.")
if syscmd:
# use custom CMD_MULTIMATCH
syscmd.matches = matches
else:
# fall back to default error handling
sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0])
raise ExecSystemCommand(syscmd, sysarg)
cmdname, args, cmd = "", "", None
if len(matches) == 1:
# We have a unique command match. But it may still be invalid.
match = matches[0]
cmdname, args, cmd, raw_cmdname = match[0], match[1], match[2], match[5]
if not matches:
# No commands match our entered command
syscmd = yield cmdset.get(CMD_NOMATCH)
if syscmd:
# use custom CMD_NOMATCH command
sysarg = raw_string
else:
# fallback to default error text
sysarg = _("Command '%s' is not available.") % raw_string
suggestions = string_suggestions(raw_string,
cmdset.get_all_cmd_keys_and_aliases(caller),
cutoff=0.7, maxnum=3)
if suggestions:
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True)
if len(matches) > 1:
# We have a multiple-match
syscmd = yield cmdset.get(CMD_MULTIMATCH)
sysarg = _("There were multiple matches.")
if syscmd:
# use custom CMD_MULTIMATCH
syscmd.matches = matches
else:
sysarg += _(" Type \"help\" for help.")
raise ExecSystemCommand(syscmd, sysarg)
# fall back to default error handling
sysarg = yield _SEARCH_AT_RESULT([match[2] for match in matches], caller, query=matches[0][0])
raise ExecSystemCommand(syscmd, sysarg)
# Check if this is a Channel-cmd match.
if hasattr(cmd, 'is_channel') and cmd.is_channel:
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = yield cmdset.get(CMD_CHANNEL)
if syscmd:
# replace system command with custom version
cmd = syscmd
cmd.session = session
sysarg = "%s:%s" % (cmdname, args)
raise ExecSystemCommand(cmd, sysarg)
cmdname, args, cmd = "", "", None
if len(matches) == 1:
# We have a unique command match. But it may still be invalid.
match = matches[0]
cmdname, args, cmd, raw_cmdname = match[0], match[1], match[2], match[5]
if not matches:
# No commands match our entered command
syscmd = yield cmdset.get(CMD_NOMATCH)
if syscmd:
# use custom CMD_NOMATCH command
sysarg = raw_string
else:
# fallback to default error text
sysarg = _("Command '%s' is not available.") % raw_string
suggestions = string_suggestions(raw_string,
cmdset.get_all_cmd_keys_and_aliases(caller),
cutoff=0.7, maxnum=3)
if suggestions:
sysarg += _(" Maybe you meant %s?") % utils.list_to_string(suggestions, _('or'), addquote=True)
else:
sysarg += _(" Type \"help\" for help.")
raise ExecSystemCommand(syscmd, sysarg)
# Check if this is a Channel-cmd match.
if hasattr(cmd, 'is_channel') and cmd.is_channel:
# even if a user-defined syscmd is not defined, the
# found cmd is already a system command in its own right.
syscmd = yield cmdset.get(CMD_CHANNEL)
if syscmd:
# replace system command with custom version
cmd = syscmd
cmd.session = session
sysarg = "%s:%s" % (cmdname, args)
raise ExecSystemCommand(cmd, sysarg)
# A normal command.
ret = yield _run_command(cmd, cmdname, raw_cmdname, args)
ret = yield _run_command(cmd, cmdname, args, raw_cmdname, cmdset, session, player)
returnValue(ret)
except ErrorReported as exc:
@ -629,7 +653,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
sysarg = exc.sysarg
if syscmd:
ret = yield _run_command(syscmd, syscmd.key, syscmd, sysarg)
ret = yield _run_command(syscmd, syscmd.key, sysarg,
unformatted_raw_string, cmdset, session, player)
returnValue(ret)
elif sysarg:
# return system arg

View file

@ -35,7 +35,7 @@ def _init_command(cls, **kwargs):
if cls.aliases and not is_iter(cls.aliases):
try:
cls.aliases = [str(alias).strip().lower()
for alias in cls.aliases.split(',')]
for alias in cls.aliases.split(',')]
except Exception:
cls.aliases = []
cls.aliases = list(set(alias for alias in cls.aliases
@ -57,7 +57,7 @@ def _init_command(cls, **kwargs):
if not hasattr(cls, 'locks'):
# default if one forgets to define completely
cls.locks = "cmd:all()"
if not "cmd:" in cls.locks:
if "cmd:" not in cls.locks:
cls.locks = "cmd:all();" + cls.locks
for lockstring in cls.locks.split(';'):
if lockstring and not ':' in lockstring:
@ -197,7 +197,6 @@ class Command(with_metaclass(CommandMeta, object)):
try:
# first assume input is a command (the most common case)
return self._matchset.intersection(cmd._matchset)
#return cmd.key in self._matchset
except AttributeError:
# probably got a string
return cmd in self._matchset
@ -211,9 +210,8 @@ class Command(with_metaclass(CommandMeta, object)):
"""
try:
return self._matchset.isdisjoint(cmd._matchset)
#return not cmd.key in self._matcheset
except AttributeError:
return not cmd in self._matchset
return cmd not in self._matchset
def __contains__(self, query):
"""
@ -308,7 +306,7 @@ class Command(with_metaclass(CommandMeta, object)):
def msg(self, text=None, to_obj=None, from_obj=None,
session=None, **kwargs):
"""
This is a shortcut instad of calling msg() directly on an
This is a shortcut instead of calling msg() directly on an
object - it will detect if caller is an Object or a Player and
also appends self.session automatically if self.msg_all_sessions is False.
@ -398,17 +396,18 @@ class Command(with_metaclass(CommandMeta, object)):
"""
# a simple test command to show the available properties
string = "-" * 50
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
string += "\n|w%s|n - Command variables from evennia:\n" % self.key
string += "-" * 50
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
string += "cmd locks (self.locks): {w%s{n\n" % self.locks
string += "help category (self.help_category): {w%s{n\n" % self.help_category.capitalize()
string += "object calling (self.caller): {w%s{n\n" % self.caller
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
string += "\nname of cmd (self.key): |w%s|n\n" % self.key
string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases
string += "cmd locks (self.locks): |w%s|n\n" % self.locks
string += "help category (self.help_category): |w%s|n\n" % self.help_category.capitalize()
string += "object calling (self.caller): |w%s|n\n" % self.caller
string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj
string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring
# show cmdset.key instead of cmdset to shorten output
string += fill("current cmdset (self.cmdset): {w%s{n\n" % (self.cmdset.key if self.cmdset.key else self.cmdset.__class__))
string += fill("current cmdset (self.cmdset): |w%s|n\n" %
(self.cmdset.key if self.cmdset.key else self.cmdset.__class__))
self.caller.msg(string)

View file

@ -1,21 +0,0 @@
#
# This is Evennia's default connection screen. It is imported
# and run from server/conf/connection_screens.py.
#
from django.conf import settings
from evennia.utils import utils
DEFAULT_SCREEN = \
"""{b=============================================================={n
Welcome to {g%s{n, version %s!
If you have an existing account, connect to it by typing:
{wconnect <username> <password>{n
If you need to create an account, type (without the <>'s):
{wcreate <username> <password>{n
If you have spaces in your username, enclose it in double quotes.
Enter {whelp{n for more info. {wlook{n will re-show this screen.
{b=============================================================={n""" \
% (settings.SERVERNAME, utils.get_evennia_version())

View file

@ -9,7 +9,7 @@ import re
from django.conf import settings
from evennia.server.sessionhandler import SESSIONS
from evennia.server.models import ServerConfig
from evennia.utils import prettytable, search, class_from_module
from evennia.utils import evtable, search, class_from_module
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -40,7 +40,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
help_category = "Admin"
def func(self):
"Implementing the function"
"""Implementing the function"""
caller = self.caller
args = self.args
@ -86,7 +86,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
# Carry out the booting of the sessions in the boot list.
feedback = None
if not 'quiet' in self.switches:
if 'quiet' not in self.switches:
feedback = "You have been disconnected by %s.\n" % caller.name
if reason:
feedback += "\nReason given: %s" % reason
@ -108,13 +108,12 @@ def list_bans(banlist):
if not banlist:
return "No active bans were found."
table = prettytable.PrettyTable(["{wid", "{wname/ip", "{wdate", "{wreason"])
table = evtable.EvTable("|wid", "|wname/ip", "|wdate", "|wreason")
for inum, ban in enumerate(banlist):
table.add_row([str(inum + 1),
ban[0] and ban[0] or ban[1],
ban[3], ban[4]])
string = "{wActive bans:{n\n%s" % table
return string
table.add_row(str(inum + 1),
ban[0] and ban[0] or ban[1],
ban[3], ban[4])
return "|wActive bans:|n\n%s" % table
class CmdBan(COMMAND_DEFAULT_CLASS):
@ -174,7 +173,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
if not self.args or (self.switches
and not any(switch in ('ip', 'name')
for switch in self.switches)):
for switch in self.switches)):
self.caller.msg(list_bans(banlist))
return
@ -202,7 +201,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
# save updated banlist
banlist.append(bantup)
ServerConfig.objects.conf('server_bans', banlist)
self.caller.msg("%s-Ban {w%s{n was added." % (typ, ban))
self.caller.msg("%s-Ban |w%s|n was added." % (typ, ban))
class CmdUnban(COMMAND_DEFAULT_CLASS):
@ -223,7 +222,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
help_category = "Admin"
def func(self):
"Implement unbanning"
"""Implement unbanning"""
banlist = ServerConfig.objects.conf('server_bans')
@ -240,14 +239,14 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
if not banlist:
self.caller.msg("There are no bans to clear.")
elif not (0 < num < len(banlist) + 1):
self.caller.msg("Ban id {w%s{x was not found." % self.args)
self.caller.msg("Ban id |w%s|x was not found." % self.args)
else:
# all is ok, clear ban
ban = banlist[num - 1]
del banlist[num - 1]
ServerConfig.objects.conf('server_bans', banlist)
self.caller.msg("Cleared ban %s: %s" %
(num, " ".join([s for s in ban[:2]])))
(num, " ".join([s for s in ban[:2]])))
class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
@ -270,7 +269,7 @@ class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
help_category = "Admin"
def func(self):
"Implements the command."
"""Implements the command."""
caller = self.caller
args = self.args
@ -346,7 +345,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
help_category = "Admin"
def func(self):
"Implement the command"
"""Implement the command"""
caller = self.caller
args = self.args
@ -382,7 +381,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
obj = caller.search(objname, global_search=True)
if not obj:
return
if rooms_only and not obj.location is None:
if rooms_only and obj.location is not None:
caller.msg("%s is not a room. Ignored." % objname)
continue
if players_only and not obj.has_player:
@ -414,7 +413,7 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
help_category = "Admin"
def func(self):
"Implement the function."
"""Implement the function."""
caller = self.caller
@ -455,7 +454,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
help_category = "Admin"
def func(self):
"Implement function"
"""Implement function"""
caller = self.caller
switches = self.switches
@ -481,36 +480,37 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
caller.msg("You are not allowed to examine this object.")
return
string = "Permissions on {w%s{n: " % obj.key
string = "Permissions on |w%s|n: " % obj.key
if not obj.permissions.all():
string += "<None>"
else:
string += ", ".join(obj.permissions.all())
if (hasattr(obj, 'player') and
hasattr(obj.player, 'is_superuser') and
obj.player.is_superuser):
obj.player.is_superuser):
string += "\n(... but this object is currently controlled by a SUPERUSER! "
string += "All access checks are passed automatically.)"
caller.msg(string)
return
# we supplied an argument on the form obj = perm
if not obj.access(caller, 'control'):
caller.msg("You are not allowed to edit this object's permissions.")
locktype = "edit" if playermode else "control"
if not obj.access(caller, locktype):
caller.msg("You are not allowed to edit this %s's permissions."
% ("player" if playermode else "object"))
return
cstring = ""
tstring = ""
caller_result = []
target_result = []
if 'del' in switches:
# delete the given permission(s) from object.
for perm in self.rhslist:
obj.permissions.remove(perm)
if obj.permissions.get(perm):
cstring += "\nPermissions %s could not be removed from %s." % (perm, obj.name)
caller_result.append("\nPermissions %s could not be removed from %s." % (perm, obj.name))
else:
cstring += "\nPermission %s removed from %s (if they existed)." % (perm, obj.name)
tstring += "\n%s revokes the permission(s) %s from you." % (caller.name, perm)
caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name))
target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm))
else:
# add a new permission
permissions = obj.permissions.all()
@ -525,15 +525,16 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
return
if perm in permissions:
cstring += "\nPermission '%s' is already defined on %s." % (rhs, obj.name)
caller_result.append("\nPermission '%s' is already defined on %s." % (rhs, obj.name))
else:
obj.permissions.add(perm)
plystring = "the Player" if playermode else "the Object/Character"
cstring += "\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring)
tstring += "\n%s gives you (%s, %s) the permission '%s'." % (caller.name, obj.name, plystring, rhs)
caller.msg(cstring.strip())
if tstring:
obj.msg(tstring.strip())
caller_result.append("\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring))
target_result.append("\n%s gives you (%s, %s) the permission '%s'."
% (caller.name, obj.name, plystring, rhs))
caller.msg("".join(caller_result).strip())
if target_result:
obj.msg("".join(target_result).strip())
class CmdWall(COMMAND_DEFAULT_CLASS):
@ -550,7 +551,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
help_category = "Admin"
def func(self):
"Implements command"
"""Implements command"""
if not self.args:
self.caller.msg("Usage: @wall <message>")
return

View file

@ -29,15 +29,13 @@ from evennia.utils import logger, utils
_RE_COMMENT = re.compile(r"^#.*?$", re.MULTILINE + re.DOTALL)
_RE_CODE_START = re.compile(r"^# batchcode code:", re.MULTILINE)
_COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
#from evennia.commands.default.muxcommand import _COMMAND_DEFAULT_CLASS
# limit symbols for API inclusion
__all__ = ("CmdBatchCommands", "CmdBatchCode")
_HEADER_WIDTH = 70
_UTF8_ERROR = \
"""
{rDecode error in '%s'.{n
_UTF8_ERROR = """
|rDecode error in '%s'.|n
This file contains non-ascii character(s). This is common if you
wrote some input in a language that has more letters and special
@ -83,9 +81,9 @@ print "leaving run ..."
"""
#------------------------------------------------------------
# -------------------------------------------------------------
# Helper functions
#------------------------------------------------------------
# -------------------------------------------------------------
def format_header(caller, entry):
"""
@ -100,7 +98,7 @@ def format_header(caller, entry):
header = utils.crop(entry, width=width)
ptr = caller.ndb.batch_stackptr + 1
stacklen = len(caller.ndb.batch_stack)
header = "{w%02i/%02i{G: %s{n" % (ptr, stacklen, header)
header = "|w%02i/%02i|G: %s|n" % (ptr, stacklen, header)
# add extra space to the side for padding.
header = "%s%s" % (header, " " * (width - len(header)))
header = header.replace('\n', '\\n')
@ -114,7 +112,7 @@ def format_code(entry):
"""
code = ""
for line in entry.split('\n'):
code += "\n{G>>>{n %s" % line
code += "\n|G>>>|n %s" % line
return code.strip()
@ -164,9 +162,9 @@ def step_pointer(caller, step=1):
stack = caller.ndb.batch_stack
nstack = len(stack)
if ptr + step <= 0:
caller.msg("{RBeginning of batch file.")
caller.msg("|RBeginning of batch file.")
if ptr + step >= nstack:
caller.msg("{REnd of batch file.")
caller.msg("|REnd of batch file.")
caller.ndb.batch_stackptr = max(0, min(nstack - 1, ptr + step))
@ -186,10 +184,10 @@ def show_curr(caller, showall=False):
string = format_header(caller, entry)
codeall = entry.strip()
string += "{G(hh for help)"
string += "|G(hh for help)"
if showall:
for line in codeall.split('\n'):
string += "\n{G|{n %s" % line
string += "\n|G||n %s" % line
caller.msg(string)
@ -217,9 +215,9 @@ def purge_processor(caller):
caller.scripts.validate() # this will purge interactive mode
#------------------------------------------------------------
# -------------------------------------------------------------
# main access commands
#------------------------------------------------------------
# -------------------------------------------------------------
class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
@ -243,7 +241,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
help_category = "Building"
def func(self):
"Starts the processor."
"""Starts the processor."""
caller = self.caller
@ -253,7 +251,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
return
python_path = self.args
#parse indata file
# parse indata file
try:
commands = BATCHCMD.parse_file(python_path)
@ -289,7 +287,8 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
caller.msg("\nBatch-command processor - Interactive mode for %s ..." % python_path)
show_curr(caller)
else:
caller.msg("Running Batch-command processor - Automatic mode for %s (this might take some time) ..." % python_path)
caller.msg("Running Batch-command processor - Automatic mode for %s (this might take some time) ..."
% python_path)
procpool = False
if "PythonProcPool" in utils.server_services():
@ -301,11 +300,11 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
if procpool:
# run in parallel process
def callback(r):
caller.msg(" {GBatchfile '%s' applied." % python_path)
caller.msg(" |GBatchfile '%s' applied." % python_path)
purge_processor(caller)
def errback(e):
caller.msg(" {RError from processor: '%s'" % e)
caller.msg(" |RError from processor: '%s'" % e)
purge_processor(caller)
utils.run_async(_PROCPOOL_BATCHCMD_SOURCE,
@ -315,7 +314,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
at_err=errback)
else:
# run in-process (might block)
for inum in range(len(commands)):
for _ in range(len(commands)):
# loop through the batch file
if not batch_cmd_exec(caller):
return
@ -323,7 +322,7 @@ class CmdBatchCommands(_COMMAND_DEFAULT_CLASS):
# clean out the safety cmdset and clean out all other
# temporary attrs.
string = " Batchfile '%s' applied." % python_path
caller.msg("{G%s" % string)
caller.msg("|G%s" % string)
purge_processor(caller)
@ -352,7 +351,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
help_category = "Building"
def func(self):
"Starts the processor."
"""Starts the processor."""
caller = self.caller
@ -363,7 +362,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
python_path = self.args
debug = 'debug' in self.switches
#parse indata file
# parse indata file
try:
codes = BATCHCODE.parse_file(python_path)
except UnicodeDecodeError as err:
@ -410,11 +409,11 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
if procpool:
# run in parallel process
def callback(r):
caller.msg(" {GBatchfile '%s' applied." % python_path)
caller.msg(" |GBatchfile '%s' applied." % python_path)
purge_processor(caller)
def errback(e):
caller.msg(" {RError from processor: '%s'" % e)
caller.msg(" |RError from processor: '%s'" % e)
purge_processor(caller)
utils.run_async(_PROCPOOL_BATCHCODE_SOURCE,
codes=codes,
@ -423,7 +422,7 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
at_err=errback)
else:
# un in-process (will block)
for inum in range(len(codes)):
for _ in range(len(codes)):
# loop through the batch file
if not batch_code_exec(caller):
return
@ -431,14 +430,14 @@ class CmdBatchCode(_COMMAND_DEFAULT_CLASS):
# clean out the safety cmdset and clean out all other
# temporary attrs.
string = " Batchfile '%s' applied." % python_path
caller.msg("{G%s" % string)
caller.msg("|G%s" % string)
purge_processor(caller)
#------------------------------------------------------------
# -------------------------------------------------------------
# State-commands for the interactive batch processor modes
# (these are the same for both processors)
#------------------------------------------------------------
# -------------------------------------------------------------
class CmdStateAbort(_COMMAND_DEFAULT_CLASS):
"""
@ -453,7 +452,7 @@ class CmdStateAbort(_COMMAND_DEFAULT_CLASS):
locks = "cmd:perm(batchcommands)"
def func(self):
"Exit back to default."
"""Exit back to default."""
purge_processor(self.caller)
self.caller.msg("Exited processor and reset out active cmdset back to the default one.")
@ -472,6 +471,7 @@ class CmdStateLL(_COMMAND_DEFAULT_CLASS):
def func(self):
show_curr(self.caller, showall=True)
class CmdStatePP(_COMMAND_DEFAULT_CLASS):
"""
pp
@ -644,7 +644,7 @@ class CmdStateSS(_COMMAND_DEFAULT_CLASS):
else:
step = 1
for istep in range(step):
for _ in range(step):
if caller.ndb.batch_batchmode == "batch_code":
batch_code_exec(caller)
else:
@ -673,7 +673,7 @@ class CmdStateSL(_COMMAND_DEFAULT_CLASS):
else:
step = 1
for istep in range(step):
for _ in range(step):
if caller.ndb.batch_batchmode == "batch_code":
batch_code_exec(caller)
else:
@ -699,7 +699,7 @@ class CmdStateCC(_COMMAND_DEFAULT_CLASS):
ptr = caller.ndb.batch_stackptr
step = nstack - ptr
for istep in range(step):
for _ in range(step):
if caller.ndb.batch_batchmode == "batch_code":
batch_code_exec(caller)
else:
@ -775,7 +775,7 @@ class CmdStateQQ(_COMMAND_DEFAULT_CLASS):
class CmdStateHH(_COMMAND_DEFAULT_CLASS):
"Help command"
"""Help command"""
key = "hh"
help_category = "BatchProcess"
@ -810,12 +810,12 @@ class CmdStateHH(_COMMAND_DEFAULT_CLASS):
self.caller.msg(string)
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Defining the cmdsets for the interactive batchprocessor
# mode (same for both processors)
#
#------------------------------------------------------------
# -------------------------------------------------------------
class BatchSafeCmdSet(CmdSet):
"""
@ -827,7 +827,7 @@ class BatchSafeCmdSet(CmdSet):
priority = 150 # override other cmdsets.
def at_cmdset_creation(self):
"Init the cmdset"
"""Init the cmdset"""
self.add(CmdStateAbort())
@ -839,7 +839,7 @@ class BatchInteractiveCmdSet(CmdSet):
priority = 104
def at_cmdset_creation(self):
"init the cmdset"
"""init the cmdset"""
self.add(CmdStateAbort())
self.add(CmdStateLL())
self.add(CmdStatePP())

View file

@ -591,9 +591,11 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
if not obj:
return
desc = self.args
obj.db.desc = desc
caller.msg("The description was set on %s." % obj.get_display_name(caller))
if obj.access(caller, "edit"):
obj.db.desc = desc
caller.msg("The description was set on %s." % obj.get_display_name(caller))
else:
caller.msg("You don't have permission to edit the description of %s." % obj.key)
class CmdDestroy(COMMAND_DEFAULT_CLASS):
@ -1138,7 +1140,7 @@ class CmdName(ObjManipCommand):
caller.msg("No name defined!")
return
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
caller.mgs("You don't have right to edit this player %s." % obj)
caller.msg("You don't have right to edit this player %s." % obj)
return
obj.username = newname
obj.save()

View file

@ -30,6 +30,7 @@ class CharacterCmdSet(CmdSet):
self.add(general.CmdDrop())
self.add(general.CmdGive())
self.add(general.CmdSay())
self.add(general.CmdWhisper())
self.add(general.CmdAccess())
# The help system

View file

@ -60,16 +60,17 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
"""
Handle the looking.
"""
caller = self.caller
if not self.args:
target = self.caller.location
target = caller.location
if not target:
self.caller.msg("You have no location to look at!")
caller.msg("You have no location to look at!")
return
else:
target = self.caller.search(self.args)
target = caller.search(self.args, use_dbref=caller.check_permstring("Builders"))
if not target:
return
self.msg(self.caller.at_look(target))
self.msg(caller.at_look(target))
class CmdNick(COMMAND_DEFAULT_CLASS):
@ -175,10 +176,9 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
errstring += "Not a valid nick index."
else:
errstring += "Nick not found."
if "delete" in switches or "del" in switches:
# clear the nick
if caller.nicks.has(old_nickstring, category=nicktype):
if old_nickstring and caller.nicks.has(old_nickstring, category=nicktype):
caller.nicks.remove(old_nickstring, category=nicktype)
string += "\nNick removed: '|w%s|n' -> |w%s|n." % (old_nickstring, old_replstring)
else:
@ -354,6 +354,8 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
caller.msg("You give %s to %s." % (to_give.key, target.key))
to_give.move_to(target, quiet=True)
target.msg("%s gives you %s." % (caller.key, to_give.key))
# Call the object script's at_give() method.
to_give.at_give(caller, target)
class CmdSetDesc(COMMAND_DEFAULT_CLASS):
@ -415,7 +417,50 @@ class CmdSay(COMMAND_DEFAULT_CLASS):
# Build the string to emit to neighbors.
emit_string = '%s says, "%s|n"' % (caller.name, speech)
caller.location.msg_contents(emit_string, exclude=caller, from_obj=caller)
caller.location.msg_contents(text=(emit_string, {"type": "say"}),
exclude=caller, from_obj=caller)
class CmdWhisper(COMMAND_DEFAULT_CLASS):
"""
Speak privately as your character to another
Usage:
whisper <player> = <message>
Talk privately to those in your current location, without
others being informed.
"""
key = "whisper"
locks = "cmd:all()"
def func(self):
"""Run the whisper command"""
caller = self.caller
if not self.lhs or not self.rhs:
caller.msg("Usage: whisper <player> = <message>")
return
receiver = caller.search(self.lhs)
if not receiver:
return
if caller == receiver:
caller.msg("You can't whisper to yourself.")
return
speech = self.rhs
# Feedback for the object doing the talking.
caller.msg('You whisper to %s, "%s|n"' % (receiver.key, speech))
# Build the string to emit to receiver.
emit_string = '%s whispers, "%s|n"' % (caller.name, speech)
receiver.msg(text=(emit_string, {"type": "whisper"}), from_obj=caller)
class CmdPose(COMMAND_DEFAULT_CLASS):
@ -458,7 +503,8 @@ class CmdPose(COMMAND_DEFAULT_CLASS):
self.caller.msg(msg)
else:
msg = "%s%s" % (self.caller.name, self.args)
self.caller.location.msg_contents(msg, from_obj=self.caller)
self.caller.location.msg_contents(text=(msg, {"type": "pose"}),
from_obj=self.caller)
class CmdAccess(COMMAND_DEFAULT_CLASS):

View file

@ -60,7 +60,7 @@ class CmdHelp(Command):
if self.session.protocol_key in ("websocket", "ajax/comet"):
try:
options = self.caller.player.db._saved_webclient_options
options = self.player.db._saved_webclient_options
if options and options["helppopup"]:
usemore = False
except KeyError:

View file

@ -9,6 +9,7 @@ from evennia.commands.command import Command
# limit symbol import for API
__all__ = ("MuxCommand", "MuxPlayerCommand")
class MuxCommand(Command):
"""
This sets up the basis for a MUX command. The idea
@ -98,7 +99,7 @@ class MuxCommand(Command):
# split out switches
switches = []
if args and len(args) > 1 and args[0] == "/":
if args and len(args) > 1 and raw[0] == "/":
# we have a switch, or a set of switches. These end with a space.
switches = args[1:].split(None, 1)
if len(switches) > 1:
@ -150,33 +151,32 @@ class MuxCommand(Command):
"""
# a simple test command to show the available properties
string = "-" * 50
string += "\n{w%s{n - Command variables from evennia:\n" % self.key
string += "\n|w%s|n - Command variables from evennia:\n" % self.key
string += "-" * 50
string += "\nname of cmd (self.key): {w%s{n\n" % self.key
string += "cmd aliases (self.aliases): {w%s{n\n" % self.aliases
string += "cmd locks (self.locks): {w%s{n\n" % self.locks
string += "help category (self.help_category): {w%s{n\n" % self.help_category
string += "object calling (self.caller): {w%s{n\n" % self.caller
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj
string += "command string given (self.cmdstring): {w%s{n\n" % self.cmdstring
string += "\nname of cmd (self.key): |w%s|n\n" % self.key
string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases
string += "cmd locks (self.locks): |w%s|n\n" % self.locks
string += "help category (self.help_category): |w%s|n\n" % self.help_category
string += "object calling (self.caller): |w%s|n\n" % self.caller
string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj
string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring
# show cmdset.key instead of cmdset to shorten output
string += utils.fill("current cmdset (self.cmdset): {w%s{n\n" % self.cmdset)
string += utils.fill("current cmdset (self.cmdset): |w%s|n\n" % self.cmdset)
string += "\n" + "-" * 50
string += "\nVariables from MuxCommand baseclass\n"
string += "\nVariables from MuxCommand baseclass\n"
string += "-" * 50
string += "\nraw argument (self.raw): {w%s{n \n" % self.raw
string += "cmd args (self.args): {w%s{n\n" % self.args
string += "cmd switches (self.switches): {w%s{n\n" % self.switches
string += "space-separated arg list (self.arglist): {w%s{n\n" % self.arglist
string += "lhs, left-hand side of '=' (self.lhs): {w%s{n\n" % self.lhs
string += "lhs, comma separated (self.lhslist): {w%s{n\n" % self.lhslist
string += "rhs, right-hand side of '=' (self.rhs): {w%s{n\n" % self.rhs
string += "rhs, comma separated (self.rhslist): {w%s{n\n" % self.rhslist
string += "\nraw argument (self.raw): |w%s|n \n" % self.raw
string += "cmd args (self.args): |w%s|n\n" % self.args
string += "cmd switches (self.switches): |w%s|n\n" % self.switches
string += "space-separated arg list (self.arglist): |w%s|n\n" % self.arglist
string += "lhs, left-hand side of '=' (self.lhs): |w%s|n\n" % self.lhs
string += "lhs, comma separated (self.lhslist): |w%s|n\n" % self.lhslist
string += "rhs, right-hand side of '=' (self.rhs): |w%s|n\n" % self.rhs
string += "rhs, comma separated (self.rhslist): |w%s|n\n" % self.rhslist
string += "-" * 50
self.caller.msg(string)
class MuxPlayerCommand(MuxCommand):
"""
This is an on-Player version of the MuxCommand. Since these commands sit

View file

@ -13,7 +13,7 @@ The property self.character can be used to access the character when
these commands are triggered with a connected character (such as the
case of the @ooc command), it is None if we are OOC.
Note that under MULTISESSION_MODE > 2, Player- commands should use
Note that under MULTISESSION_MODE > 2, Player commands should use
self.msg() and similar methods to reroute returns to the correct
method. Otherwise all text will be returned to all connected sessions.
@ -23,7 +23,7 @@ from builtins import range
import time
from django.conf import settings
from evennia.server.sessionhandler import SESSIONS
from evennia.utils import utils, create, search, prettytable, evtable
from evennia.utils import utils, create, search, evtable
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -44,7 +44,7 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
"""
def parse(self):
"Custom parsing"
"""Custom parsing"""
super(MuxPlayerLookCommand, self).parse()
@ -62,7 +62,7 @@ class MuxPlayerLookCommand(COMMAND_DEFAULT_CLASS):
# store playable property
if self.args:
self.playable = dict((utils.to_str(char.key.lower()), char)
for char in playable).get(self.args.lower(), None)
for char in playable).get(self.args.lower(), None)
else:
self.playable = playable
@ -83,10 +83,10 @@ class CmdOOCLook(MuxPlayerLookCommand):
Look in the ooc state.
"""
#This is an OOC version of the look command. Since a
#Player doesn't have an in-game existence, there is no
#concept of location or "self". If we are controlling
#a character, pass control over to normal look.
# This is an OOC version of the look command. Since a
# Player doesn't have an in-game existence, there is no
# concept of location or "self". If we are controlling
# a character, pass control over to normal look.
key = "look"
aliases = ["l", "ls"]
@ -97,11 +97,11 @@ class CmdOOCLook(MuxPlayerLookCommand):
player_caller = True
def func(self):
"implement the ooc look command"
"""implement the ooc look command"""
if _MULTISESSION_MODE < 2:
# only one character allowed
self.msg("You are out-of-character (OOC).\nUse {w@ic{n to get back into the game.")
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
return
# call on-player look helper method
@ -128,7 +128,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
player_caller = True
def func(self):
"create the new character"
"""create the new character"""
player = self.player
if not self.args:
self.msg("Usage: @charcreate <charname> [= description]")
@ -139,8 +139,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1
if not player.is_superuser and \
(player.db._playable_characters and
len(player.db._playable_characters) >= charmax):
(player.db._playable_characters and
len(player.db._playable_characters) >= charmax):
self.msg("You may only create a maximum of %i characters." % charmax)
return
from evennia.objects.models import ObjectDB
@ -150,15 +150,13 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
# check if this Character already exists. Note that we are only
# searching the base character typeclass here, not any child
# classes.
self.msg("{rA character named '{w%s{r' already exists.{n" % key)
self.msg("|rA character named '|w%s|r' already exists.|n" % key)
return
# create the character
start_location = ObjectDB.objects.get_id(settings.START_LOCATION)
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
permissions = settings.PERMISSION_PLAYER_DEFAULT
new_character = create.create_object(typeclass, key=key,
location=start_location,
home=default_home,
@ -171,7 +169,8 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
new_character.db.desc = desc
elif not new_character.db.desc:
new_character.db.desc = "This is a Player."
self.msg("Created new character %s. Use {w@ic %s{n to enter the game as this character." % (new_character.key, new_character.key))
self.msg("Created new character %s. Use |w@ic %s|n to enter the game as this character."
% (new_character.key, new_character.key))
class CmdCharDelete(COMMAND_DEFAULT_CLASS):
@ -188,7 +187,7 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
help_category = "General"
def func(self):
"delete the character"
"""delete the character"""
player = self.player
if not self.args:
@ -196,23 +195,23 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
return
# use the playable_characters list to search
match = [char for char in utils.make_iter(player.db._playable_characters) if char.key.lower() == self.args.lower()]
match = [char for char in utils.make_iter(player.db._playable_characters)
if char.key.lower() == self.args.lower()]
if not match:
self.msg("You have no such character to delete.")
return
elif len(match) > 1:
self.msg("Aborting - there are two characters with the same name. Ask an admin to delete the right one.")
return
else: # one match
else: # one match
from evennia.utils.evmenu import get_input
def _callback(caller, prompt, result):
def _callback(caller, callback_prompt, result):
if result.lower() == "yes":
# only take action
delobj = caller.ndb._char_to_delete
key = delobj.key
caller.db._playable_characters = [char for char
in caller.db._playable_characters if char != delobj]
caller.db._playable_characters = [pc for pc in caller.db._playable_characters if pc != delobj]
delobj.delete()
self.msg("Character '%s' was permanently deleted." % key)
else:
@ -272,7 +271,8 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
self.msg("That is not a valid character choice.")
return
if len(new_character) > 1:
self.msg("Multiple targets with the same name:\n %s" % ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character))
self.msg("Multiple targets with the same name:\n %s"
% ", ".join("%s(#%s)" % (obj.key, obj.id) for obj in new_character))
return
else:
new_character = new_character[0]
@ -280,7 +280,7 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
player.puppet_object(session, new_character)
player.db._last_puppet = new_character
except RuntimeError as exc:
self.msg("{rYou cannot become {C%s{n: %s" % (new_character.name, exc))
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
# note that this is inheriting from MuxPlayerLookCommand,
@ -306,7 +306,7 @@ class CmdOOC(MuxPlayerLookCommand):
player_caller = True
def func(self):
"Implement function"
"""Implement function"""
player = self.player
session = self.session
@ -322,17 +322,18 @@ class CmdOOC(MuxPlayerLookCommand):
# disconnect
try:
player.unpuppet_object(session)
self.msg("\n{GYou go OOC.{n\n")
self.msg("\n|GYou go OOC.|n\n")
if _MULTISESSION_MODE < 2:
# only one character allowed
self.msg("You are out-of-character (OOC).\nUse {w@ic{n to get back into the game.")
self.msg("You are out-of-character (OOC).\nUse |w@ic|n to get back into the game.")
return
self.msg(player.at_look(target=self.playable, session=session))
except RuntimeError as exc:
self.msg("{rCould not unpuppet from {c%s{n: %s" % (old_char, exc))
self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc))
class CmdSessions(COMMAND_DEFAULT_CLASS):
"""
@ -352,23 +353,21 @@ class CmdSessions(COMMAND_DEFAULT_CLASS):
player_caller = True
def func(self):
"Implement function"
"""Implement function"""
player = self.player
sessions = player.sessions.all()
table = prettytable.PrettyTable(["{wsessid",
"{wprotocol",
"{whost",
"{wpuppet/character",
"{wlocation"])
table = evtable.EvTable("|wsessid",
"|wprotocol",
"|whost",
"|wpuppet/character",
"|wlocation")
for sess in sorted(sessions, key=lambda x: x.sessid):
char = player.get_puppet(sess)
table.add_row([str(sess.sessid), str(sess.protocol_key),
type(sess.address) == tuple and sess.address[0] or sess.address,
char and str(char) or "None",
char and str(char.location) or "N/A"])
string = "{wYour current session(s):{n\n%s" % table
self.msg(string)
table.add_row(str(sess.sessid), str(sess.protocol_key),
type(sess.address) == tuple and sess.address[0] or sess.address,
char and str(char) or "None",
char and str(char.location) or "N/A")
self.msg("|wYour current session(s):|n\n%s" % table)
class CmdWho(COMMAND_DEFAULT_CLASS):
@ -408,45 +407,45 @@ class CmdWho(COMMAND_DEFAULT_CLASS):
nplayers = (SESSIONS.player_count())
if show_session_data:
# privileged info
table = prettytable.PrettyTable(["{wPlayer Name",
"{wOn for",
"{wIdle",
"{wPuppeting",
"{wRoom",
"{wCmds",
"{wProtocol",
"{wHost"])
for session in session_list:
if not session.logged_in: continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
player = session.get_player()
puppet = session.get_puppet()
location = puppet.location.key if puppet and puppet.location else "None"
table.add_row([utils.crop(player.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1),
utils.crop(puppet.key if puppet else "None", width=25),
utils.crop(location, width=25),
session.cmd_total,
session.protocol_key,
isinstance(session.address, tuple) and session.address[0] or session.address])
else:
# unprivileged
table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{wIdle"])
table = evtable.EvTable("|wPlayer Name",
"|wOn for",
"|wIdle",
"|wPuppeting",
"|wRoom",
"|wCmds",
"|wProtocol",
"|wHost")
for session in session_list:
if not session.logged_in:
continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
player = session.get_player()
table.add_row([utils.crop(player.key, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1)])
isone = nplayers == 1
string = "{wPlayers:{n\n%s\n%s unique account%s logged in." % (table, "One" if isone else nplayers, "" if isone else "s")
self.msg(string)
puppet = session.get_puppet()
location = puppet.location.key if puppet and puppet.location else "None"
table.add_row(utils.crop(player.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1),
utils.crop(puppet.key if puppet else "None", width=25),
utils.crop(location, width=25),
session.cmd_total,
session.protocol_key,
isinstance(session.address, tuple) and session.address[0] or session.address)
else:
# unprivileged
table = evtable.EvTable("|wPlayer name", "|wOn for", "|wIdle")
for session in session_list:
if not session.logged_in:
continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
player = session.get_player()
table.add_row(utils.crop(player.key, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1))
is_one = nplayers == 1
self.msg("|wPlayers:|n\n%s\n%s unique account%s logged in."
% (table, "One" if is_one else nplayers, "" if is_one else "s"))
class CmdOption(COMMAND_DEFAULT_CLASS):
@ -489,13 +488,13 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
if "save" in self.switches:
# save all options
self.caller.db._saved_protocol_flags = flags
self.msg("{gSaved all options. Use @option/clear to remove.{n")
self.msg("|gSaved all options. Use @option/clear to remove.|n")
if "clear" in self.switches:
# clear all saves
self.caller.db._saved_protocol_flags = {}
self.msg("{gCleared all saved options.")
self.msg("|gCleared all saved options.")
options = dict(flags) # make a copy of the flag dict
options = dict(flags) # make a copy of the flag dict
saved_options = dict(self.caller.attributes.get("_saved_protocol_flags", default={}))
if "SCREENWIDTH" in options:
@ -503,7 +502,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
options["SCREENWIDTH"] = options["SCREENWIDTH"][0]
else:
options["SCREENWIDTH"] = " \n".join("%s : %s" % (screenid, size)
for screenid, size in options["SCREENWIDTH"].iteritems())
for screenid, size in options["SCREENWIDTH"].iteritems())
if "SCREENHEIGHT" in options:
if len(options["SCREENHEIGHT"]) == 1:
options["SCREENHEIGHT"] = options["SCREENHEIGHT"][0]
@ -521,8 +520,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
changed = "|y*|n" if key in saved_options and flags[key] != saved_options[key] else ""
row.append("%s%s" % (saved, changed))
table.add_row(*row)
self.msg("{wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table))
self.msg("|wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table))
return
@ -532,30 +530,30 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
# Try to assign new values
def validate_encoding(val):
def validate_encoding(new_encoding):
# helper: change encoding
try:
utils.to_str(utils.to_unicode("test-string"), encoding=val)
utils.to_str(utils.to_unicode("test-string"), encoding=new_encoding)
except LookupError:
raise RuntimeError("The encoding '|w%s|n' is invalid. " % val)
raise RuntimeError("The encoding '|w%s|n' is invalid. " % new_encoding)
return val
def validate_size(val):
return {0: int(val)}
def validate_size(new_size):
return {0: int(new_size)}
def validate_bool(val):
return True if val.lower() in ("true", "on", "1") else False
def validate_bool(new_bool):
return True if new_bool.lower() in ("true", "on", "1") else False
def update(name, val, validator):
def update(new_name, new_val, validator):
# helper: update property and report errors
try:
old_val = flags[name]
new_val = validator(val)
flags[name] = new_val
self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (name, old_val, new_val))
return {name: new_val}
old_val = flags.get(new_name, False)
new_val = validator(new_val)
flags[new_name] = new_val
self.msg("Option |w%s|n was changed from '|w%s|n' to '|w%s|n'." % (new_name, old_val, new_val))
return {new_name: new_val}
except Exception, err:
self.msg("|rCould not set option |w%s|r:|n %s" % (name, err))
self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err))
return False
validators = {"ANSI": validate_bool,
@ -590,16 +588,15 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
saved_options.update(optiondict)
self.player.attributes.add("_saved_protocol_flags", saved_options)
for key in optiondict:
self.msg("{gSaved option %s.{n" % key)
self.msg("|gSaved option %s.|n" % key)
if "clear" in self.switches:
# clear this save
for key in optiondict:
self.player.attributes.get("_saved_protocol_flags", {}).pop(key, None)
self.msg("{gCleared saved %s." % key)
self.msg("|gCleared saved %s." % key)
self.session.update_flags(**optiondict)
class CmdPassword(COMMAND_DEFAULT_CLASS):
"""
change your password
@ -616,14 +613,14 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
player_caller = True
def func(self):
"hook function."
"""hook function."""
player = self.player
if not self.rhs:
self.msg("Usage: @password <oldpass> = <newpass>")
return
oldpass = self.lhslist[0] # this is already stripped by parse()
newpass = self.rhslist[0] # ''
oldpass = self.lhslist[0] # Both of these are
newpass = self.rhslist[0] # already stripped by parse()
if not player.check_password(oldpass):
self.msg("The specified old password isn't correct.")
elif len(newpass) < 3:
@ -654,11 +651,11 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
player_caller = True
def func(self):
"hook function"
"""hook function"""
player = self.player
if 'all' in self.switches:
player.msg("{RQuitting{n all sessions. Hope to see you soon again.", session=self.session)
player.msg("|RQuitting|n all sessions. Hope to see you soon again.", session=self.session)
for session in player.sessions.all():
player.disconnect_session_from_player(session)
else:
@ -673,7 +670,6 @@ class CmdQuit(COMMAND_DEFAULT_CLASS):
player.disconnect_session_from_player(self.session)
class CmdColorTest(COMMAND_DEFAULT_CLASS):
"""
testing which colors your client support
@ -695,23 +691,23 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
player_caller = True
def table_format(self, table):
"""
Helper method to format the ansi/xterm256 tables.
Takes a table of columns [[val,val,...],[val,val,...],...]
"""
if not table:
return [[]]
"""
Helper method to format the ansi/xterm256 tables.
Takes a table of columns [[val,val,...],[val,val,...],...]
"""
if not table:
return [[]]
extra_space = 1
max_widths = [max([len(str(val)) for val in col]) for col in table]
ftable = []
for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
for icol, col in enumerate(table)])
return ftable
extra_space = 1
max_widths = [max([len(str(val)) for val in col]) for col in table]
ftable = []
for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " *
extra_space for icol, col in enumerate(table)])
return ftable
def func(self):
"Show color tables"
"""Show color tables"""
if self.args.startswith("a"):
# show ansi 16-color table
@ -721,9 +717,11 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
# show all ansi color-related codes
col1 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[48:56]]
col2 = ["%s%s|n" % (code, code.replace("|", "||")) for code, _ in ap.ext_ansi_map[56:64]]
col3 = ["%s%s|n" % (code.replace("\\",""), code.replace("|", "||").replace("\\", "")) for code, _ in ap.ext_ansi_map[-8:]]
col4 = ["%s%s|n" % (code.replace("\\",""), code.replace("|", "||").replace("\\", "")) for code, _ in ap.ansi_bright_bgs[-8:]]
col2.extend(["" for i in range(len(col1)-len(col2))])
col3 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ext_ansi_map[-8:]]
col4 = ["%s%s|n" % (code.replace("\\", ""), code.replace("|", "||").replace("\\", ""))
for code, _ in ap.ansi_bright_bgs[-8:]]
col2.extend(["" for _ in range(len(col1)-len(col2))])
table = utils.format_table([col1, col2, col4, col3])
string = "ANSI colors:"
for row in table:
@ -742,9 +740,8 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
# foreground table
table[ir].append("|%i%i%i%s|n" % (ir, ig, ib, "||%i%i%i" % (ir, ig, ib)))
# background table
table[6+ir].append("|%i%i%i|[%i%i%i%s|n" % (5 - ir, 5 - ig, 5 - ib,
ir, ig, ib,
"||[%i%i%i" % (ir, ig, ib)))
table[6+ir].append("|%i%i%i|[%i%i%i%s|n"
% (5 - ir, 5 - ig, 5 - ib, ir, ig, ib, "||[%i%i%i" % (ir, ig, ib)))
table = self.table_format(table)
string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):"
string += "\n" + "\n".join("".join(row) for row in table)
@ -753,15 +750,15 @@ class CmdColorTest(COMMAND_DEFAULT_CLASS):
for igray in range(6):
letter = chr(97 + (ibatch*6 + igray))
inverse = chr(122 - (ibatch*6 + igray))
table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % (letter)))
table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % (letter)))
table[0 + igray].append("|=%s%s |n" % (letter, "||=%s" % letter))
table[6 + igray].append("|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter))
for igray in range(6):
# the last row (y, z) has empty columns
if igray < 2:
letter = chr(121 + igray)
inverse = chr(98 - igray)
fg = "|=%s%s |n" % (letter, "||=%s" % (letter))
bg = "|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % (letter))
fg = "|=%s%s |n" % (letter, "||=%s" % letter)
bg = "|=%s|[=%s%s |n" % (inverse, letter, "||[=%s" % letter)
else:
fg, bg = " ", " "
table[0 + igray].append(fg)
@ -800,7 +797,7 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
player_caller = True
def _recache_locks(self, player):
"Helper method to reset the lockhandler on an already puppeted object"
"""Helper method to reset the lockhandler on an already puppeted object"""
if self.session:
char = self.session.puppet
if char:
@ -811,25 +808,26 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
player.locks.reset()
def func(self):
"Perform the command"
"""Perform the command"""
player = self.player
permstr = player.is_superuser and " (superuser)" or " (%s)" % (", ".join(player.permissions.all()))
permstr = player.is_superuser and " (superuser)" or "(%s)" % (", ".join(player.permissions.all()))
if self.cmdstring == '@unquell':
if not player.attributes.get('_quell'):
self.msg("Already using normal Player permissions%s." % permstr)
self.msg("Already using normal Player permissions %s." % permstr)
else:
player.attributes.remove('_quell')
self.msg("Player permissions%s restored." % permstr)
self.msg("Player permissions %s restored." % permstr)
else:
if player.attributes.get('_quell'):
self.msg("Already quelling Player%s permissions." % permstr)
self.msg("Already quelling Player %s permissions." % permstr)
return
player.attributes.add('_quell', True)
puppet = self.session.puppet
if puppet:
cpermstr = " (%s)" % ", ".join(puppet.permissions.all())
cpermstr = "Quelling to current puppet's permissions%s." % cpermstr
cpermstr += "\n(Note: If this is higher than Player permissions%s, the lowest of the two will be used.)" % permstr
cpermstr = "(%s)" % ", ".join(puppet.permissions.all())
cpermstr = "Quelling to current puppet's permissions %s." % cpermstr
cpermstr += "\n(Note: If this is higher than Player permissions %s," \
" the lowest of the two will be used.)" % permstr
cpermstr += "\nUse @unquell to return to normal permission usage."
self.msg(cpermstr)
else:

View file

@ -109,7 +109,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"Define function"
"""Define function"""
# Only allow shutdown if caller has session
if not self.caller.sessions.get():
return
@ -126,6 +126,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
def _py_load(caller):
return ""
def _py_code(caller, buf):
"""
Execute the buffer.
@ -139,10 +140,12 @@ def _py_code(caller, buf):
show_input=False)
return True
def _py_quit(caller):
del caller.db._py_measure_time
caller.msg("Exited the code editor.")
def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
show_input=True):
"""
@ -174,9 +177,9 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
if show_input:
try:
caller.msg(">>> %s" % pycode, session=session,
options={"raw":True})
options={"raw": True})
except TypeError:
caller.msg(">>> %s" % pycode, options={"raw":True})
caller.msg(">>> %s" % pycode, options={"raw": True})
try:
try:
@ -204,9 +207,10 @@ def _run_code_snippet(caller, pycode, mode="eval", measure_time=False,
ret = "\n".join("%s" % line for line in errlist if line)
try:
caller.msg(ret, session=session, options={"raw":True})
caller.msg(ret, session=session, options={"raw": True})
except TypeError:
caller.msg(ret, options={"raw":True})
caller.msg(ret, options={"raw": True})
class CmdPy(COMMAND_DEFAULT_CLASS):
"""
@ -234,8 +238,8 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
You can explore The evennia API from inside the game by calling
evennia.help(), evennia.managers.help() etc.
{rNote: In the wrong hands this command is a severe security risk.
It should only be accessible by trusted server admins/superusers.{n
|rNote: In the wrong hands this command is a severe security risk.
It should only be accessible by trusted server admins/superusers.|n
"""
key = "@py"
@ -244,7 +248,7 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"hook function"
"""hook function"""
caller = self.caller
pycode = self.args
@ -266,13 +270,14 @@ class CmdPy(COMMAND_DEFAULT_CLASS):
# helper function. Kept outside so it can be imported and run
# by other commands.
def format_script_list(scripts):
"Takes a list of scripts and formats the output."
"""Takes a list of scripts and formats the output."""
if not scripts:
return "<No scripts>"
table = EvTable("{wdbref{n", "{wobj{n", "{wkey{n", "{wintval{n", "{wnext{n",
"{wrept{n", "{wdb", "{wtypeclass{n", "{wdesc{n",
table = EvTable("|wdbref|n", "|wobj|n", "|wkey|n", "|wintval|n", "|wnext|n",
"|wrept|n", "|wdb", "|wtypeclass|n", "|wdesc|n",
align='r', border="tablecols")
for script in scripts:
nextrep = script.time_until_next_repeat()
@ -326,12 +331,11 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"implement method"
"""implement method"""
caller = self.caller
args = self.args
string = ""
if args:
if "start" in self.switches:
# global script-start mode
@ -374,9 +378,9 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
else:
string = "Stopping script '%s'." % scripts[0].key
scripts[0].stop()
#import pdb
#pdb.set_trace()
ScriptDB.objects.validate() #just to be sure all is synced
# import pdb # DEBUG
# pdb.set_trace() # DEBUG
ScriptDB.objects.validate() # just to be sure all is synced
else:
# multiple matches.
string = "Multiple script matches. Please refine your search:\n"
@ -409,26 +413,21 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"Implement the command"
"""Implement the command"""
caller = self.caller
if self.args and self.args.isdigit():
nlim = int(self.args)
else:
nlim = 10
nlim = int(self.args) if self.args and self.args.isdigit() else 10
nobjs = ObjectDB.objects.count()
base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS
nchars = ObjectDB.objects.filter(db_typeclass_path=base_char_typeclass).count()
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=base_char_typeclass).count()
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(
db_typeclass_path=base_char_typeclass).count()
nexits = ObjectDB.objects.filter(db_location__isnull=False, db_destination__isnull=False).count()
nother = nobjs - nchars - nrooms - nexits
nobjs = nobjs or 1 # fix zero-div error with empty database
nobjs = nobjs or 1 # fix zero-div error with empty database
# total object sum table
totaltable = EvTable("{wtype{n", "{wcomment{n", "{wcount{n", "{w%%{n", border="table", align="l")
totaltable = EvTable("|wtype|n", "|wcomment|n", "|wcount|n", "|w%%|n", border="table", align="l")
totaltable.align = 'l'
totaltable.add_row("Characters", "(BASE_CHARACTER_TYPECLASS)", nchars, "%.2f" % ((float(nchars) / nobjs) * 100))
totaltable.add_row("Rooms", "(location=None)", nrooms, "%.2f" % ((float(nrooms) / nobjs) * 100))
@ -436,7 +435,7 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
totaltable.add_row("Other", "", nother, "%.2f" % ((float(nother) / nobjs) * 100))
# typeclass table
typetable = EvTable("{wtypeclass{n", "{wcount{n", "{w%%{n", border="table", align="l")
typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="table", align="l")
typetable.align = 'l'
dbtotals = ObjectDB.objects.object_totals()
for path, count in dbtotals.items():
@ -444,15 +443,15 @@ class CmdObjects(COMMAND_DEFAULT_CLASS):
# last N table
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim):]
latesttable = EvTable("{wcreated{n", "{wdbref{n", "{wname{n", "{wtypeclass{n", align="l", border="table")
latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", align="l", border="table")
latesttable.align = 'l'
for obj in objs:
latesttable.add_row(utils.datetime_format(obj.date_created),
obj.dbref, obj.key, obj.path)
string = "\n{wObject subtype totals (out of %i Objects):{n\n%s" % (nobjs, totaltable)
string += "\n{wObject typeclass distribution:{n\n%s" % typetable
string += "\n{wLast %s Objects created:{n\n%s" % (min(nobjs, nlim), latesttable)
string = "\n|wObject subtype totals (out of %i Objects):|n\n%s" % (nobjs, totaltable)
string += "\n|wObject typeclass distribution:|n\n%s" % typetable
string += "\n|wLast %s Objects created:|n\n%s" % (min(nobjs, nlim), latesttable)
caller.msg(string)
@ -473,7 +472,7 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"List the players"
"""List the players"""
caller = self.caller
if self.args and self.args.isdigit():
@ -485,17 +484,17 @@ class CmdPlayers(COMMAND_DEFAULT_CLASS):
# typeclass table
dbtotals = PlayerDB.objects.object_totals()
typetable = EvTable("{wtypeclass{n", "{wcount{n", "{w%%{n", border="cells", align="l")
typetable = EvTable("|wtypeclass|n", "|wcount|n", "|w%%|n", border="cells", align="l")
for path, count in dbtotals.items():
typetable.add_row(path, count, "%.2f" % ((float(count) / nplayers) * 100))
# last N table
plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):]
latesttable = EvTable("{wcreated{n", "{wdbref{n", "{wname{n", "{wtypeclass{n", border="cells", align="l")
latesttable = EvTable("|wcreated|n", "|wdbref|n", "|wname|n", "|wtypeclass|n", border="cells", align="l")
for ply in plyrs:
latesttable.add_row(utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path)
string = "\n{wPlayer typeclass distribution:{n\n%s" % typetable
string += "\n{wLast %s Players created:{n\n%s" % (min(nplayers, nlim), latesttable)
string = "\n|wPlayer typeclass distribution:|n\n%s" % typetable
string += "\n|wLast %s Players created:|n\n%s" % (min(nplayers, nlim), latesttable)
caller.msg(string)
@ -525,7 +524,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"Implement command"
"""Implement command"""
caller = self.caller
switches = self.switches
@ -540,9 +539,9 @@ class CmdService(COMMAND_DEFAULT_CLASS):
if not switches or switches[0] == "list":
# Just display the list of installed services and their
# status, then exit.
table = EvTable("{wService{n (use @services/start|stop|delete)", "{wstatus", align="l")
table = EvTable("|wService|n (use @services/start|stop|delete)", "|wstatus", align="l")
for service in service_collection.services:
table.add_row(service.name, service.running and "{gRunning" or "{rNot Running")
table.add_row(service.name, service.running and "|gRunning" or "|rNot Running")
caller.msg(unicode(table))
return
@ -584,7 +583,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
return
if switches[0] == "start":
#Starts a service.
# Attempt to start a service.
if service.running:
caller.msg('That service is already running.')
return
@ -608,23 +607,23 @@ class CmdAbout(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"Show the version"
"""Display information about server or target"""
string = """
{cEvennia{n %s{n
|cEvennia|n %s|n
MUD/MUX/MU* development system
{wLicence{n BSD 3-Clause Licence
{wWeb{n http://www.evennia.com
{wIrc{n #evennia on FreeNode
{wForum{n http://www.evennia.com/discussions
{wMaintainer{n (2010-) Griatch (griatch AT gmail DOT com)
{wMaintainer{n (2006-10) Greg Taylor
|wLicence|n https://opensource.org/licenses/BSD-3-Clause
|wWeb|n http://www.evennia.com
|wIrc|n #evennia on FreeNode
|wForum|n http://www.evennia.com/discussions
|wMaintainer|n (2010-) Griatch (griatch AT gmail DOT com)
|wMaintainer|n (2006-10) Greg Taylor
{wOS{n %s
{wPython{n %s
{wTwisted{n %s
{wDjango{n %s
|wOS|n %s
|wPython|n %s
|wTwisted|n %s
|wDjango|n %s
""" % (utils.get_evennia_version(),
os.name,
sys.version.split()[0],
@ -649,8 +648,8 @@ class CmdTime(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"Show server time data in a table."
table1 = EvTable("|wServer time","", align="l", width=78)
"""Show server time data in a table."""
table1 = EvTable("|wServer time", "", align="l", width=78)
table1.add_row("Current uptime", utils.time_format(gametime.uptime(), 3))
table1.add_row("Total runtime", utils.time_format(gametime.runtime(), 2))
table1.add_row("First start", datetime.datetime.fromtimestamp(gametime.server_epoch()))
@ -682,20 +681,20 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
Some Important statistics in the table:
{wServer load{n is an average of processor usage. It's usually
|wServer load|n is an average of processor usage. It's usually
between 0 (no usage) and 1 (100% usage), but may also be
temporarily higher if your computer has multiple CPU cores.
The {wResident/Virtual memory{n displays the total memory used by
The |wResident/Virtual memory|n displays the total memory used by
the server process.
Evennia {wcaches{n all retrieved database entities when they are
Evennia |wcaches|n all retrieved database entities when they are
loaded by use of the idmapper functionality. This allows Evennia
to maintain the same instances of an entity and allowing
non-persistent storage schemes. The total amount of cached objects
are displayed plus a breakdown of database object types.
The {wflushmem{n switch allows to flush the object cache. Please
The |wflushmem|n switch allows to flush the object cache. Please
note that due to how Python's memory management works, releasing
caches may not show you a lower Residual/Virtual memory footprint,
the released memory will instead be re-used by the program.
@ -707,7 +706,7 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
help_category = "System"
def func(self):
"Show list."
"""Show list."""
global _IDMAPPER
if not _IDMAPPER:
@ -741,21 +740,21 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
if has_psutil:
loadavg = psutil.cpu_percent()
_mem = psutil.virtual_memory()
rmem = _mem.used / (1000.0 * 1000)
rmem = _mem.used / (1000.0 * 1000)
pmem = _mem.percent
if "mem" in self.switches:
string = "Total computer memory usage: {w%g{n MB (%g%%)"
string = "Total computer memory usage: |w%g|n MB (%g%%)"
self.caller.msg(string % (rmem, pmem))
return
# Display table
loadtable = EvTable("property", "statistic", align="l")
loadtable.add_row("Total CPU load", "%g %%" % loadavg)
loadtable.add_row("Total computer memory usage","%g MB (%g%%)" % (rmem, pmem))
loadtable.add_row("Total computer memory usage", "%g MB (%g%%)" % (rmem, pmem))
loadtable.add_row("Process ID", "%g" % pid),
else:
loadtable = "Not available on Windows without 'psutil' library " \
"(install with {wpip install psutil{n)."
"(install with |wpip install psutil|n)."
else:
# Linux / BSD (OSX) - proper pid-based statistics
@ -767,46 +766,49 @@ class CmdServerLoad(COMMAND_DEFAULT_CLASS):
loadavg = os.getloadavg()[0]
rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1000.0 # resident memory
vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1000.0 # virtual memory
pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # percent of resident memory to total
pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # % of resident memory to total
rusage = _RESOURCE.getrusage(_RESOURCE.RUSAGE_SELF)
if "mem" in self.switches:
string = "Memory usage: RMEM: {w%g{n MB (%g%%), " \
" VMEM (res+swap+cache): {w%g{n MB."
string = "Memory usage: RMEM: |w%g|n MB (%g%%), VMEM (res+swap+cache): |w%g|n MB."
self.caller.msg(string % (rmem, pmem, vmem))
return
loadtable = EvTable("property", "statistic", align="l")
loadtable.add_row("Server load (1 min)", "%g" % loadavg)
loadtable.add_row("Process ID", "%g" % pid),
loadtable.add_row("Memory usage","%g MB (%g%%)" % (rmem, pmem))
loadtable.add_row("Memory usage", "%g MB (%g%%)" % (rmem, pmem))
loadtable.add_row("Virtual address space", "")
loadtable.add_row("{x(resident+swap+caching){n", "%g MB" % vmem)
loadtable.add_row("CPU time used (total)", "%s (%gs)" % (utils.time_format(rusage.ru_utime), rusage.ru_utime))
loadtable.add_row("CPU time used (user)", "%s (%gs)" % (utils.time_format(rusage.ru_stime), rusage.ru_stime))
loadtable.add_row("Page faults", "%g hard, %g soft, %g swapouts" % (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap))
loadtable.add_row("|x(resident+swap+caching)|n", "%g MB" % vmem)
loadtable.add_row("CPU time used (total)", "%s (%gs)"
% (utils.time_format(rusage.ru_utime), rusage.ru_utime))
loadtable.add_row("CPU time used (user)", "%s (%gs)"
% (utils.time_format(rusage.ru_stime), rusage.ru_stime))
loadtable.add_row("Page faults", "%g hard, %g soft, %g swapouts"
% (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap))
loadtable.add_row("Disk I/O", "%g reads, %g writes" % (rusage.ru_inblock, rusage.ru_oublock))
loadtable.add_row("Network I/O", "%g in, %g out" % (rusage.ru_msgrcv, rusage.ru_msgsnd))
loadtable.add_row("Context switching", "%g vol, %g forced, %g signals" % (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals))
loadtable.add_row("Context switching", "%g vol, %g forced, %g signals"
% (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals))
# os-generic
string = "{wServer CPU and Memory load:{n\n%s" % loadtable
string = "|wServer CPU and Memory load:|n\n%s" % loadtable
# object cache count (note that sys.getsiseof is not called so this works for pypy too.
total_num, cachedict = _IDMAPPER.cache_size()
sorted_cache = sorted([(key, num) for key, num in cachedict.items() if num > 0],
key=lambda tup: tup[1], reverse=True)
key=lambda tup: tup[1], reverse=True)
memtable = EvTable("entity name", "number", "idmapper %", align="l")
for tup in sorted_cache:
memtable.add_row(tup[0], "%i" % tup[1], "%.2f" % (float(tup[1]) / total_num * 100))
string += "\n{w Entity idmapper cache:{n %i items\n%s" % (total_num, memtable)
string += "\n|w Entity idmapper cache:|n %i items\n%s" % (total_num, memtable)
# return to caller
self.caller.msg(string)
class CmdTickers(COMMAND_DEFAULT_CLASS):
"""
View running tickers
@ -832,13 +834,9 @@ class CmdTickers(COMMAND_DEFAULT_CLASS):
table = EvTable("interval (s)", "object", "path/methodname", "idstring", "db")
for sub in all_subs:
table.add_row(sub[3],
"%s%s" % (sub[0] or "[None]", sub[0] and " (#%s)" % (sub[0].id if hasattr(sub[0], "id") else "") or ""),
"%s%s" % (sub[0] or "[None]",
sub[0] and " (#%s)" % (sub[0].id if hasattr(sub[0], "id") else "") or ""),
sub[1] if sub[1] else sub[2],
sub[4] or "[Unset]",
"*" if sub[5] else "-")
self.caller.msg("|wActive tickers|n:\n" + unicode(table))

View file

@ -38,7 +38,7 @@ class CommandTest(EvenniaTest):
Tests a command
"""
def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, receiver=None, cmdstring=None):
def call(self, cmdobj, args, msg=None, cmdset=None, noansi=True, caller=None, receiver=None, cmdstring=None, obj=None):
"""
Test a command by assigning all the needed
properties to cmdobj and running
@ -48,6 +48,10 @@ class CommandTest(EvenniaTest):
cmdobj.at_post_cmd()
The msgreturn value is compared to eventual
output sent to caller.msg in the game
Returns:
msg (str): The received message that was sent to the caller.
"""
caller = caller if caller else self.char1
receiver = receiver if receiver else caller
@ -60,9 +64,10 @@ class CommandTest(EvenniaTest):
cmdobj.session = SESSIONS.session_from_sessid(1)
cmdobj.player = self.player
cmdobj.raw_string = cmdobj.key + " " + args
cmdobj.obj = caller if caller else self.char1
cmdobj.obj = obj or (caller if caller else self.char1)
# test
old_msg = receiver.msg
returned_msg = ""
try:
receiver.msg = Mock()
cmdobj.at_pre_cmd()
@ -74,18 +79,23 @@ class CommandTest(EvenniaTest):
for name, args, kwargs in receiver.msg.mock_calls]
# Get the first element of a tuple if msg received a tuple instead of a string
stored_msg = [smsg[0] if isinstance(smsg, tuple) else smsg for smsg in stored_msg]
returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg)
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
if msg is not None:
returned_msg = "||".join(_RE.sub("", mess) for mess in stored_msg)
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
if msg == "" and returned_msg or not returned_msg.startswith(msg.strip()):
sep1 = "\n" + "="*30 + "Wanted message" + "="*34 + "\n"
sep2 = "\n" + "="*30 + "Returned message" + "="*32 + "\n"
sep3 = "\n" + "="*78
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
raise AssertionError(retval)
else:
returned_msg = "\n".join(stored_msg)
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
finally:
receiver.msg = old_msg
return returned_msg
# ------------------------------------------------------------
# Individual module Tests
# ------------------------------------------------------------
@ -119,6 +129,9 @@ class TestGeneral(CommandTest):
def test_say(self):
self.call(general.CmdSay(), "Testing", "You say, \"Testing\"")
def test_whisper(self):
self.call(general.CmdWhisper(), "Obj = Testing", "You whisper to Obj, \"Testing\"")
def test_access(self):
self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):")

View file

@ -30,6 +30,8 @@ CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE
# would also block dummyrunner, so it's not added as default.
_LATEST_FAILED_LOGINS = defaultdict(list)
def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS):
"""
This will check the session's address against the
@ -95,8 +97,8 @@ def create_guest_player(session):
bans = ServerConfig.objects.conf("server_bans")
if bans and any(tup[2].match(session.address) for tup in bans if tup[2]):
# this is a banned IP!
string = "{rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.{x"
string = "|rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.|x"
session.msg(string)
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return True, None
@ -118,14 +120,11 @@ def create_guest_player(session):
permissions = settings.PERMISSION_GUEST_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
ptypeclass = settings.BASE_GUEST_TYPECLASS
new_player = _create_player(session, playername, password,
permissions, ptypeclass)
new_player = _create_player(session, playername, password, permissions, ptypeclass)
if new_player:
_create_character(session, new_player, typeclass,
home, permissions)
_create_character(session, new_player, typeclass, home, permissions)
return True, new_player
except Exception:
# We are in the middle between logged in and -not, so we have
# to handle tracebacks ourselves at this point. If we don't,
@ -150,7 +149,7 @@ def create_normal_player(session, name, password):
# check for too many login errors too quick.
if _throttle(session, maxlim=5, timeout=5*60):
# timeout is 5 minutes.
session.msg("{RYou made too many connection attempts. Try again in a few minutes.{n")
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
return None
# Match account name and check password
@ -167,15 +166,14 @@ def create_normal_player(session, name, password):
player.at_failed_login(session)
return None
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0]==player.name.lower() for tup in bans)
if bans and (any(tup[0] == player.name.lower() for tup in bans)
or
any(tup[2].match(session.address) for tup in bans if tup[2])):
# this is a banned IP or name!
string = "{rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.{x"
string = "|rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.|x"
session.msg(string)
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return None
@ -213,7 +211,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
# check for too many login errors too quick.
if _throttle(session, maxlim=5, timeout=5*60, storage=_LATEST_FAILED_LOGINS):
# timeout is 5 minutes.
session.msg("{RYou made too many connection attempts. Try again in a few minutes.{n")
session.msg("|RYou made too many connection attempts. Try again in a few minutes.|n")
return
args = self.args
@ -237,12 +235,6 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
name, password = parts
player = create_normal_player(session, name, password)
if player:
# actually do the login. This will call all other hooks:
# session.at_login()
# player.at_init() # always called when object is loaded from disk
# player.at_first_login() # only once, for player-centric setup
# player.at_pre_login()
# player.at_post_login(session=session)
session.sessionhandler.login(session, player)
@ -264,7 +256,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
arg_regex = r"\s.*?|$"
def func(self):
"Do checks and create account"
"""Do checks and create account"""
session = self.caller
args = self.args.strip()
@ -309,12 +301,12 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0]==playername.lower() for tup in bans)
if bans and (any(tup[0] == playername.lower() for tup in bans)
or
any(tup[2].match(session.address) for tup in bans if tup[2])):
# this is a banned IP or name!
string = "{rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.{x"
string = "|rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.|x"
session.msg(string)
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return
@ -327,8 +319,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
if new_player:
if MULTISESSION_MODE < 2:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
_create_character(session, new_player, typeclass,
default_home, permissions)
_create_character(session, new_player, typeclass, default_home, permissions)
# tell the caller everything went well.
string = "A new account '%s' was created. Welcome!"
if " " in playername:
@ -361,7 +352,7 @@ class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()"
def func(self):
"Simply close the connection."
"""Simply close the connection."""
session = self.caller
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
@ -383,7 +374,7 @@ class CmdUnconnectedLook(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()"
def func(self):
"Show the connect screen."
"""Show the connect screen."""
connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE)
if not connection_screen:
connection_screen = "No connection screen found. Please contact an admin."
@ -405,25 +396,25 @@ class CmdUnconnectedHelp(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()"
def func(self):
"Shows help"
"""Shows help"""
string = \
"""
You are not yet logged into the game. Commands available at this point:
{wcreate{n - create a new account
{wconnect{n - connect with an existing account
{wlook{n - re-show the connection screen
{whelp{n - show this help
{wencoding{n - change the text encoding to match your client
{wscreenreader{n - make the server more suitable for use with screen readers
{wquit{n - abort the connection
|wcreate|n - create a new account
|wconnect|n - connect with an existing account
|wlook|n - re-show the connection screen
|whelp|n - show this help
|wencoding|n - change the text encoding to match your client
|wscreenreader|n - make the server more suitable for use with screen readers
|wquit|n - abort the connection
First create an account e.g. with {wcreate Anna c67jHL8p{n
(If you have spaces in your name, use double quotes: {wcreate "Anna the Barbarian" c67jHL8p{n
Next you can connect to the game: {wconnect Anna c67jHL8p{n
First create an account e.g. with |wcreate Anna c67jHL8p|n
(If you have spaces in your name, use double quotes: |wcreate "Anna the Barbarian" c67jHL8p|n
Next you can connect to the game: |wconnect Anna c67jHL8p|n
You can use the {wlook{n command if you want to see the connect screen again.
You can use the |wlook|n command if you want to see the connect screen again.
"""
self.caller.msg(string)
@ -479,10 +470,10 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
pencoding = self.session.protocol_flags.get("ENCODING", None)
string = ""
if pencoding:
string += "Default encoding: {g%s{n (change with {w@encoding <encoding>{n)" % pencoding
string += "Default encoding: |g%s|n (change with |w@encoding <encoding>|n)" % pencoding
encodings = settings.ENCODINGS
if encodings:
string += "\nServer's alternative encodings (tested in this order):\n {g%s{n" % ", ".join(encodings)
string += "\nServer's alternative encodings (tested in this order):\n |g%s|n" % ", ".join(encodings)
if not string:
string = "No encodings found."
else:
@ -492,7 +483,8 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
try:
utils.to_str(utils.to_unicode("test-string"), encoding=encoding)
except LookupError:
string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n" % (encoding, old_encoding)
string = "|rThe encoding '|w%s|r' is invalid. Keeping the previous encoding '|w%s|r'.|n"\
% (encoding, old_encoding)
else:
self.session.protocol_flags["ENCODING"] = encoding
string = "Your custom text encoding was changed from '|w%s|n' to '|w%s|n'." % (old_encoding, encoding)
@ -501,6 +493,7 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
self.session.sessionhandler.session_portal_sync(self.session)
self.caller.msg(string.strip())
class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
"""
Activate screenreader mode.
@ -515,21 +508,20 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
aliases = "@screenreader"
def func(self):
"Flips screenreader setting."
"""Flips screenreader setting."""
new_setting = not self.session.protocol_flags.get("SCREENREADER", False)
self.session.protocol_flags["SCREENREADER"] = new_setting
string = "Screenreader mode turned {w%s{n." % ("on" if new_setting else "off")
self.session.protocol_flags["SCREENREADER"] = new_setting
string = "Screenreader mode turned |w%s|n." % ("on" if new_setting else "off")
self.caller.msg(string)
self.session.sessionhandler.session_portal_sync(self.session)
def _create_player(session, playername, password, permissions, typeclass=None):
def _create_player(session, playername, password, permissions, typeclass=None, email=None):
"""
Helper function, creates a player of the specified typeclass.
"""
try:
new_player = create.create_player(playername, None, password,
permissions=permissions, typeclass=typeclass)
new_player = create.create_player(playername, email, password, permissions=permissions, typeclass=typeclass)
except Exception as e:
session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % e)
@ -543,7 +535,7 @@ def _create_player(session, playername, password, permissions, typeclass=None):
# join the new player to the public channel
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
if not pchannel.connect(new_player):
if not pchannel or not pchannel.connect(new_player):
string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_err(string)
return new_player
@ -555,8 +547,7 @@ def _create_character(session, new_player, typeclass, home, permissions):
This is meant for Guest and MULTISESSION_MODE < 2 situations.
"""
try:
new_character = create.create_object(typeclass, key=new_player.key,
home=home, permissions=permissions)
new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions)
# set playable character list
new_player.db._playable_characters.append(new_character)

View file

@ -95,7 +95,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
listening = [ob for ob in subs if ob.is_connected and ob not in self.mutelist]
if subs:
# display listening subscribers in bold
string = ", ".join([player.key if player not in listening else "{w%s{n" % player.key for player in subs])
string = ", ".join([player.key if player not in listening else "|w%s|n" % player.key for player in subs])
else:
string = "<None>"
return string
@ -126,7 +126,6 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
return True
return False
def connect(self, subscriber):
"""
Connect the user to this channel. This checks access.
@ -254,7 +253,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
try:
# note our addition of the from_channel keyword here. This could be checked
# by a custom player.msg() to treat channel-receives differently.
entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel":self.id})
entity.msg(msgobj.message, from_obj=msgobj.senders, options={"from_channel": self.id})
except AttributeError as e:
logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity))
@ -331,7 +330,6 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
"""
self.msg(message, senders=senders, header=header, keep_log=False)
# hooks
def channel_prefix(self, msg=None, emit=False):
@ -376,7 +374,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
message accordingly.
Args:
msgob (Msg or TempMsg): The message to analyze for a pose.
msgobj (Msg or TempMsg): The message to analyze for a pose.
sender_string (str): The name of the sender/poser.
Returns:
@ -426,7 +424,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
Hook method. Formats a message body for display.
Args:
msgob (Msg or TempMsg): The message object to send.
msgobj (Msg or TempMsg): The message object to send.
emit (bool, optional): The message is agnostic of senders.
Returns:
@ -475,7 +473,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
value, leaving the channel will be aborted.
Args:
joiner (object): The joining object.
leaver (object): The leaving object.
Returns:
should_leave (bool): If `False`, channel parting is aborted.
@ -488,7 +486,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
Hook method. Runs right after an object or player leaves a channel.
Args:
joiner (object): The joining object.
leaver (object): The leaving object.
"""
pass

View file

@ -580,7 +580,7 @@ class CmdDecline(CmdTradeBase):
self.msg_other(caller, self.str_other
% "%s changes their mind, |Rdeclining|n the current offer." % caller.key)
else:
# no accept_ance to change
# no acceptance to change
caller.msg(self.str_caller % "You |Rdecline|n the current offer.")
self.msg_other(caller, self.str_other % "%s declines the current offer." % caller.key)

View file

@ -2,32 +2,25 @@
Contribution - Griatch 2011
[Note - with the advent of MULTISESSION_MODE=2, this is not really as
> Note - with the advent of MULTISESSION_MODE=2, this is not really as
necessary anymore - the ooclook and @charcreate commands in that mode
replaces this module with better functionality.]
replaces this module with better functionality. This remains here for
inspiration.
This is a simple character creation commandset. A suggestion is to
test this together with menu_login, which doesn't create a Character
on its own. This shows some more info and gives the Player the option
to create a character without any more customizations than their name
(further options are unique for each game anyway).
This is a simple character creation commandset for the Player level.
It shows some more info and gives the Player the option to create a
character without any more customizations than their name (further
options are unique for each game anyway).
Since this extends the OOC cmdset, logging in from the menu will
automatically drop the Player into this cmdset unless they logged off
while puppeting a Character already before.
In MULTISESSION_MODEs 0 and 1, you will automatically log into an
existing Character. When using `@ooc` you will then end up in this
cmdset.
Installation:
Read the instructions in contrib/examples/cmdset.py in order to create
a new default cmdset module for Evennia to use (copy the template up
one level, and change the settings file's relevant variables to point
to the cmdsets inside). If you already have such a module you should
of course use that.
Next import this module in your custom cmdset module and add the
following line to the end of OOCCmdSet's at_cmdset_creation():
self.add(chargen.OOCCmdSetCharGen)
Import this module to `mygame/commands/default_cmdsets.py` and
add `chargen.OOCCMdSetCharGen` to the `PlayerCmdSet` class
(it says where to add it). Reload.
"""
@ -179,7 +172,7 @@ class CmdOOCCharacterCreate(Command):
else:
avail_chars = [new_character.id]
self.caller.db._character_dbrefs = avail_chars
self.caller.msg("|gThe Character |c%s|g was successfully created!" % charname)
self.caller.msg("|gThe character |c%s|g was successfully created!" % charname)
class OOCCmdSetCharGen(default_cmds.PlayerCmdSet):

View file

@ -12,14 +12,23 @@ Usage:
Use as the normal gametime module, that is by importing and using the
helper functions in this module in your own code. The calendar can be
specified in your settings file by adding and setting custom values
for one or more of the variables `TIME_SECS_PER_MIN`,
`TIME_MINS_PER_HOUR`, `TIME_DAYS_PER_WEEK`, `TIME_WEEKS_PER_MONTH` and
`TIME_MONTHS_PER_YEAR`. These are all given in seconds and whereas
they are called "week", "month" etc these names could represent
whatever fits your game. You can also set `TIME_UNITS` to a dict
mapping the name of a unit to its length in seconds (like `{"min":
60, ...}. If not given, sane defaults will be used.
customized by adding the `TIME_UNITS` dictionary to your settings
file. This maps unit names to their length, expressed in the smallest
unit. Here's the default as an example:
TIME_UNITS = {
"sec": 1,
"min": 60,
"hr": 60 * 60,
"hour": 60 * 60,
"day": 60 * 60 * 24,
"week": 60 * 60 * 24 * 7,
"month": 60 * 60 * 24 * 7 * 4,
"yr": 60 * 60 * 24 * 7 * 4 * 12,
"year": 60 * 60 * 24 * 7 * 4 * 12, }
When using a custom calendar, these time unit names are used as kwargs to
the converter functions in this module.
"""
@ -28,33 +37,23 @@ mapping the name of a unit to its length in seconds (like `{"min":
from django.conf import settings
from evennia import DefaultScript
from evennia.utils.create import create_script
from evennia.utils.gametime import gametime
from evennia.utils import gametime
# The game time speedup / slowdown relative real time
TIMEFACTOR = settings.TIME_FACTOR
# Game-time units, in game time seconds. These are supplied as a
# convenient measure for determining the current in-game time, e.g.
# when defining in-game events. The words month, week and year can be
# used to mean whatever units of time are used in your game.
SEC = 1
MIN = getattr(settings, "TIME_SECS_PER_MIN", 60)
HOUR = getattr(settings, "TIME_MINS_PER_HOUR", 60) * MIN
DAY = getattr(settings, "TIME_HOURS_PER_DAY", 24) * HOUR
WEEK = getattr(settings, "TIME_DAYS_PER_WEEK", 7) * DAY
MONTH = getattr(settings, "TIME_WEEKS_PER_MONTH", 4) * WEEK
YEAR = getattr(settings, "TIME_MONTHS_PER_YEAR", 12) * MONTH
# these are the unit names understood by the scheduler.
# These are the unit names understood by the scheduler.
# Each unit must be consistent and expressed in seconds.
UNITS = getattr(settings, "TIME_UNITS", {
"sec": SEC,
"min": MIN,
"hr": HOUR,
"hour": HOUR,
"day": DAY,
"week": WEEK,
"month": MONTH,
"year": YEAR,
"yr": YEAR,
})
# default custom calendar
"sec": 1,
"min": 60,
"hr": 60 * 60,
"hour": 60 * 60,
"day": 60 * 60 * 24,
"week": 60 * 60 * 24 * 7,
"month": 60 * 60 * 24 * 7 * 4,
"yr": 60 * 60 * 24 * 7 * 4 * 12,
"year": 60 * 60 * 24 * 7 * 4 * 12, })
def time_to_tuple(seconds, *divisors):
@ -92,7 +91,8 @@ def gametime_to_realtime(format=False, **kwargs):
Kwargs:
format (bool): Formatting the output.
times (int): The various components of the time (must match UNITS).
days, month etc (int): These are the names of time units that must
match the `settings.TIME_UNITS` dict keys.
Returns:
time (float or tuple): The realtime difference or the same
@ -163,7 +163,7 @@ def custom_gametime(absolute=False):
week, day, hour, minute, second).
"""
current = gametime(absolute=absolute)
current = gametime.gametime(absolute=absolute)
units = sorted(set(UNITS.values()), reverse=True)
del units[-1]
return time_to_tuple(current, *units)
@ -186,7 +186,7 @@ def real_seconds_until(**kwargs):
The number of real seconds before the given game time is up.
"""
current = gametime(absolute=True)
current = gametime.gametime(absolute=True)
units = sorted(set(UNITS.values()), reverse=True)
# Remove seconds from the tuple
del units[-1]
@ -232,25 +232,26 @@ def schedule(callback, repeat=False, **kwargs):
"""
Call the callback when the game time is up.
This function will setup a script that will be called when the
time corresponds to the game time. If the game is stopped for
more than a few seconds, the callback may be called with a slight
delay. If `repeat` is set to True, the callback will be called
again next time the game time matches the given time. The time
is given in units as keyword arguments. For instance:
>>> schedule(func, min=5, sec=0) # Will call next hour at :05.
>>> schedule(func, hour=2, min=30, sec=0) # Will call the next day at 02:30.
Args:
callback (function): the callback function that will be called [1].
repeat (bool, optional): should the callback be called regularly?
times (str: int): the time to call the callback.
[1] The callback must be a top-level function, since the script will
be persistent.
callback (function): The callback function that will be called. This
must be a top-level function since the script will be persistent.
repeat (bool, optional): Should the callback be called regularly?
day, month, etc (str: int): The time units to call the callback; should
match the keys of TIME_UNITS.
Returns:
The created script (Script).
script (Script): The created script.
Examples:
schedule(func, min=5, sec=0) # Will call next hour at :05.
schedule(func, hour=2, min=30, sec=0) # Will call the next day at 02:30.
Notes:
This function will setup a script that will be called when the
time corresponds to the game time. If the game is stopped for
more than a few seconds, the callback may be called with a
slight delay. If `repeat` is set to True, the callback will be
called again next time the game time matches the given time.
The time is given in units as keyword arguments.
"""
seconds = real_seconds_until(**kwargs)

View file

@ -40,6 +40,7 @@ from evennia.commands.cmdset import CmdSet
from evennia.utils import create, logger, utils, ansi
from evennia.commands.default.muxcommand import MuxCommand
from evennia.commands.cmdhandler import CMD_LOGINSTART
from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate
# limit symbol import for API
__all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate",
@ -161,99 +162,77 @@ class CmdUnconnectedCreate(MuxCommand):
"""Do checks and create account"""
session = self.caller
try:
playername, email, password = self.playerinfo
except ValueError:
string = "\n\r Usage (without <>): create \"<playername>\" <email> <password>"
session.msg(string)
return
if not re.findall('^[\w. @+-]+$', playername) or not (0 < len(playername) <= 30):
session.msg("\n\r Playername can be max 30 characters, or less. Letters, spaces,"
" digits and @/./+/-/_ only.") # this echoes the restrictions made by django's auth module.
return
if not email or not password:
session.msg("\n\r You have to supply an e-mail address followed by a password.")
return
if not utils.validate_email_address(email):
# check so the email at least looks ok.
session.msg("'%s' is not a valid e-mail address." % email)
return
# Run sanity and security checks
if PlayerDB.objects.filter(username=playername):
# player already exists
# sanity checks
if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30):
# this echoes the restrictions made by django's auth
# module (except not allowing spaces, for convenience of
# logging in).
string = "\n\r Playername can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
session.msg(string)
return
# strip excessive spaces in playername
playername = re.sub(r"\s+", " ", playername).strip()
if PlayerDB.objects.filter(username__iexact=playername):
# player already exists (we also ignore capitalization here)
session.msg("Sorry, there is already a player with the name '%s'." % playername)
return
if PlayerDB.objects.get_player_from_email(email):
# email already set on a player
session.msg("Sorry, there is already a player with that email address.")
return
if len(password) < 3:
# too short password
string = "Your password must be at least 3 characters or longer."
string += "\n\rFor best security, make it at least 8 characters long, "
string += "avoid making it a real word and mix numbers into it."
# Reserve playernames found in GUEST_LIST
if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST):
string = "\n\r That name is reserved. Please choose another Playername."
session.msg(string)
return
if not re.findall(r"^[\w. @+\-']+$", password) or not (3 < len(password)):
string = "\n\r Password should be longer than 3 characers. Letters, spaces, digits and @/./+/-/_/' only." \
"\nFor best security, make it longer than 8 characters. You can also use a phrase of" \
"\nmany words if you enclose the password in double quotes."
session.msg(string)
return
# Check IP and/or name bans
bans = ServerConfig.objects.conf("server_bans")
if bans and (any(tup[0] == playername.lower() for tup in bans)
or
any(tup[2].match(session.address) for tup in bans if tup[2])):
# this is a banned IP or name!
string = "|rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.|x"
session.msg(string)
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return
# everything's ok. Create the new player account.
try:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT
try:
new_player = create.create_player(playername, email, password, permissions=permissions)
except Exception as e:
session.msg("There was an error creating the default Player/Character:\n%s\n"
" If this problem persists, contact an admin." % e)
logger.log_trace()
return
# 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
# hooks during login later)
new_player.db.FIRST_LOGIN = True
# join the new player to the public channel
pchanneldef = settings.CHANNEL_PUBLIC
if pchanneldef:
pchannel = ChannelDB.objects.get_channel(pchanneldef[0])
if not pchannel.connect(new_player):
string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_err(string)
if MULTISESSION_MODE < 2:
# if we only allow one character, create one with the same name as Player
# (in mode 2, the character must be created manually once logging in)
new_character = create.create_object(typeclass, key=playername,
location=default_home, home=default_home,
permissions=permissions)
# set playable character list
new_player.db._playable_characters.append(new_character)
# allow only the character itself and the player to puppet this character (and Admin).
new_character.locks.add("puppet:id(%i) or pid(%i) or perm(Developer) or pperm(Developer)" %
(new_character.id, new_player.id))
# If no description is set, set a default description
if not new_character.db.desc:
new_character.db.desc = "This is a Player."
# We need to set this to have @ic auto-connect to this character
new_player.db._last_puppet = new_character
# tell the caller everything went well.
string = "A new account '%s' was created. Welcome!"
if " " in playername:
string += "\n\nYou can now log in with the command 'connect %s <your password>'."
else:
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, email))
typeclass = settings.BASE_CHARACTER_TYPECLASS
new_player = default_unloggedin._create_player(session, playername, password, permissions, email=email)
if new_player:
if MULTISESSION_MODE < 2:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
default_unloggedin._create_character(session, new_player, typeclass, default_home, permissions)
# tell the caller everything went well.
string = "A new account '%s' was created. Welcome!"
if " " in playername:
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
else:
string += "\n\nYou can now log with the command 'connect %s <your password>'."
session.msg(string % (playername, email))
except Exception:
# We are in the middle between logged in and -not, so we have
@ -261,6 +240,7 @@ class CmdUnconnectedCreate(MuxCommand):
# we won't see any errors at all.
session.msg("An error occurred. Please e-mail an admin if the problem persists.")
logger.log_trace()
raise
class CmdUnconnectedQuit(MuxCommand):
@ -276,8 +256,7 @@ class CmdUnconnectedQuit(MuxCommand):
def func(self):
"""Simply close the connection."""
session = self.caller
session.msg("Good bye! Disconnecting ...")
session.session_disconnect()
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
class CmdUnconnectedLook(MuxCommand):

View file

@ -68,6 +68,7 @@ Installation/testing:
"""
from __future__ import division
import datetime
import re
from django.conf import settings
from evennia import DefaultRoom
@ -129,12 +130,13 @@ class ExtendedRoom(DefaultRoom):
"""
Calculate the current time and season ids.
"""
# get the current time as parts of year and parts of day
# returns a tuple (years,months,weeks,days,hours,minutes,sec)
time = gametime.gametime(format=True)
month, hour = time[1], time[4]
season = float(month) / MONTHS_PER_YEAR
timeslot = float(hour) / HOURS_PER_DAY
# get the current time as parts of year and parts of day.
# we assume a standard calendar here and use 24h format.
timestamp = gametime.gametime(absolute=True)
# note that fromtimestamp includes the effects of server time zone!
datestamp = datetime.datetime.fromtimestamp(timestamp)
season = float(datestamp.month) / MONTHS_PER_YEAR
timeslot = float(datestamp.hour) / HOURS_PER_DAY
# figure out which slots these represent
if SEASONAL_BOUNDARIES[0] <= season < SEASONAL_BOUNDARIES[1]:

View file

@ -12,18 +12,18 @@ When in use, all messages being sent to the character will make use of
the character's gender, for example the echo
```
char.msg("%s falls on {p face with a thud." % char.key)
char.msg("%s falls on |p face with a thud." % char.key)
```
will result in "Tom falls on his|her|its face with a thud" depending
on the gender of the object being messaged. Default gender is
"neutral".
will result in "Tom falls on his|her|its|their face with a thud"
depending on the gender of the object being messaged. Default gender
is "ambiguous" (they).
To use, have DefaultCharacter inherit from this, or change
setting.DEFAULT_CHARACTER to point to this class.
The `@gender` command needs to be added to the default cmdset
before it becomes available.
The `@gender` command needs to be added to the default cmdset before
it becomes available.
"""
@ -50,7 +50,7 @@ _GENDER_PRONOUN_MAP = {"male": {"s": "he",
"p": "their",
"a": "theirs"}
}
_RE_GENDER_PRONOUN = re.compile(r'({s|{S|{o|{O|{p|{P|{a|{A)')
_RE_GENDER_PRONOUN = re.compile(r'(?<!\|)\|(?!\|)[sSoOpPaA]')
# in-game command for setting the gender
@ -59,7 +59,7 @@ class SetGender(Command):
Sets gender on yourself
Usage:
@gender male|female|neutral|ambiguous
@gender male||female||neutral||ambiguous
"""
key = "@gender"
@ -73,10 +73,10 @@ class SetGender(Command):
caller = self.caller
arg = self.args.strip().lower()
if not arg in ("male", "female", "neutral", "ambiguous"):
caller.msg("Usage: @gender male|female|neutral|ambiguous")
caller.msg("Usage: @gender male||female||neutral||ambiguous")
return
caller.db.gender = arg
caller.msg("Your gender was set to %s." % arg)
caller.msg("Your gender was set to %s." % arg)
# Gender-aware character class
@ -103,10 +103,10 @@ class GenderCharacter(DefaultCharacter):
regex_match (MatchObject): the regular expression match.
Notes:
- `{s`, `{S`: Subjective form: he, she, it, He, She, It, They
- `{o`, `{O`: Objective form: him, her, it, Him, Her, It, Them
- `{p`, `{P`: Possessive form: his, her, its, His, Her, Its, Their
- `{a`, `{A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
- `|s`, `|S`: Subjective form: he, she, it, He, She, It, They
- `|o`, `|O`: Objective form: him, her, it, Him, Her, It, Them
- `|p`, `|P`: Possessive form: his, her, its, His, Her, Its, Their
- `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
"""
typ = regex_match.group()[1] # "s", "O" etc
@ -134,5 +134,8 @@ class GenderCharacter(DefaultCharacter):
"""
# pre-process the text before continuing
text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text)
try:
text = _RE_GENDER_PRONOUN.sub(self._get_pronoun, text)
except TypeError:
pass
super(GenderCharacter, self).msg(text, from_obj=from_obj, session=session, **kwargs)

View file

@ -6,11 +6,15 @@ Evennia Contribution - grungies1138 2016
A simple Brandymail style @mail system that uses the Msg class from Evennia Core.
Installation:
import MailCommand from this module into the default Player or Character command set
import CmdMail from this module (from evennia.contrib.mail import CmdMail),
and add into the default Player or Character command set (self.add(CmdMail)).
"""
import re
from evennia import ObjectDB, PlayerDB
from evennia import default_cmds
from evennia.utils import create, evtable
from evennia.utils import create, evtable, make_iter
from evennia.comms.models import Msg
@ -48,7 +52,7 @@ class CmdMail(default_cmds.MuxCommand):
@mail 2
@mail Griatch=New mail/Hey man, I am sending you a message!
@mail/delete 6
@mail/forward feend78 Griatch=You guys should read this.
@mail/forward feend78 Griatch=4/You guys should read this.
@mail/reply 9=Thanks for the info!
"""
key = "@mail"
@ -56,9 +60,67 @@ class CmdMail(default_cmds.MuxCommand):
lock = "cmd:all()"
help_category = "General"
def search_targets(self, namelist):
"""
Search a list of targets of the same type as caller.
Args:
caller (Object or Player): The type of object to search.
namelist (list): List of strings for objects to search for.
Returns:
targetlist (list): List of matches, if any.
"""
nameregex = r"|".join(r"^%s$" % re.escape(name) for name in make_iter(namelist))
if hasattr(self.caller, "player") and self.caller.player:
matches = list(ObjectDB.objects.filter(db_key__iregex=nameregex))
else:
matches = list(PlayerDB.objects.filter(username__iregex=nameregex))
return matches
def get_all_mail(self):
"""
Returns a list of all the messages where the caller is a recipient.
Returns:
messages (list): list of Msg objects.
"""
# mail_messages = Msg.objects.get_by_tag(category="mail")
# messages = []
try:
player = self.caller.player
except AttributeError:
player = self.caller
messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=player)
return messages
def send_mail(self, recipients, subject, message, caller):
"""
Function for sending new mail. Also useful for sending notifications from objects or systems.
Args:
recipients (list): list of Player or character objects to receive the newly created mails.
subject (str): The header or subject of the message to be delivered.
message (str): The body of the message being sent.
caller (obj): The object (or Player or Character) that is sending the message.
"""
for recipient in recipients:
recipient.msg("You have received a new @mail from %s" % caller)
new_message = create.create_message(self.caller, message, receivers=recipient, header=subject)
new_message.tags.add("U", category="mail")
if recipients:
caller.msg("You sent your message.")
return
else:
caller.msg("No valid players found. Cannot send message.")
return
def func(self):
subject = ""
body = ""
if self.switches or self.args:
if "delete" in self.switches:
try:
@ -66,12 +128,15 @@ class CmdMail(default_cmds.MuxCommand):
self.caller.msg("No Message ID given. Unable to delete.")
return
else:
if self.get_all_mail()[int(self.lhs) - 1]:
self.get_all_mail()[int(self.lhs) - 1].delete()
all_mail = self.get_all_mail()
mind = int(self.lhs) - 1
if all_mail[mind]:
all_mail[mind].delete()
self.caller.msg("Message %s deleted" % self.lhs)
else:
self.caller.msg("That message does not exist.")
return
raise IndexError
except IndexError:
self.caller.msg("That message does not exist.")
except ValueError:
self.caller.msg("Usage: @mail/delete <message ID>")
elif "forward" in self.switches:
@ -83,29 +148,33 @@ class CmdMail(default_cmds.MuxCommand):
self.caller.msg("You must define a message to forward.")
return
else:
all_mail = self.get_all_mail()
if "/" in self.rhs:
message_number, message = self.rhs.split("/")
if self.get_all_mail()[int(message_number) - 1]:
old_message = self.get_all_mail()[int(message_number) - 1]
mind = int(message_number) - 1
self.send_mail(self.lhslist, "FWD: " + old_message.header,
if all_mail[mind]:
old_message = all_mail[mind]
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header,
message + "\n---- Original Message ----\n" + old_message.message,
self.caller)
self.caller.msg("Message forwarded.")
else:
self.caller.msg("Message does not exist.")
return
raise IndexError
else:
if self.get_all_mail()[int(self.rhs) - 1]:
old_message = self.get_all_mail()[int(self.rhs) - 1]
self.send_mail(self.lhslist, "FWD: " + old_message.header,
mind = int(self.rhs) - 1
if all_mail[mind]:
old_message = all_mail[mind]
self.send_mail(self.search_targets(self.lhslist), "FWD: " + old_message.header,
"\n---- Original Message ----\n" + old_message.message, self.caller)
self.caller.msg("Message forwarded.")
old_message.tags.remove("u", category="mail")
old_message.tags.add("f", category="mail")
else:
self.caller.msg("Message does not exist.")
return
raise IndexError
except IndexError:
self.caller.msg("Message does not exixt.")
except ValueError:
self.caller.msg("Usage: @mail/forward <player list>=<#>[/<Message>]")
elif "reply" in self.switches:
@ -117,29 +186,33 @@ class CmdMail(default_cmds.MuxCommand):
self.caller.msg("You must supply a reply message")
return
else:
if self.get_all_mail()[int(self.lhs) - 1]:
old_message = self.get_all_mail()[int(self.lhs) - 1]
all_mail = self.get_all_mail()
mind = int(self.lhs) - 1
if all_mail[mind]:
old_message = all_mail[mind]
self.send_mail(old_message.senders, "RE: " + old_message.header,
self.rhs + "\n---- Original Message ----\n" + old_message.message, self.caller)
old_message.tags.remove("u", category="mail")
old_message.tags.add("r", category="mail")
return
else:
self.caller.msg("Message does not exist.")
return
raise IndexError
except IndexError:
self.caller.msg("Message does not exist.")
except ValueError:
self.caller.msg("Usage: @mail/reply <#>=<message>")
else:
# normal send
if self.rhs:
if "/" in self.rhs:
subject, body = self.rhs.split("/", 1)
else:
body = self.rhs
self.send_mail(self.lhslist, subject, body, self.caller)
self.send_mail(self.search_targets(self.lhslist), subject, body, self.caller)
else:
try:
message = self.get_all_mail()[int(self.lhs) - 1]
except ValueError:
except (ValueError, IndexError):
self.caller.msg("'%s' is not a valid mail id." % self.lhs)
return
@ -176,47 +249,8 @@ class CmdMail(default_cmds.MuxCommand):
table.reformat_column(4, width=7)
self.caller.msg(_HEAD_CHAR * _WIDTH)
self.caller.msg(table)
self.caller.msg(unicode(table))
self.caller.msg(_HEAD_CHAR * _WIDTH)
else:
self.caller.msg("Sorry, you don't have any messages. What a pathetic loser!")
self.caller.msg("There are no messages in your inbox.")
def get_all_mail(self):
"""
Returns a list of all the messages where the caller is a recipient.
Returns:
messages (list): list of Msg objects.
"""
# mail_messages = Msg.objects.get_by_tag(category="mail")
# messages = []
messages = Msg.objects.get_by_tag(category="mail", raw_queryset=True).filter(db_receivers_players=self.caller.player)
return messages
def send_mail(self, recipients, subject, message, caller):
"""
Function for sending new mail. Also useful for sending notifications from objects or systems.
Args:
recipients (list): list of Player or character objects to receive the newly created mails.
subject (str): The header or subject of the message to be delivered.
message (str): The body of the message being sent.
caller (obj): The object (or Player or Character) that is sending the message.
"""
recobjs = []
for char in recipients:
if self.caller.player.search(char) is not None:
recobjs.append(self.caller.player.search(char))
if recobjs:
for recipient in recobjs:
recipient.msg("You have received a new @mail from %s" % caller)
new_message = create.create_message(self.caller, message, receivers=recipient, header=subject)
new_message.tags.add("U", category="mail")
caller.msg("You sent your message.")
return
else:
caller.msg("No valid players found. Cannot send message.")
return

View file

@ -98,6 +98,7 @@ from typeclasses import rooms, exits
from random import randint
import random
# A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an
# island surrounded by water (≈). By giving no instructions for the water
# characters we effectively skip it and create no rooms for those squares.
@ -287,6 +288,88 @@ def _map_to_list(game_map):
else character for character in list_map]
def build_map(caller, game_map, legend, iterations=1, build_exits=True):
"""
Receives the fetched map and legend vars provided by the player.
Args:
caller (Object): The creator of the map.
game_map (str): An ASCII map string.
legend (dict): Mapping of map symbols to object types.
iterations (int): The number of iteration passes.
build_exits (bool): Create exits between new rooms.
Notes:
The map
is iterated over character by character, comparing it to the trigger
characters in the legend var and executing the build instructions on
finding a match. The map is iterated over according to the `iterations`
value and exits are optionally generated between adjacent rooms according
to the `build_exits` value.
"""
# Split map string to list of rows and create reference list.
caller.msg("Creating Map...")
caller.msg(game_map)
game_map = _map_to_list(game_map)
# Create a reference dictionary which be passed to build functions and
# will store obj returned by build functions so objs can be referenced.
room_dict = {}
caller.msg("Creating Landmass...")
for iteration in xrange(iterations):
for y in xrange(len(game_map)):
for x in xrange(len(game_map[y])):
for key in legend:
# obs - we must use == for unicode
if utils.to_unicode(game_map[y][x]) == utils.to_unicode(key):
room = legend[key](x, y, iteration=iteration,
room_dict=room_dict,
caller=caller)
if iteration == 0:
room_dict[(x, y)] = room
if build_exits:
# Creating exits. Assumes single room object in dict entry
caller.msg("Connecting Areas...")
for loc_key, location in room_dict.iteritems():
x = loc_key[0]
y = loc_key[1]
# north
if (x, y-1) in room_dict:
if room_dict[(x, y-1)]:
create_object(exits.Exit, key="north",
aliases=["n"], location=location,
destination=room_dict[(x, y-1)])
# east
if (x+1, y) in room_dict:
if room_dict[(x+1, y)]:
create_object(exits.Exit, key="east",
aliases=["e"], location=location,
destination=room_dict[(x+1, y)])
# south
if (x, y+1) in room_dict:
if room_dict[(x, y+1)]:
create_object(exits.Exit, key="south",
aliases=["s"], location=location,
destination=room_dict[(x, y+1)])
# west
if (x-1, y) in room_dict:
if room_dict[(x-1, y)]:
create_object(exits.Exit, key="west",
aliases=["w"], location=location,
destination=room_dict[(x-1, y)])
caller.msg("Map Created.")
# access command
class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
"""
Build a map from a 2D ASCII map.
@ -396,72 +479,3 @@ class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
# Pass map and legend to the build function.
build_map(caller, game_map, legend, iterations, build_exits)
def build_map(caller, game_map, legend, iterations=1, build_exits=True):
"""
Receives the fetched map and legend vars provided by the player. The map
is iterated over character by character, comparing it to the trigger
characters in the legend var and executing the build instructions on
finding a match. The map is iterated over according to the `iterations`
value and exits are optionally generated between adjacent rooms according
to the `build_exits` value.
"""
# Split map string to list of rows and create reference list.
caller.msg("Creating Map...")
caller.msg(game_map)
game_map = _map_to_list(game_map)
# Create a reference dictionary which be passed to build functions and
# will store obj returned by build functions so objs can be referenced.
room_dict = {}
caller.msg("Creating Landmass...")
for iteration in xrange(iterations):
for y in xrange(len(game_map)):
for x in xrange(len(game_map[y])):
for key in legend:
if game_map[y][x] in key:
room = legend[key](x, y, iteration=iteration,
room_dict=room_dict,
caller=caller)
if iteration == 0:
room_dict[(x, y)] = room
if build_exits:
# Creating exits. Assumes single room object in dict entry
caller.msg("Connecting Areas...")
for loc_key, location in room_dict.iteritems():
x = loc_key[0]
y = loc_key[1]
# north
if (x, y-1) in room_dict:
if room_dict[(x, y-1)]:
create_object(exits.Exit, key="north",
aliases=["n"], location=location,
destination=room_dict[(x, y-1)])
# east
if (x+1, y) in room_dict:
if room_dict[(x+1, y)]:
create_object(exits.Exit, key="east",
aliases=["e"], location=location,
destination=room_dict[(x+1, y)])
# south
if (x, y+1) in room_dict:
if room_dict[(x, y+1)]:
create_object(exits.Exit, key="south",
aliases=["s"], location=location,
destination=room_dict[(x, y+1)])
# west
if (x-1, y) in room_dict:
if room_dict[(x-1, y)]:
create_object(exits.Exit, key="west",
aliases=["w"], location=location,
destination=room_dict[(x-1, y)])
caller.msg("Map Created.")

View file

@ -5,7 +5,11 @@ Contribution - Griatch 2016
A simple two-way exit that represents a door that can be opened and
closed. Can easily be expanded from to make it lockable, destroyable
etc.
etc. Note that the simpledoor is based on Evennia locks, so it will
not work for a superuser (which bypasses all locks) - the superuser
will always appear to be able to close/open the door over and over
without the locks stopping you. To use the door, use `@quell` or a
non-superuser account.
Installation:

View file

@ -135,9 +135,10 @@ class CmdStop(Command):
stored deferred from the exit traversal above.
"""
currently_moving = self.caller.ndb.currently_moving
if currently_moving:
if currently_moving and not currently_moving.called:
currently_moving.cancel()
self.caller.msg("You stop moving.")
self.caller.location.msg_contents("%s stops." % self.get_display_name())
for observer in self.caller.location.contents_get(self.caller):
observer.msg("%s stops." % self.caller.get_display_name(observer))
else:
self.caller.msg("You are not moving.")

View file

@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
"""
Testing suite for contrib folder
"""
from django.conf import settings
import datetime
from evennia.commands.default.tests import CommandTest
from evennia.utils.test_resources import EvenniaTest
from mock import Mock
from mock import Mock, patch
# Testing of rplanguage module
@ -168,44 +169,52 @@ class TestRPSystem(EvenniaTest):
# Testing of ExtendedRoom contrib
from django.conf import settings
from evennia.contrib import extended_room
from evennia import gametime
from evennia.objects.objects import DefaultRoom
# mock gametime to return 7th month, 10 in morning
gametime.gametime = Mock(return_value=(None, 7, None, None, 10))
# mock settings so we're not affected by a given server's hours of day/months in year
settings.TIME_MONTH_PER_YEAR = 12
settings.TIME_HOUR_PER_DAY = 24
class ForceUTCDatetime(datetime.datetime):
"""Force UTC datetime."""
@classmethod
def fromtimestamp(cls, timestamp):
"""Force fromtimestamp to run with naive datetimes."""
return datetime.datetime.utcfromtimestamp(timestamp)
@patch('evennia.contrib.extended_room.datetime.datetime', ForceUTCDatetime)
class TestExtendedRoom(CommandTest):
room_typeclass = extended_room.ExtendedRoom
DETAIL_DESC = "A test detail."
SUMMER_DESC = "A summer description."
SPRING_DESC = "A spring description."
OLD_DESC = "Old description."
settings.TIME_ZONE = "UTC"
def setUp(self):
super(TestExtendedRoom, self).setUp()
self.room1.ndb.last_timeslot = "night"
self.room1.ndb.last_timeslot = "afternoon"
self.room1.ndb.last_season = "winter"
self.room1.db.details = {'testdetail': self.DETAIL_DESC}
self.room1.db.summer_desc = self.SUMMER_DESC
self.room1.db.spring_desc = self.SPRING_DESC
self.room1.db.desc = self.OLD_DESC
# mock gametime to return April 9, 2064, at 21:06 (spring evening)
gametime.gametime = Mock(return_value=2975000766)
def test_return_appearance(self):
# get the appearance of a non-extended room for contrast purposes
old_desc = DefaultRoom.return_appearance(self.room1, self.char1)
# the new appearance should be the old one, but with the desc switched
self.assertEqual(old_desc.replace(self.OLD_DESC, self.SUMMER_DESC), self.room1.return_appearance(self.char1))
self.assertEqual("summer", self.room1.ndb.last_season)
self.assertEqual("morning", self.room1.ndb.last_timeslot)
self.assertEqual(old_desc.replace(self.OLD_DESC, self.SPRING_DESC),
self.room1.return_appearance(self.char1))
self.assertEqual("spring", self.room1.ndb.last_season)
self.assertEqual("evening", self.room1.ndb.last_timeslot)
def test_return_detail(self):
self.assertEqual(self.DETAIL_DESC, self.room1.return_detail("testdetail"))
def test_cmdextendedlook(self):
self.call(extended_room.CmdExtendedLook(), "here", "Room(#1)\n%s" % self.SUMMER_DESC)
self.call(extended_room.CmdExtendedLook(), "here", "Room(#1)\n%s" % self.SPRING_DESC)
self.call(extended_room.CmdExtendedLook(), "testdetail", self.DETAIL_DESC)
self.call(extended_room.CmdExtendedLook(), "nonexistent", "Could not find 'nonexistent'.")
@ -219,7 +228,7 @@ class TestExtendedRoom(CommandTest):
self.call(extended_room.CmdExtendedDesc(), "", "Descriptions on Room:")
def test_cmdgametime(self):
self.call(extended_room.CmdGameTime(), "", "It's a summer day, in the morning.")
self.call(extended_room.CmdGameTime(), "", "It's a spring day, in the evening.")
# Test the contrib barter system
@ -306,11 +315,11 @@ class TestBarter(CommandTest):
self.call(barter.CmdTradeHelp(), "", "Trading commands\n", caller=self.char1)
self.call(barter.CmdFinish(), ": Ending.", "You say, \"Ending.\"\n [You aborted trade. No deal was made.]")
# Test wilderness
from evennia.contrib import wilderness
from evennia import DefaultCharacter
class TestWilderness(EvenniaTest):
def setUp(self):
@ -426,3 +435,323 @@ class TestWilderness(EvenniaTest):
for direction, correct_loc in directions.iteritems(): # Not compatible with Python 3
new_loc = wilderness.get_new_coordinates(loc, direction)
self.assertEquals(new_loc, correct_loc, direction)
# Testing chargen contrib
from evennia.contrib import chargen
class TestChargen(CommandTest):
def test_ooclook(self):
self.call(chargen.CmdOOCLook(), "foo", "You have no characters to look at", caller=self.player)
self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.", caller=self.player)
def test_charcreate(self):
self.call(chargen.CmdOOCCharacterCreate(), "testchar", "The character testchar was successfully created!", caller=self.player)
self.call(chargen.CmdOOCCharacterCreate(), "testchar", "Character testchar already exists.", caller=self.player)
self.assertTrue(self.player.db._character_dbrefs)
self.call(chargen.CmdOOCLook(), "", "You, TestPlayer, are an OOC ghost without form.",caller=self.player)
self.call(chargen.CmdOOCLook(), "testchar", "testchar(", caller=self.player)
# Testing custom_gametime
from evennia.contrib import custom_gametime
def _testcallback():
pass
class TestCustomGameTime(EvenniaTest):
def setUp(self):
super(TestCustomGameTime, self).setUp()
gametime.gametime = Mock(return_value=2975000898.46) # does not seem to work
def tearDown(self):
if hasattr(self, "timescript"):
self.timescript.stop()
def test_time_to_tuple(self):
self.assertEqual(custom_gametime.time_to_tuple(10000, 34,2,4,6,1), (294, 2, 0, 0, 0, 0))
self.assertEqual(custom_gametime.time_to_tuple(10000, 3,3,4), (3333, 0, 0, 1))
self.assertEqual(custom_gametime.time_to_tuple(100000, 239,24,3), (418, 4, 0, 2))
def test_gametime_to_realtime(self):
self.assertEqual(custom_gametime.gametime_to_realtime(days=2, mins=4), 86520.0)
self.assertEqual(custom_gametime.gametime_to_realtime(format=True, days=2), (0,0,0,1,0,0,0))
def test_realtime_to_gametime(self):
self.assertEqual(custom_gametime.realtime_to_gametime(days=2, mins=34), 349680.0)
self.assertEqual(custom_gametime.realtime_to_gametime(days=2, mins=34, format=True), (0, 0, 0, 4, 1, 8, 0))
self.assertEqual(custom_gametime.realtime_to_gametime(format=True, days=2, mins=4), (0, 0, 0, 4, 0, 8, 0))
def test_custom_gametime(self):
self.assertEqual(custom_gametime.custom_gametime(), (102, 5, 2, 6, 21, 8, 18))
self.assertEqual(custom_gametime.custom_gametime(absolute=True), (102, 5, 2, 6, 21, 8, 18))
def test_real_seconds_until(self):
self.assertEqual(custom_gametime.real_seconds_until(year=2300, month=11, day=6), 31911667199.77)
def test_schedule(self):
self.timescript = custom_gametime.schedule(_testcallback, repeat=True, min=5, sec=0)
self.assertEqual(self.timescript.interval, 1700.7699999809265)
# Test dice module
@patch('random.randint', return_value=5)
class TestDice(CommandTest):
def test_roll_dice(self, mocked_randint):
# we must import dice here for the mocked randint to apply correctly.
from evennia.contrib import dice
self.assertEqual(dice.roll_dice(6, 6, modifier=('+', 4)), mocked_randint()*6 + 4)
self.assertEqual(dice.roll_dice(6, 6, conditional=('<', 35)), True)
self.assertEqual(dice.roll_dice(6, 6, conditional=('>', 33)), False)
def test_cmddice(self, mocked_randint):
from evennia.contrib import dice
self.call(dice.CmdDice(), "3d6 + 4", "You roll 3d6 + 4.| Roll(s): 5, 5 and 5. Total result is 19.")
self.call(dice.CmdDice(), "100000d1000", "The maximum roll allowed is 10000d10000.")
self.call(dice.CmdDice(), "/secret 3d6 + 4", "You roll 3d6 + 4 (secret, not echoed).")
# Test email-login
from evennia.contrib import email_login
class TestEmailLogin(CommandTest):
def test_connect(self):
self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test", "The email 'mytest@test.com' does not match any accounts.")
self.call(email_login.CmdUnconnectedCreate(), '"mytest" mytest@test.com test11111', "A new account 'mytest' was created. Welcome!")
self.call(email_login.CmdUnconnectedConnect(), "mytest@test.com test11111", "", caller=self.player.sessions.get()[0])
def test_quit(self):
self.call(email_login.CmdUnconnectedQuit(), "", "", caller=self.player.sessions.get()[0])
def test_unconnectedlook(self):
self.call(email_login.CmdUnconnectedLook(), "", "==========")
def test_unconnectedhelp(self):
self.call(email_login.CmdUnconnectedHelp(), "", "You are not yet logged into the game.")
# test gendersub contrib
from evennia.contrib import gendersub
class TestGenderSub(CommandTest):
def test_setgender(self):
self.call(gendersub.SetGender(), "male", "Your gender was set to male.")
self.call(gendersub.SetGender(), "ambiguous", "Your gender was set to ambiguous.")
self.call(gendersub.SetGender(), "Foo", "Usage: @gender")
def test_gendercharacter(self):
char = create_object(gendersub.GenderCharacter, key="Gendered", location=self.room1)
txt = "Test |p gender"
self.assertEqual(gendersub._RE_GENDER_PRONOUN.sub(char._get_pronoun, txt), "Test their gender")
# test mail contrib
from evennia.contrib import mail
class TestMail(CommandTest):
def test_mail(self):
self.call(mail.CmdMail(), "2", "'2' is not a valid mail id.", caller=self.player)
self.call(mail.CmdMail(), "", "There are no messages in your inbox.", caller=self.player)
self.call(mail.CmdMail(), "Char=Message 1", "You have received a new @mail from Char|You sent your message.", caller=self.char1)
self.call(mail.CmdMail(), "Char=Message 2", "You sent your message.", caller=self.char2)
self.call(mail.CmdMail(), "TestPlayer2=Message 2",
"You have received a new @mail from TestPlayer2(player 2)|You sent your message.", caller=self.player2)
self.call(mail.CmdMail(), "TestPlayer=Message 1", "You sent your message.", caller=self.player2)
self.call(mail.CmdMail(), "TestPlayer=Message 2", "You sent your message.", caller=self.player2)
self.call(mail.CmdMail(), "", "| ID: From: Subject:", caller=self.player)
self.call(mail.CmdMail(), "2", "From: TestPlayer2", caller=self.player)
self.call(mail.CmdMail(), "/forward TestPlayer2 = 1/Forward message", "You sent your message.|Message forwarded.", caller=self.player)
self.call(mail.CmdMail(), "/reply 2=Reply Message2", "You sent your message.", caller=self.player)
self.call(mail.CmdMail(), "/delete 2", "Message 2 deleted", caller=self.player)
# test map builder contrib
from evennia.contrib import mapbuilder
class TestMapBuilder(CommandTest):
def test_cmdmapbuilder(self):
self.call(mapbuilder.CmdMapBuilder(),
"evennia.contrib.mapbuilder.EXAMPLE1_MAP evennia.contrib.mapbuilder.EXAMPLE1_LEGEND",
"""Creating Map...|≈≈≈≈≈
n
n
|Creating Landmass...|""")
self.call(mapbuilder.CmdMapBuilder(),
"evennia.contrib.mapbuilder.EXAMPLE2_MAP evennia.contrib.mapbuilder.EXAMPLE2_LEGEND",
"""Creating Map...|≈ ≈ ≈ ≈ ≈
|Creating Landmass...|""")
# test menu_login
from evennia.contrib import menu_login
class TestMenuLogin(CommandTest):
def test_cmdunloggedlook(self):
self.call(menu_login.CmdUnloggedinLook(), "", "======")
# test multidescer contrib
from evennia.contrib import multidescer
class TestMultidescer(CommandTest):
def test_cmdmultidesc(self):
self.call(multidescer.CmdMultiDesc(),"/list", "Stored descs:\ncaller:")
self.call(multidescer.CmdMultiDesc(),"test = Desc 1", "Stored description 'test': \"Desc 1\"")
self.call(multidescer.CmdMultiDesc(),"test2 = Desc 2", "Stored description 'test2': \"Desc 2\"")
self.call(multidescer.CmdMultiDesc(),"/swap test-test2", "Swapped descs 'test' and 'test2'.")
self.call(multidescer.CmdMultiDesc(),"test3 = Desc 3init", "Stored description 'test3': \"Desc 3init\"")
self.call(multidescer.CmdMultiDesc(),"/list", "Stored descs:\ntest3: Desc 3init\ntest: Desc 1\ntest2: Desc 2\ncaller:")
self.call(multidescer.CmdMultiDesc(),"test3 = Desc 3", "Stored description 'test3': \"Desc 3\"")
self.call(multidescer.CmdMultiDesc(),"/set test1 + test2 + + test3", "test1 Desc 2 Desc 3\n\n"
"The above was set as the current description.")
self.assertEqual(self.char1.db.desc, "test1 Desc 2 Desc 3")
# test simpledoor contrib
from evennia.contrib import simpledoor
class TestSimpleDoor(CommandTest):
def test_cmdopen(self):
self.call(simpledoor.CmdOpen(), "newdoor;door:contrib.simpledoor.SimpleDoor,backdoor;door = Room2",
"Created new Exit 'newdoor' from Room to Room2 (aliases: door).|Note: A doortype exit was "
"created ignored eventual custom returnexit type.|Created new Exit 'newdoor' from Room2 to Room (aliases: door).")
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "You close newdoor.", cmdstring="close")
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "newdoor is already closed.", cmdstring="close")
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "You open newdoor.", cmdstring="open")
self.call(simpledoor.CmdOpenCloseDoor(), "newdoor", "newdoor is already open.", cmdstring="open")
# test slow_exit contrib
from evennia.contrib import slow_exit
slow_exit.MOVE_DELAY = {"stroll":0, "walk": 0, "run": 0, "sprint": 0}
class TestSlowExit(CommandTest):
def test_exit(self):
exi = create_object(slow_exit.SlowExit, key="slowexit", location=self.room1, destination=self.room2)
exi.at_traverse(self.char1, self.room2)
self.call(slow_exit.CmdSetSpeed(), "walk", "You are now walking.")
self.call(slow_exit.CmdStop(), "", "You stop moving.")
# test talking npc contrib
from evennia.contrib import talking_npc
class TestTalkingNPC(CommandTest):
def test_talkingnpc(self):
npc = create_object(talking_npc.TalkingNPC, key="npctalker", location=self.room1)
self.call(talking_npc.CmdTalk(), "","(You walk up and talk to Char.)|")
npc.delete()
# tests for the tutorial world
# test tutorial_world/mob
from evennia.contrib.tutorial_world import mob
class TestTutorialWorldMob(EvenniaTest):
def test_mob(self):
mobobj = create_object(mob.Mob, key="mob")
self.assertEqual(mobobj.db.is_dead, True)
mobobj.set_alive()
self.assertEqual(mobobj.db.is_dead, False)
mobobj.set_dead()
self.assertEqual(mobobj.db.is_dead, True)
mobobj._set_ticker(0, "foo", stop=True)
#TODO should be expanded with further tests of the modes and damage etc.
# test tutorial_world/objects
from evennia.contrib.tutorial_world import objects as tutobjects
class TestTutorialWorldObjects(CommandTest):
def test_tutorialobj(self):
obj1 = create_object(tutobjects.TutorialObject, key="tutobj")
obj1.reset()
self.assertEqual(obj1.location, obj1.home)
def test_readable(self):
readable = create_object(tutobjects.Readable, key="book", location=self.room1)
readable.db.readable_text = "Text to read"
self.call(tutobjects.CmdRead(), "book","You read book:\n Text to read", obj=readable)
def test_climbable(self):
climbable = create_object(tutobjects.Climbable, key="tree", location=self.room1)
self.call(tutobjects.CmdClimb(), "tree", "You climb tree. Having looked around, you climb down again.", obj=climbable)
self.assertEqual(self.char1.tags.get("tutorial_climbed_tree", category="tutorial_world"), "tutorial_climbed_tree")
def test_obelisk(self):
obelisk = create_object(tutobjects.Obelisk, key="obelisk", location=self.room1)
self.assertEqual(obelisk.return_appearance(self.char1).startswith("|cobelisk("), True)
def test_lightsource(self):
light = create_object(tutobjects.LightSource, key="torch", location=self.room1)
self.call(tutobjects.CmdLight(), "", "You light torch.", obj=light)
light._burnout()
if hasattr(light, "deferred"):
light.deferred.cancel()
self.assertFalse(light.pk)
def test_crumblingwall(self):
wall = create_object(tutobjects.CrumblingWall, key="wall", location=self.room1)
self.assertFalse(wall.db.button_exposed)
self.assertFalse(wall.db.exit_open)
wall.db.root_pos = {"yellow":0, "green":0,"red":0,"blue":0}
self.call(tutobjects.CmdShiftRoot(), "blue root right",
"You shove the root adorned with small blue flowers to the right.", obj=wall)
self.call(tutobjects.CmdShiftRoot(), "red root left",
"You shift the reddish root to the left.", obj=wall)
self.call(tutobjects.CmdShiftRoot(), "yellow root down",
"You shove the root adorned with small yellow flowers downwards.", obj=wall)
self.call(tutobjects.CmdShiftRoot(), "green root up",
"You shift the weedy green root upwards.|Holding aside the root you think you notice something behind it ...", obj=wall)
self.call(tutobjects.CmdPressButton(), "",
"You move your fingers over the suspicious depression, then gives it a decisive push. First", obj=wall)
self.assertTrue(wall.db.button_exposed)
self.assertTrue(wall.db.exit_open)
wall.reset()
if hasattr(wall, "deferred"):
wall.deferred.cancel()
wall.delete()
def test_weapon(self):
weapon = create_object(tutobjects.Weapon, key="sword", location=self.char1)
self.call(tutobjects.CmdAttack(), "Char", "You stab with sword.", obj=weapon, cmdstring="stab")
self.call(tutobjects.CmdAttack(), "Char", "You slash with sword.", obj=weapon, cmdstring="slash")
def test_weaponrack(self):
rack = create_object(tutobjects.WeaponRack, key="rack", location=self.room1)
rack.db.available_weapons = ["sword"]
self.call(tutobjects.CmdGetWeapon(), "", "You find Rusty sword.", obj=rack)
# test tutorial_world/
from evennia.contrib.tutorial_world import rooms as tutrooms
class TestTutorialWorldRooms(CommandTest):
def test_cmdtutorial(self):
room = create_object(tutrooms.TutorialRoom, key="tutroom")
self.char1.location = room
self.call(tutrooms.CmdTutorial(), "", "Sorry, there is no tutorial help available here.")
self.call(tutrooms.CmdTutorialSetDetail(), "detail;foo;foo2 = A detail", "Detail set: 'detail;foo;foo2': 'A detail'", obj=room)
self.call(tutrooms.CmdTutorialLook(), "", "tutroom(", obj=room)
self.call(tutrooms.CmdTutorialLook(), "detail", "A detail", obj=room)
self.call(tutrooms.CmdTutorialLook(), "foo", "A detail", obj=room)
room.delete()
def test_weatherroom(self):
room = create_object(tutrooms.WeatherRoom, key="weatherroom")
room.update_weather()
tutrooms.TICKER_HANDLER.remove(interval=room.db.interval, callback=room.update_weather, idstring="tutorial")
room.delete()
def test_introroom(self):
room = create_object(tutrooms.IntroRoom, key="introroom")
room.at_object_receive(self.char1, self.room1)
def test_bridgeroom(self):
room = create_object(tutrooms.BridgeRoom, key="bridgeroom")
room.update_weather()
self.char1.move_to(room)
self.call(tutrooms.CmdBridgeHelp(), "", "You are trying hard not to fall off the bridge ...", obj=room)
self.call(tutrooms.CmdLookBridge(), "", "bridgeroom\nYou are standing very close to the the bridge's western foundation.", obj=room)
room.at_object_leave(self.char1, self.room1)
tutrooms.TICKER_HANDLER.remove(interval=room.db.interval, callback=room.update_weather, idstring="tutorial")
room.delete()
def test_darkroom(self):
room = create_object(tutrooms.DarkRoom, key="darkroom")
self.char1.move_to(room)
self.call(tutrooms.CmdDarkHelp(), "", "Can't help you until")
def test_teleportroom(self):
create_object(tutrooms.TeleportRoom, key="teleportroom")
def test_outroroom(self):
create_object(tutrooms.OutroRoom, key="outroroom")

View file

@ -3,7 +3,7 @@ Example script for testing. This adds a simple timer that has your
character make observations and notices at irregular intervals.
To test, use
@script me = examples.bodyfunctions.BodyFunctions
@script me = tutorial_examples.bodyfunctions.BodyFunctions
The script will only send messages to the object it is stored on, so
make sure to put it on yourself or you won't see any messages!

View file

@ -367,8 +367,9 @@ class LightSource(TutorialObject):
pass
finally:
# start the burn timer. When it runs out, self._burnout
# will be called.
utils.delay(60 * 3, self._burnout)
# will be called. We store the deferred so it can be
# killed in unittesting.
self.deferred = utils.delay(60 * 3, self._burnout)
return True
@ -636,9 +637,10 @@ class CrumblingWall(TutorialObject, DefaultExit):
self.caller.msg("The exit leads nowhere, there's just more stone behind it ...")
else:
self.destination = eloc[0]
self.exit_open = True
# start a 45 second timer before closing again
utils.delay(45, self.reset)
self.db.exit_open = True
# start a 45 second timer before closing again. We store the deferred so it can be
# killed in unittesting.
self.deferred = utils.delay(45, self.reset)
def _translate_position(self, root, ipos):
"""Translates the position into words"""

View file

@ -279,7 +279,7 @@ class TutorialRoom(DefaultRoom):
# These are rainy weather strings
WEATHER_STRINGS = (
"The rain coming down from the iron-grey sky intensifies.",
"A gush of wind throws the rain right in your face. Despite your cloak you shiver.",
"A gust of wind throws the rain right in your face. Despite your cloak you shiver.",
"The rainfall eases a bit and the sky momentarily brightens.",
"For a moment it looks like the rain is slowing, then it begins anew with renewed force.",
"The rain pummels you with large, heavy drops. You hear the rumble of thunder in the distance.",
@ -313,8 +313,8 @@ class WeatherRoom(TutorialRoom):
# subscribe ourselves to a ticker to repeatedly call the hook
# "update_weather" on this object. The interval is randomized
# so as to not have all weather rooms update at the same time.
interval = random.randint(50, 70)
TICKER_HANDLER.add(interval=interval, callback=self.update_weather, idstring="tutorial")
self.db.interval = random.randint(50, 70)
TICKER_HANDLER.add(interval=self.db.interval, callback=self.update_weather, idstring="tutorial")
# this is parsed by the 'tutorial' command on TutorialRooms.
self.db.tutorial_info = \
"This room has a Script running that has it echo a weather-related message at irregular intervals."
@ -590,7 +590,7 @@ class BridgeCmdSet(CmdSet):
BRIDGE_WEATHER = (
"The rain intensifies, making the planks of the bridge even more slippery.",
"A gush of wind throws the rain right in your face.",
"A gust of wind throws the rain right in your face.",
"The rainfall eases a bit and the sky momentarily brightens.",
"The bridge shakes under the thunder of a closeby thunder strike.",
"The rain pummels you with large, heavy drops. You hear the distinct howl of a large hound in the distance.",

View file

@ -216,20 +216,20 @@ msgstr ""
#: server/initial_setup.py:29
msgid ""
"\n"
"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if "
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
"you need\n"
"help, want to contribute, report issues or just join the community.\n"
"As Player #1 you can create a demo/tutorial area with {w@batchcommand "
"tutorial_world.build{n.\n"
"As Player #1 you can create a demo/tutorial area with |w@batchcommand "
"tutorial_world.build|n.\n"
" "
msgstr ""
"\n"
"Bienvenue dans ton nouveau jeu basé sur {wEvennia{n ! Visitez http://www."
"Bienvenue dans ton nouveau jeu basé sur |wEvennia|n ! Visitez http://www."
"evennia.com si vous avez besoin\n"
"d'aide, si vous voulez contribuer, rapporter des problèmes ou faire partie "
"de la communauté.\n"
"En tant que Joueur #1 vous pouvez créer une zone de démo/tutoriel avec "
"{w@batchcommand tutorial_world.build{n.\n"
"|w@batchcommand tutorial_world.build|n.\n"
" "
#: server/initial_setup.py:102

View file

@ -221,16 +221,16 @@ msgstr "Aggiorna l'handler del canale."
#: .\server\initial_setup.py:29
msgid ""
"\n"
"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if you need\n"
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need\n"
"help, want to contribute, report issues or just join the community.\n"
"As Player #1 you can create a demo/tutorial area with {w@batchcommand tutorial_world.build{n.\n"
"As Player #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n.\n"
" "
msgstr ""
"\n"
"Benvenuto al tuo nuovo gioco creato con {wEvennia{n! Visita http://www.evennia.com se ti\n"
"Benvenuto al tuo nuovo gioco creato con |wEvennia|n! Visita http://www.evennia.com se ti\n"
"serve aiuto, se vuoi collaborare, segnalare errori o se desideri unirti alla comunità online.\n"
"In qualità di Giocatore #1 puoi creare un'area dimostrativa/tutorial digitando il comando:\n"
"{w@batchcommand tutorial_world.build{n.\n"
"|w@batchcommand tutorial_world.build|n.\n"
" "
#: .\server\initial_setup.py:99

View file

@ -208,19 +208,19 @@ msgstr ""
#: server/initial_setup.py:30
msgid ""
"\n"
"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if "
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
"you need\n"
"help, want to contribute, report issues or just join the community.\n"
"As Player #1 you can create a demo/tutorial area with {w@batchcommand "
"tutorial_world.build{n.\n"
"As Player #1 you can create a demo/tutorial area with |w@batchcommand "
"tutorial_world.build|n.\n"
" "
msgstr ""
"\n"
"Bem-vindo a seu novo jogo criado com {wEvennia{n! Visite http://www.evennia.com\n"
"Bem-vindo a seu novo jogo criado com |wEvennia|n! Visite http://www.evennia.com\n"
"se você precisar de ajuda, desejar contribuir, reportar bugs ou apenas\n"
"juntar-se à comunidade.\n"
"Como Player #1 você pode criar uma área demonstrativa/tutorial digitando\n"
"o comando {w@batchcommand tutorial_world.build{n.\n"
"o comando |w@batchcommand tutorial_world.build|n.\n"
" "
#: server/initial_setup.py:103

View file

@ -214,11 +214,11 @@ msgstr "Detta är en generisk lagringskontainer."
#: server/initial_setup.py:29
msgid ""
"\n"
"Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if "
"Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if "
"you need\n"
"help, want to contribute, report issues or just join the community.\n"
"As Player #1 you can create a demo/tutorial area with {w@batchcommand "
"tutorial_world.build{n.\n"
"As Player #1 you can create a demo/tutorial area with |w@batchcommand "
"tutorial_world.build|n.\n"
" "
msgstr ""

View file

@ -40,16 +40,21 @@ class ObjectCreateForm(forms.ModelForm):
fields = '__all__'
db_key = forms.CharField(label="Name/Key",
widget=forms.TextInput(attrs={'size': '78'}),
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",)
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. "
"If creating a Character, check so the name is unique among characters!",)
db_typeclass_path = forms.CharField(label="Typeclass",
initial=settings.BASE_OBJECT_TYPECLASS,
widget=forms.TextInput(attrs={'size': '78'}),
help_text="This defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass. If you are creating a Character you should use the typeclass defined by settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
help_text="This defines what 'type' of entity this is. This variable holds a "
"Python path to a module with a valid Evennia Typeclass. If you are "
"creating a Character you should use the typeclass defined by "
"settings.BASE_CHARACTER_TYPECLASS or one derived from that.")
db_cmdset_storage = forms.CharField(label="CmdSet",
initial="",
required=False,
widget=forms.TextInput(attrs={'size': '78'}),
help_text="Most non-character objects don't need a cmdset and can leave this field blank.")
help_text="Most non-character objects don't need a cmdset"
" and can leave this field blank.")
raw_id_fields = ('db_destination', 'db_location', 'db_home')
@ -63,8 +68,10 @@ class ObjectEditForm(ObjectCreateForm):
fields = '__all__'
db_lock_storage = forms.CharField(label="Locks",
required=False,
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),
help_text="In-game lock definition string. If not given, defaults will be used. This string should be on the form <i>type:lockfunction(args);type2:lockfunction2(args);...")
widget=forms.Textarea(attrs={'cols': '100', 'rows': '2'}),
help_text="In-game lock definition string. If not given, defaults will be used. "
"This string should be on the form "
"<i>type:lockfunction(args);type2:lockfunction2(args);...")
class ObjectDBAdmin(admin.ModelAdmin):
@ -90,15 +97,15 @@ class ObjectDBAdmin(admin.ModelAdmin):
form = ObjectEditForm
fieldsets = (
(None, {
'fields': (('db_key','db_typeclass_path'), ('db_lock_storage', ),
('db_location', 'db_home'), 'db_destination','db_cmdset_storage'
'fields': (('db_key', 'db_typeclass_path'), ('db_lock_storage', ),
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
)}),
)
add_form = ObjectCreateForm
add_fieldsets = (
(None, {
'fields': (('db_key','db_typeclass_path'),
'fields': (('db_key', 'db_typeclass_path'),
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
)}),
)

View file

@ -21,6 +21,7 @@ _MULTIMATCH_REGEX = re.compile(settings.SEARCH_MULTIMATCH_REGEX, re.I + re.U)
# Try to use a custom way to parse id-tagged multimatches.
class ObjectDBManager(TypedObjectManager):
"""
This ObjectManager implements methods for searching
@ -79,11 +80,13 @@ class ObjectDBManager(TypedObjectManager):
if dbref:
return dbref
# not a dbref. Search by name.
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
if exact:
return self.filter(cand_restriction & Q(db_player__username__iexact=ostring))
else: # fuzzy matching
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)).values_list("db_key", flat=True)
else: # fuzzy matching
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)
).values_list("db_key", flat=True)
if candidates:
index_matches = string_partial_matching(ply_cands, ostring, ret_index=True)
return [obj for ind, obj in enumerate(make_iter(candidates)) if ind in index_matches]
@ -103,7 +106,8 @@ class ObjectDBManager(TypedObjectManager):
Returns:
matches (list): The matching objects.
"""
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
return self.filter(cand_restriction & Q(db_key__iexact=oname, db_typeclass_path__exact=otypeclass_path))
# attr/property related
@ -121,7 +125,9 @@ class ObjectDBManager(TypedObjectManager):
matches (list): All objects having the given attribute_name defined at all.
"""
cand_restriction = candidates != None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
cand_restriction = candidates is not None and Q(db_attributes__db_obj__pk__in=[_GA(obj, "id") for obj
in make_iter(candidates)
if obj]) or Q()
return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)))
@returns_typeclass_list
@ -144,20 +150,23 @@ class ObjectDBManager(TypedObjectManager):
cannot be indexed, searching by Attribute key is to be preferred whenever possible.
"""
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
## This doesn't work if attribute_value is an object. Workaround below
# This doesn't work if attribute_value is an object. Workaround below
if isinstance(attribute_value, (basestring, int, float, bool)):
return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name, db_attributes__db_value=attribute_value))
return self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name,
db_attributes__db_value=attribute_value))
else:
# We have to loop for safety since the referenced lookup gives deepcopy error if attribute value is an object.
# We must loop for safety since the referenced lookup gives deepcopy error if attribute value is an object.
global _ATTR
if not _ATTR:
from evennia.typeclasses.models import Attribute as _ATTR
cands = list(self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name)))
results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands, db_value=attribute_value)]
results = [attr.objectdb_set.all() for attr in _ATTR.objects.filter(objectdb__in=cands,
db_value=attribute_value)]
return chain(*results)
@returns_typeclass_list
@ -174,8 +183,9 @@ class ObjectDBManager(TypedObjectManager):
"""
property_name = "db_%s" % property_name.lstrip('db_')
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
querykwargs = {property_name:None}
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
querykwargs = {property_name: None}
try:
return list(self.filter(cand_restriction).exclude(Q(**querykwargs)))
except exceptions.FieldError:
@ -198,8 +208,9 @@ class ObjectDBManager(TypedObjectManager):
if isinstance(property_name, basestring):
if not property_name.startswith('db_'):
property_name = "db_%s" % property_name
querykwargs = {property_name:property_value}
cand_restriction = candidates != None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates) if obj]) or Q()
querykwargs = {property_name: property_value}
cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
if obj]) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
try:
return list(self.filter(cand_restriction & type_restriction & Q(**querykwargs)))
@ -207,7 +218,8 @@ class ObjectDBManager(TypedObjectManager):
return []
except ValueError:
from evennia.utils import logger
logger.log_err("The property '%s' does not support search criteria of the type %s." % (property_name, type(property_value)))
logger.log_err("The property '%s' does not support search criteria of the type %s." %
(property_name, type(property_value)))
return []
@returns_typeclass_list
@ -228,7 +240,7 @@ class ObjectDBManager(TypedObjectManager):
@returns_typeclass_list
def get_objs_with_key_or_alias(self, ostring, exact=True,
candidates=None, typeclasses=None):
candidates=None, typeclasses=None):
"""
Args:
ostring (str): A search criterion.
@ -253,7 +265,7 @@ class ObjectDBManager(TypedObjectManager):
# build query objects
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj]
cand_restriction = candidates != None and Q(pk__in=candidates_id) or Q()
cand_restriction = candidates is not None and Q(pk__in=candidates_id) or Q()
type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
if exact:
# exact match - do direct search
@ -264,7 +276,8 @@ class ObjectDBManager(TypedObjectManager):
search_candidates = self.filter(cand_restriction & type_restriction)
else:
# fuzzy without supplied candidates - we select our own candidates
search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) | Q(db_tags__db_key__istartswith=ostring))).distinct()
search_candidates = self.filter(type_restriction & (Q(db_key__istartswith=ostring) |
Q(db_tags__db_key__istartswith=ostring))).distinct()
# fuzzy matching
key_strings = search_candidates.values_list("db_key", flat=True).order_by("id")
@ -275,10 +288,10 @@ class ObjectDBManager(TypedObjectManager):
else:
# match by alias rather than by key
search_candidates = search_candidates.filter(db_tags__db_tagtype__iexact="alias",
db_tags__db_key__icontains=ostring)
db_tags__db_key__icontains=ostring)
alias_strings = []
alias_candidates = []
#TODO create the alias_strings and alias_candidates lists more effiently?
# TODO create the alias_strings and alias_candidates lists more efficiently?
for candidate in search_candidates:
for alias in candidate.aliases.all():
alias_strings.append(alias)
@ -343,13 +356,16 @@ class ObjectDBManager(TypedObjectManager):
"""
if attribute_name:
# attribute/property search (always exact).
matches = self.get_objs_with_db_property_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
matches = self.get_objs_with_db_property_value(attribute_name, searchdata,
candidates=candidates, typeclasses=typeclass)
if matches:
return matches
return self.get_objs_with_attr_value(attribute_name, searchdata, candidates=candidates, typeclasses=typeclass)
return self.get_objs_with_attr_value(attribute_name, searchdata,
candidates=candidates, typeclasses=typeclass)
else:
# normal key/alias search
return self.get_objs_with_key_or_alias(searchdata, exact=exact, candidates=candidates, typeclasses=typeclass)
return self.get_objs_with_key_or_alias(searchdata, exact=exact,
candidates=candidates, typeclasses=typeclass)
if not searchdata and searchdata != 0:
return []
@ -372,7 +388,7 @@ class ObjectDBManager(TypedObjectManager):
candidates = [cand for cand in make_iter(candidates) if cand]
if typeclass:
candidates = [cand for cand in candidates
if _GA(cand, "db_typeclass_path") in typeclass]
if _GA(cand, "db_typeclass_path") in typeclass]
dbref = not attribute_name and exact and use_dbref and self.dbref(searchdata)
if dbref:
@ -418,7 +434,6 @@ class ObjectDBManager(TypedObjectManager):
#
# ObjectManager Copy method
#
def copy_object(self, original_object, new_key=None,
new_location=None, new_home=None,

View file

@ -51,8 +51,7 @@ class ContentsHandler(object):
Re-initialize the content cache
"""
self._pkcache.update(dict((obj.pk, None) for obj in
ObjectDB.objects.filter(db_location=self.obj) if obj.pk))
self._pkcache.update(dict((obj.pk, None) for obj in ObjectDB.objects.filter(db_location=self.obj) if obj.pk))
def get(self, exclude=None):
"""
@ -79,7 +78,7 @@ class ContentsHandler(object):
return [self._idcache[pk] for pk in pks]
except KeyError:
# this means an actual failure of caching. Return real database match.
logger.log_err("contents cache failed for %s." % (self.obj.key))
logger.log_err("contents cache failed for %s." % self.obj.key)
return list(ObjectDB.objects.filter(db_location=self.obj))
def add(self, obj):
@ -110,11 +109,12 @@ class ContentsHandler(object):
self._pkcache = {}
self.init()
#------------------------------------------------------------
# -------------------------------------------------------------
#
# ObjectDB
#
#------------------------------------------------------------
# -------------------------------------------------------------
class ObjectDB(TypedObject):
"""
@ -173,17 +173,18 @@ class ObjectDB(TypedObject):
help_text='a Player connected to this object, if any.')
# the session id associated with this player, if any
db_sessid = models.CommaSeparatedIntegerField(null=True, max_length=32, verbose_name="session id",
help_text="csv list of session ids of connected Player, if any.")
help_text="csv list of session ids of connected Player, if any.")
# The location in the game world. Since this one is likely
# to change often, we set this with the 'location' property
# to transparently handle Typeclassing.
db_location = models.ForeignKey('self', related_name="locations_set", db_index=True, on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='game location')
blank=True, null=True, verbose_name='game location')
# a safety location, this usually don't change much.
db_home = models.ForeignKey('self', related_name="homes_set", on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='home location')
blank=True, null=True, verbose_name='home location')
# destination of this object - primarily used by exits.
db_destination = models.ForeignKey('self', related_name="destinations_set", db_index=True, on_delete=models.SET_NULL,
db_destination = models.ForeignKey('self', related_name="destinations_set",
db_index=True, on_delete=models.SET_NULL,
blank=True, null=True, verbose_name='destination',
help_text='a destination, used only by exit objects.')
# database storage of persistant cmdsets.
@ -204,28 +205,28 @@ class ObjectDB(TypedObject):
# cmdset_storage property handling
def __cmdset_storage_get(self):
"getter"
"""getter"""
storage = self.db_cmdset_storage
return [path.strip() for path in storage.split(',')] if storage else []
def __cmdset_storage_set(self, value):
"setter"
self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value))
"""setter"""
self.db_cmdset_storage = ",".join(str(val).strip() for val in make_iter(value))
self.save(update_fields=["db_cmdset_storage"])
def __cmdset_storage_del(self):
"deleter"
"""deleter"""
self.db_cmdset_storage = None
self.save(update_fields=["db_cmdset_storage"])
cmdset_storage = property(__cmdset_storage_get, __cmdset_storage_set, __cmdset_storage_del)
# location getsetter
def __location_get(self):
"Get location"
"""Get location"""
return self.db_location
def __location_set(self, location):
"Set location, checking for loops and allowing dbref"
"""Set location, checking for loops and allowing dbref"""
if isinstance(location, (basestring, int)):
# allow setting of #dbref
dbid = dbref(location, reqhash=False)
@ -237,9 +238,9 @@ class ObjectDB(TypedObject):
pass
try:
def is_loc_loop(loc, depth=0):
"Recursively traverse target location, trying to catch a loop."
"""Recursively traverse target location, trying to catch a loop."""
if depth > 10:
return
return None
elif loc == self:
raise RuntimeError
elif loc is None:
@ -248,7 +249,7 @@ class ObjectDB(TypedObject):
try:
is_loc_loop(location)
except RuntimeWarning:
# we caught a infitite location loop!
# we caught an infinite location loop!
# (location1 is in location2 which is in location1 ...)
pass
@ -281,7 +282,7 @@ class ObjectDB(TypedObject):
return
def __location_del(self):
"Cleanly delete the location reference"
"""Cleanly delete the location reference"""
self.db_location = None
self.save(update_fields=["db_location"])
location = property(__location_get, __location_set, __location_del)
@ -311,7 +312,6 @@ class ObjectDB(TypedObject):
[o.contents_cache.init() for o in self.__dbclass__.get_all_cached_instances()]
class Meta(object):
"Define Django meta options"
"""Define Django meta options"""
verbose_name = "Object"
verbose_name_plural = "Objects"

View file

@ -21,7 +21,7 @@ from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.commands import cmdhandler
from evennia.utils import logger
from evennia.utils.utils import (variable_from_module, lazy_property,
make_iter, to_unicode, calledby)
make_iter, to_unicode, calledby, is_iter)
_MULTISESSION_MODE = settings.MULTISESSION_MODE
@ -34,6 +34,7 @@ _SESSID_MAX = 16 if _MULTISESSION_MODE in (1, 3) else 1
from django.utils.translation import ugettext as _
class ObjectSessionHandler(object):
"""
Handles the get/setting of the sessid
@ -133,7 +134,7 @@ class ObjectSessionHandler(object):
Remove session from handler.
Args:
sessid (Session or int): Session or session id to remove.
session (Session or int): Session or session id to remove.
"""
try:
@ -144,7 +145,7 @@ class ObjectSessionHandler(object):
sessid_cache = self._sessid_cache
if sessid in sessid_cache:
sessid_cache.remove(sessid)
self.obj.db_sessid = ",".join(str(val) for val in sessid_cache)
self.obj.db_sessid = ",".join(str(val) for val in sessid_cache)
self.obj.save(update_fields=["db_sessid"])
def clear(self):
@ -167,10 +168,9 @@ class ObjectSessionHandler(object):
return len(self._sessid_cache)
#
# Base class to inherit from.
#
class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
@ -221,7 +221,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
return self.db_player and self.db_player.is_superuser \
and not self.db_player.attributes.get("_quell")
and not self.db_player.attributes.get("_quell")
def contents_get(self, exclude=None):
"""
@ -241,7 +241,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
con = self.contents_cache.get(exclude=exclude)
#print "contents_get:", self, con, id(self), calledby()
# print "contents_get:", self, con, id(self), calledby() # DEBUG
return con
contents = property(contents_get)
@ -370,8 +370,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# do nick-replacement on search
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
if(global_search or (is_string and searchdata.startswith("#") and
len(searchdata) > 1 and searchdata[1:].isdigit())):
if (global_search or (is_string and searchdata.startswith("#") and
len(searchdata) > 1 and searchdata[1:].isdigit())):
# only allow exact matching if searching the entire database
# or unique #dbrefs
exact = True
@ -403,8 +403,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
use_dbref=use_dbref)
if quiet:
return results
return _AT_SEARCH_RESULT(results, self, query=searchdata,
nofound_string=nofound_string, multimatch_string=multimatch_string)
return _AT_SEARCH_RESULT(results, self, query=searchdata,
nofound_string=nofound_string, multimatch_string=multimatch_string)
def search_player(self, searchdata, quiet=False):
"""
@ -474,11 +474,9 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# nick replacement - we require full-word matching.
# do text encoding conversion
raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=True)
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=True)
return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs)
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
"""
Emits something to a session attached to the object.
@ -549,21 +547,28 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
for obj in contents:
func(obj, **kwargs)
def msg_contents(self, message, exclude=None, from_obj=None, mapping=None, **kwargs):
def msg_contents(self, text=None, exclude=None, from_obj=None, mapping=None, **kwargs):
"""
Emits a message to all objects inside this object.
Args:
message (str): Message to send.
text (str or tuple): Message to send. If a tuple, this should be
on the valid OOB outmessage form `(message, {kwargs})`,
where kwargs are optional data passed to the `text`
outputfunc.
exclude (list, optional): A list of objects not to send to.
from_obj (Object, optional): An object designated as the
"sender" of the message. See `DefaultObject.msg()` for
more info.
mapping (dict, optional): A mapping of formatting keys
`{"key":<object>, "key2":<object2>,...}. The keys
must match `{key}` markers in `message` and will be
must match `{key}` markers in the `text` if this is a string or
in the internal `message` if `text` is a tuple. These
formatting statements will be
replaced by the return of `<object>.get_display_name(looker)`
for every looker that is messaged.
for every looker in contents that receives the
message. This allows for every object to potentially
get its own customized string.
Kwargs:
Keyword arguments will be passed on to `obj.msg()` for all
messaged objects.
@ -577,14 +582,23 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
not have `get_display_name()`, its string value will be used.
Example:
Say char is a Character object and npc is an NPC object:
Say Char is a Character object and Npc is an NPC object:
action = 'kicks'
char.location.msg_contents(
"{attacker} {action} {defender}",
mapping=dict(attacker=char, defender=npc, action=action),
exclude=(char, npc))
"{attacker} kicks {defender}",
mapping=dict(attacker=char, defender=npc), exclude=(char, npc))
This will result in everyone in the room seeing 'Char kicks NPC'
where everyone may potentially see different results for Char and Npc
depending on the results of `char.get_display_name(looker)` and
`npc.get_display_name(looker)` for each particular onlooker
"""
# we also accept an outcommand on the form (message, {kwargs})
is_outcmd = text and is_iter(text)
inmessage = text[0] if is_outcmd else text
outkwargs = text[1] if is_outcmd and len(text) > 1 else {}
contents = self.contents
if exclude:
exclude = make_iter(exclude)
@ -592,12 +606,12 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
for obj in contents:
if mapping:
substitutions = {t: sub.get_display_name(obj)
if hasattr(sub, 'get_display_name')
else str(sub)
for t, sub in mapping.items()}
obj.msg(message.format(**substitutions), from_obj=from_obj, **kwargs)
if hasattr(sub, 'get_display_name')
else str(sub) for t, sub in mapping.items()}
outmessage = inmessage.format(**substitutions)
else:
obj.msg(message, from_obj=from_obj, **kwargs)
outmessage = inmessage
obj.msg(text=(outmessage, outkwargs), from_obj=from_obj, **kwargs)
def move_to(self, destination, quiet=False,
emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True):
@ -642,7 +656,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
def logerr(string="", err=None):
"Simple log helper method"
"""Simple log helper method"""
logger.log_trace()
self.msg("%s%s" % (string, "" if err is None else " (%s)" % err))
return
@ -658,7 +672,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.location = None
return True
emit_to_obj.msg(_("The destination doesn't exist."))
return
return False
if destination.destination and use_destination:
# traverse exits
destination = destination.destination
@ -667,7 +681,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
if move_hooks:
try:
if not self.at_before_move(destination):
return
return False
except Exception as err:
logerr(errtxt % "at_before_move()", err)
return False
@ -684,7 +698,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return False
if not quiet:
#tell the old room we are leaving
# tell the old room we are leaving
try:
self.announce_move_from(destination)
except Exception as err:
@ -704,7 +718,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.announce_move_to(source_location)
except Exception as err:
logerr(errtxt % "announce_move_to()", err)
return False
return False
if move_hooks:
# Perform eventual extra commands on the receiving location
@ -779,7 +793,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
obj.msg(_(string))
obj.move_to(home)
def copy(self, new_key=None):
"""
Makes an identical copy of this object, identical except for a
@ -803,15 +816,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
key = self.key
num = 1
for obj in (obj for obj in self.location.contents
if obj.key.startswith(key) and
obj.key.lstrip(key).isdigit()):
for _ in (obj for obj in self.location.contents
if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
num += 1
return "%s%03i" % (key, num)
new_key = new_key or find_clone_key()
return ObjectDB.objects.copy_object(self, new_key=new_key)
delete_iter = 0
def delete(self):
"""
Deletes this object. Before deletion, this method makes sure
@ -862,7 +875,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.attributes.clear()
self.nicks.clear()
self.aliases.clear()
self.location = None # this updates contents_cache for our location
self.location = None # this updates contents_cache for our location
# Perform the deletion of the object
super(DefaultObject, self).delete()
@ -955,8 +968,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.basetype_posthook_setup()
## hooks called by the game engine
# hooks called by the game engine #
def basetype_setup(self):
"""
@ -1032,8 +1044,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
have no cmdsets.
Kwargs:
Usually not set but could be used e.g. to force rebuilding
of a dynamically created cmdset or similar.
caller (Session, Object or Player): The caller requesting
this cmdset.
"""
pass
@ -1118,9 +1130,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
result (bool): The outcome of the access call.
accessing_obj (Object or Player): The entity trying to
gain access. access_type (str): The type of access that
was requested.
accessing_obj (Object or Player): The entity trying to gain access.
access_type (str): The type of access that was requested.
Kwargs:
Not used by default, added for possible expandability in a
@ -1147,10 +1158,10 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
before it is even started.
"""
#return has_perm(self, destination, "can_move")
# return has_perm(self, destination, "can_move")
return True
def announce_move_from(self, destination):
def announce_move_from(self, destination, msg=None, mapping=None):
"""
Called if the move is to be announced. This is
called while we are still standing in the old
@ -1158,25 +1169,56 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args:
destination (Object): The place we are going to.
msg (str, optional): a replacement message.
mapping (dict, optional): additional mapping objects.
You can override this method and call its parent with a
message to simply change the default message. In the string,
you can use the following as mappings (between braces):
object: the object which is moving.
exit: the exit from which the object is moving (if found).
origin: the location of the object before the move.
destination: the location of the object after moving.
"""
if not self.location:
return
string = "%s is leaving %s, heading for %s."
location = self.location
for obj in self.location.contents:
if obj != self:
obj.msg(string % (self.get_display_name(obj),
location.get_display_name(obj) if location else "nowhere",
destination.get_display_name(obj)))
if msg:
string = msg
else:
string = "{object} is leaving {origin}, heading for {destination}."
def announce_move_to(self, source_location):
location = self.location
exits = [o for o in location.contents if o.location is location and o.destination is destination]
if not mapping:
mapping = {}
mapping.update({
"object": self,
"exit": exits[0] if exits else "somwhere",
"origin": location or "nowhere",
"destination": destination or "nowhere",
})
location.msg_contents(string, exclude=(self, ), mapping=mapping)
def announce_move_to(self, source_location, msg=None, mapping=None):
"""
Called after the move if the move was not quiet. At this point
we are standing in the new location.
Args:
source_location (Object): The place we came from
msg (str, optional): the replacement message if location.
mapping (dict, optional): additional mapping objects.
You can override this method and call its parent with a
message to simply change the default message. In the string,
you can use the following as mappings (between braces):
object: the object which is moving.
exit: the exit from which the object is moving (if found).
origin: the location of the object before the move.
destination: the location of the object after moving.
"""
@ -1187,13 +1229,31 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.location.msg(string)
return
string = "%s arrives to %s%s."
location = self.location
for obj in self.location.contents:
if obj != self:
obj.msg(string % (self.get_display_name(obj),
location.get_display_name(obj) if location else "nowhere",
" from %s" % source_location.get_display_name(obj) if source_location else ""))
if source_location:
if msg:
string = msg
else:
string = "{object} arrives to {destination} from {origin}."
else:
string = "{object} arrives to {destination}."
origin = source_location
destination = self.location
exits = []
if origin:
exits = [o for o in destination.contents if o.location is destination and o.destination is origin]
if not mapping:
mapping = {}
mapping.update({
"object": self,
"exit": exits[0] if exits else "somewhere",
"origin": origin or "nowhere",
"destination": destination or "nowhere",
})
destination.msg_contents(string, exclude=(self, ), mapping=mapping)
def at_after_move(self, source_location):
"""
@ -1288,7 +1348,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
check for this. .
Consider this a pre-processing method before msg is passed on
to the user sesssion. If this method returns False, the msg
to the user session. If this method returns False, the msg
will not be passed on.
Args:
@ -1341,7 +1401,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return ""
# get and identify all objects
visible = (con for con in self.contents if con != looker and
con.access(looker, "view"))
con.access(looker, "view"))
exits, users, things = [], [], []
for con in visible:
key = con.get_display_name(looker)
@ -1416,6 +1476,22 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
"""
pass
def at_give(self, giver, getter):
"""
Called by the default `give` command when this object has been
given.
Args:
giver (Object): The object giving this object.
getter (Object): The object getting this object.
Notes:
This hook cannot stop the give from happening. Use
permissions for that.
"""
pass
def at_drop(self, dropper):
"""
Called by the default `drop` command when this object has been
@ -1425,7 +1501,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
dropper (Object): The object which just dropped this object.
Notes:
This hook cannot stop the pickup from happening. Use
This hook cannot stop the drop from happening. Use
permissions from that.
"""
@ -1473,7 +1549,7 @@ class DefaultCharacter(DefaultObject):
"""
super(DefaultCharacter, self).basetype_setup()
self.locks.add(";".join(["get:false()", # noone can pick up the character
"call:false()"])) # no commands can be called on character from outside
"call:false()"])) # no commands can be called on character from outside
# add the default cmdset
self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
@ -1565,7 +1641,7 @@ class DefaultCharacter(DefaultObject):
#
# Base Room object
#
class DefaultRoom(DefaultObject):
"""
@ -1581,7 +1657,7 @@ class DefaultRoom(DefaultObject):
super(DefaultRoom, self).basetype_setup()
self.locks.add(";".join(["get:false()",
"puppet:false()"])) # would be weird to puppet a room ...
"puppet:false()"])) # would be weird to puppet a room ...
self.location = None
@ -1631,7 +1707,7 @@ class ExitCommand(command.Command):
#
# Base Exit object
#
class DefaultExit(DefaultObject):
"""
@ -1683,8 +1759,8 @@ class DefaultExit(DefaultObject):
exit_cmdset.add(cmd)
return exit_cmdset
# Command hooks
def basetype_setup(self):
"""
Setup exit-security
@ -1696,8 +1772,8 @@ class DefaultExit(DefaultObject):
super(DefaultExit, self).basetype_setup()
# setting default locks (overload these in at_object_creation()
self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ...
"traverse:all()", # who can pass through exit by default
self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ...
"traverse:all()", # who can pass through exit by default
"get:false()"])) # noone can pick up the exit
# an exit should have a destination (this is replaced at creation time)

View file

@ -13,6 +13,9 @@ from evennia.utils import utils
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
_IRC_ENABLED = settings.IRC_ENABLED
_RSS_ENABLED = settings.RSS_ENABLED
_SESSIONS = None
@ -78,8 +81,10 @@ class BotStarter(DefaultScript):
"""
self.db.started = False
#
# Bot base class
class Bot(DefaultPlayer):
"""
A Bot will start itself when the server starts (it will generally
@ -157,6 +162,12 @@ class IRCBot(Bot):
irc_ssl (bool): Indicates whether to use SSL connection.
"""
if not _IRC_ENABLED:
# the bot was created, then IRC was turned off. We delete
# ourselves (this will also kill the start script)
self.delete()
return
global _SESSIONS
if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
@ -187,9 +198,9 @@ class IRCBot(Bot):
# instruct the server and portal to create a new session with
# the stored configuration
configdict = {"uid":self.dbid,
configdict = {"uid": self.dbid,
"botname": self.db.irc_botname,
"channel": self.db.irc_channel ,
"channel": self.db.irc_channel,
"network": self.db.irc_network,
"port": self.db.irc_port,
"ssl": self.db.irc_ssl}
@ -316,31 +327,32 @@ class IRCBot(Bot):
whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % player.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1)))
text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w:w.lower()))
text = "Who list (online/idle): %s" % ", ".join(sorted(whos, key=lambda w: w.lower()))
elif txt.lower().startswith("about"):
# some bot info
text = "This is an Evennia IRC bot connecting from '%s'." % settings.SERVERNAME
else:
text = "I understand 'who' and 'about'."
super(IRCBot, self).msg(privmsg=((text,), {"user":user}))
super(IRCBot, self).msg(privmsg=((text,), {"user": user}))
else:
# something to send to the main channel
if kwargs["type"] == "action":
# An action (irc pose)
text = "%s@%s %s" % (kwargs["user"], kwargs["channel"], txt)
else:
# msg - A normal channel message
text = "%s@%s: %s" % (kwargs["user"], kwargs["channel"], txt)
if not self.ndb.ev_channel and self.db.ev_channel:
# cache channel lookup
self.ndb.ev_channel = self.db.ev_channel
if self.ndb.ev_channel:
self.ndb.ev_channel.msg(text, senders=self.id)
if not self.ndb.ev_channel and self.db.ev_channel:
# cache channel lookup
self.ndb.ev_channel = self.db.ev_channel
if self.ndb.ev_channel:
self.ndb.ev_channel.msg(text, senders=self.id)
#
# RSS
class RSSBot(Bot):
"""
An RSS relayer. The RSS protocol itself runs a ticker to update
@ -354,12 +366,17 @@ class RSSBot(Bot):
Args:
ev_channel (str): Key of the Evennia channel to connect to.
rss_url (str): Full URL to the RSS feed to subscribe to.
rss_update_rate (int): How often for the feedreader to update.
rss_rate (int): How often for the feedreader to update.
Raises:
RuntimeError: If `ev_channel` does not exist.
"""
if not _RSS_ENABLED:
# The bot was created, then RSS was turned off. Delete ourselves.
self.delete()
return
global _SESSIONS
if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS
@ -390,7 +407,7 @@ class RSSBot(Bot):
Args:
session (Session, optional): Session responsible for this
command.
text (str, optional): Command string.
txt (str, optional): Command string.
kwargs (dict, optional): Additional Information passed from bot.
Not used by the RSSbot by default.

View file

@ -104,17 +104,6 @@ class PlayerDB(TypedObject, AbstractUser):
class Meta(object):
verbose_name = 'Player'
# alias to the objs property
def __characters_get(self):
return self.objs
def __characters_set(self, value):
self.objs = value
def __characters_del(self):
raise Exception("Cannot delete name")
characters = property(__characters_get, __characters_set, __characters_del)
# cmdset_storage property
# This seems very sensitive to caching, so leaving it be for now /Griatch
#@property

View file

@ -41,6 +41,7 @@ _MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
_CMDSET_PLAYER = settings.CMDSET_PLAYER
_CONNECT_CHANNEL = None
class PlayerSessionHandler(object):
"""
Manages the session(s) attached to a player.
@ -99,7 +100,6 @@ class PlayerSessionHandler(object):
return len(self.get())
class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
"""
This is the base Typeclass for all Players. Players represent
@ -188,7 +188,6 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
def sessions(self):
return PlayerSessionHandler(self)
# session-related methods
def disconnect_session_from_player(self, session):
@ -337,8 +336,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
by this Player.
"""
return list(set(session.puppet for session in self.sessions.all()
if session.puppet))
return list(set(session.puppet for session in self.sessions.all() if session.puppet))
def __get_single_puppet(self):
"""
@ -383,7 +381,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
self.nicks.clear()
self.aliases.clear()
super(DefaultPlayer, self).delete(*args, **kwargs)
## methods inherited from database model
# methods inherited from database model
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
"""
@ -447,8 +445,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
"""
raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string,
categories=("inputline", "channel"), include_player=False)
raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=False)
if not session and _MULTISESSION_MODE in (0, 1):
# for these modes we use the first/only session
sessions = self.sessions.get()
@ -557,7 +554,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
return time.time() - float(min(conn))
return None
## player hooks
# player hooks
def basetype_setup(self):
"""
@ -600,7 +597,6 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
"""
pass
# Note that the hooks below also exist in the character object's
# typeclass. You can often ignore these and rely on the character
# ones instead, unless you are implementing a multi-character game
@ -847,34 +843,36 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
is_su = self.is_superuser
# text shown when looking in the ooc area
string = "Account |g%s|n (you are Out-of-Character)" % (self.key)
result = ["Account |g%s|n (you are Out-of-Character)" % self.key]
nsess = len(sessions)
string += nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess
result.append(nsess == 1 and "\n\n|wConnected session:|n" or "\n\n|wConnected sessions (%i):|n" % nsess)
for isess, sess in enumerate(sessions):
csessid = sess.sessid
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address))
string += "\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) or " %s" % (isess + 1), addr)
string += "\n\n |whelp|n - more commands"
string += "\n |wooc <Text>|n - talk on public channel"
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple)
and str(sess.address[0]) or str(sess.address))
result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1)
or " %s" % (isess + 1), addr))
result.append("\n\n |whelp|n - more commands")
result.append("\n |wooc <Text>|n - talk on public channel")
charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1
if is_su or len(characters) < charmax:
if not characters:
string += "\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one."
result.append("\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one.")
else:
string += "\n |w@charcreate <name> [=description]|n - create new character"
string += "\n |w@chardelete <name>|n - delete a character (cannot be undone!)"
result.append("\n |w@charcreate <name> [=description]|n - create new character")
result.append("\n |w@chardelete <name>|n - delete a character (cannot be undone!)")
if characters:
string_s_ending = len(characters) > 1 and "s" or ""
string += "\n |w@ic <character>|n - enter the game (|w@ooc|n to get back here)"
result.append("\n |w@ic <character>|n - enter the game (|w@ooc|n to get back here)")
if is_su:
string += "\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters))
result.append("\n\nAvailable character%s (%i/unlimited):" % (string_s_ending, len(characters)))
else:
string += "\n\nAvailable character%s%s:" % (string_s_ending,
charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "")
result.append("\n\nAvailable character%s%s:"
% (string_s_ending, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or ""))
for char in characters:
csessions = char.sessions.all()
@ -883,14 +881,16 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# character is already puppeted
sid = sess in sessions and sessions.index(sess) + 1
if sess and sid:
string += "\n - |G%s|n [%s] (played by you in session %i)" % (char.key, ", ".join(char.permissions.all()), sid)
result.append("\n - |G%s|n [%s] (played by you in session %i)"
% (char.key, ", ".join(char.permissions.all()), sid))
else:
string += "\n - |R%s|n [%s] (played by someone else)" % (char.key, ", ".join(char.permissions.all()))
result.append("\n - |R%s|n [%s] (played by someone else)"
% (char.key, ", ".join(char.permissions.all())))
else:
# character is "free to puppet"
string += "\n - %s [%s]" % (char.key, ", ".join(char.permissions.all()))
string = ("-" * 68) + "\n" + string + "\n" + ("-" * 68)
return string
result.append("\n - %s [%s]" % (char.key, ", ".join(char.permissions.all())))
look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68)
return look_string
class DefaultGuest(DefaultPlayer):
@ -910,7 +910,6 @@ class DefaultGuest(DefaultPlayer):
self._send_to_connect_channel("|G%s connected|n" % self.key)
self.puppet_object(session, self.db._last_puppet)
def at_server_shutdown(self):
"""
We repeat the functionality of `at_disconnect()` here just to

View file

@ -7,7 +7,7 @@ The separation works like this:
Portal - (AMP client) handles protocols. It contains a list of connected
sessions in a dictionary for identifying the respective player
connected. If it looses the AMP connection it will automatically
connected. If it loses the AMP connection it will automatically
try to reconnect.
Server - (AMP server) Handles all mud operations. The server holds its own list
@ -32,33 +32,33 @@ from twisted.internet import protocol
from twisted.internet.defer import Deferred
from evennia.utils import logger
from evennia.utils.utils import to_str, variable_from_module
import zlib # Used in Compressed class
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
# communication bits
# (chr(9) and chr(10) are \t and \n, so skipping them)
PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync
SCONN = chr(11) # server creating new connection (for irc bots and etc)
PCONNSYNC = chr(12) # portal post-syncing a session
PDISCONNALL = chr(13) # portal session disconnect all
PCONNSYNC = chr(12) # portal post-syncing a session
PDISCONNALL = chr(13) # portal session disconnect all
AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed)
BATCH_RATE = 250 # max commands/sec before switching to batch-sending
BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds
BATCH_RATE = 250 # max commands/sec before switching to batch-sending
BATCH_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds
# buffers
_SENDBATCH = defaultdict(list)
_MSGBUFFER = defaultdict(list)
import zlib
def get_restart_mode(restart_file):
"""
@ -323,9 +323,9 @@ dumps = lambda data: to_str(pickle.dumps(to_str(data), pickle.HIGHEST_PROTOCOL))
loads = lambda data: pickle.loads(to_str(data))
#------------------------------------------------------------
# -------------------------------------------------------------
# Core AMP protocol for communication Server <-> Portal
#------------------------------------------------------------
# -------------------------------------------------------------
class AMPProtocol(amp.AMP):
"""
@ -385,7 +385,6 @@ class AMPProtocol(amp.AMP):
"""
pass
# Error handling
def errback(self, e, info):
@ -447,7 +446,7 @@ class AMPProtocol(amp.AMP):
Access method called by the Portal and executed on the Portal.
Args:
sessid (int): Unique Session id.
session (session): Session
kwargs (any, optional): Optional data.
Returns:
@ -473,7 +472,6 @@ class AMPProtocol(amp.AMP):
self.factory.portal.sessions.data_out(session, **kwargs)
return {}
def send_MsgServer2Portal(self, session, **kwargs):
"""
Access method - executed on the Server for sending data
@ -506,7 +504,7 @@ class AMPProtocol(amp.AMP):
# create a new session and sync it
server_sessionhandler.portal_connect(kwargs.get("sessiondata"))
elif operation == PCONNSYNC: #portal_session_sync
elif operation == PCONNSYNC: # portal_session_sync
server_sessionhandler.portal_session_sync(kwargs.get("sessiondata"))
elif operation == PDISCONN: # portal_session_disconnect
@ -515,7 +513,7 @@ class AMPProtocol(amp.AMP):
if session:
server_sessionhandler.portal_disconnect(session)
elif operation == PDISCONNALL: # portal_disconnect_all
elif operation == PDISCONNALL: # portal_disconnect_all
# portal orders all sessions to close
server_sessionhandler.portal_disconnect_all()
@ -545,7 +543,7 @@ class AMPProtocol(amp.AMP):
"""
return self.send_data(AdminPortal2Server, session.sessid, operation=operation, **kwargs)
# Portal administraton from the Server side
# Portal administration from the Server side
@AdminServer2Portal.responder
def portal_receive_adminserver2portal(self, packed_data):
@ -562,7 +560,6 @@ class AMPProtocol(amp.AMP):
operation = kwargs.pop("operation")
portal_sessionhandler = self.factory.portal.sessions
if operation == SLOGIN: # server_session_login
# a session has authenticated; sync it.
session = portal_sessionhandler.get(sessid)
@ -591,7 +588,7 @@ class AMPProtocol(amp.AMP):
# set a flag in case we are about to shut down soon
self.factory.server_restart_mode = True
elif operation == SCONN: # server_force_connection (for irc/etc)
elif operation == SCONN: # server_force_connection (for irc/etc)
portal_sessionhandler.server_connect(**kwargs)
else:
@ -665,4 +662,5 @@ class AMPProtocol(amp.AMP):
module=modulepath,
function=functionname,
args=dumps(args),
kwargs=dumps(kwargs)).addCallback(lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall")
kwargs=dumps(kwargs)).addCallback(
lambda r: loads(r["result"])).addErrback(self.errback, "FunctionCall")

View file

@ -15,8 +15,7 @@ from evennia.server.models import ServerConfig
from evennia.utils import create, logger
ERROR_NO_SUPERUSER = \
"""
ERROR_NO_SUPERUSER = """
No superuser exists yet. The superuser is the 'owner' account on
the Evennia server. Create a new superuser using the command
@ -26,16 +25,14 @@ ERROR_NO_SUPERUSER = \
"""
LIMBO_DESC = \
_("""
Welcome to your new {wEvennia{n-based game! Visit http://www.evennia.com if you need
LIMBO_DESC = _("""
Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need
help, want to contribute, report issues or just join the community.
As Player #1 you can create a demo/tutorial area with {w@batchcommand tutorial_world.build{n.
As Player #1 you can create a demo/tutorial area with |w@batchcommand tutorial_world.build|n.
""")
WARNING_POSTGRESQL_FIX = \
"""
WARNING_POSTGRESQL_FIX = """
PostgreSQL-psycopg2 compatibility fix:
The in-game channels {chan1}, {chan2} and {chan3} were created,
but the superuser was not yet connected to them. Please use in

View file

@ -40,18 +40,23 @@ IRC_MAGENTA = "13"
IRC_DGREY = "14"
IRC_GRAY = "15"
# test:
# obsolete test:
# {rred {ggreen {yyellow {bblue {mmagenta {ccyan {wwhite {xdgrey
# {Rdred {Gdgreen {Ydyellow {Bdblue {Mdmagenta {Cdcyan {Wlgrey {Xblack
# {[rredbg {[ggreenbg {[yyellowbg {[bbluebg {[mmagentabg {[ccyanbg {[wlgreybg {[xblackbg
# test:
# |rred |ggreen |yyellow |bblue |mmagenta |ccyan |wwhite |xdgrey
# |Rdred |Gdgreen |Ydyellow |Bdblue |Mdmagenta |Cdcyan |Wlgrey |Xblack
# |[rredbg |[ggreenbg |[yyellowbg |[bbluebg |[mmagentabg |[ccyanbg |[wlgreybg |[xblackbg
IRC_COLOR_MAP = dict([
# obs - {-type colors are deprecated but still used in many places.
(r'{n', IRC_RESET), # reset
(r'{n', IRC_RESET), # reset
(r'{/', ""), # line break
(r'{-', " "), # tab
(r'{_', " "), # space
(r'{*', ""), # invert
(r'{-', " "), # tab
(r'{_', " "), # space
(r'{*', ""), # invert
(r'{^', ""), # blinking text
(r'{r', IRC_COLOR + IRC_RED),
@ -69,7 +74,7 @@ IRC_COLOR_MAP = dict([
(r'{B', IRC_COLOR + IRC_DBLUE),
(r'{M', IRC_COLOR + IRC_DMAGENTA),
(r'{C', IRC_COLOR + IRC_DCYAN),
(r'{W', IRC_COLOR + IRC_GRAY), # light grey
(r'{W', IRC_COLOR + IRC_GRAY), # light grey
(r'{X', IRC_COLOR + IRC_BLACK), # pure black
(r'{[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
@ -79,14 +84,14 @@ IRC_COLOR_MAP = dict([
(r'{[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
(r'{[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
(r'{[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background
(r'{[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background
(r'{[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK), # pure black background
# |-type formatting is the thing to use.
(r'|n', IRC_RESET), # reset
(r'|n', IRC_RESET), # reset
(r'|/', ""), # line break
(r'|-', " "), # tab
(r'|_', " "), # space
(r'|*', ""), # invert
(r'|-', " "), # tab
(r'|_', " "), # space
(r'|*', ""), # invert
(r'|^', ""), # blinking text
(r'|r', IRC_COLOR + IRC_RED),
@ -104,7 +109,7 @@ IRC_COLOR_MAP = dict([
(r'|B', IRC_COLOR + IRC_DBLUE),
(r'|M', IRC_COLOR + IRC_DMAGENTA),
(r'|C', IRC_COLOR + IRC_DCYAN),
(r'|W', IRC_COLOR + IRC_GRAY), # light grey
(r'|W', IRC_COLOR + IRC_GRAY), # light grey
(r'|X', IRC_COLOR + IRC_BLACK), # pure black
(r'|[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED),
@ -114,12 +119,13 @@ IRC_COLOR_MAP = dict([
(r'|[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
(r'|[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
(r'|[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background
(r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background
(r'|[x', IRC_COLOR + IRC_NORMAL + "," + IRC_BLACK) # pure black background
])
RE_IRC_COLOR = re.compile(r"|".join([re.escape(key) for key in viewkeys(IRC_COLOR_MAP)]), re.DOTALL)
RE_MXP = re.compile(r'\{lc(.*?)\{lt(.*?)\{le', re.DOTALL)
RE_MXP = re.compile(r'\|lc(.*?)\|lt(.*?)\|le', re.DOTALL)
RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
def sub_irc(ircmatch):
"""
Substitute irc color info. Used by re.sub.
@ -133,6 +139,7 @@ def sub_irc(ircmatch):
"""
return IRC_COLOR_MAP.get(ircmatch.group(), "")
def parse_irc_colors(string):
"""
Parse {-type syntax and replace with IRC color markers
@ -156,9 +163,10 @@ def parse_irc_colors(string):
# IRC bot
class IRCBot(irc.IRCClient, Session):
"""
An IRC bot that tracks actitivity in a channel as well
An IRC bot that tracks activity in a channel as well
as sends text to it when prompted
"""
@ -190,7 +198,7 @@ class IRCBot(irc.IRCClient, Session):
logger.log_info("IRC bot '%s' connected to %s at %s:%s." % (self.nickname, self.channel,
self.network, self.port))
def disconnect(self, reason=None):
def disconnect(self, reason=""):
"""
Called by sessionhandler to disconnect this protocol.
@ -198,7 +206,7 @@ class IRCBot(irc.IRCClient, Session):
reason (str): Motivation for the disconnect.
"""
self.sessionhandler.disconnect(self)
self.sessionhandler.disconnect(self, reason=reason)
self.stopping = True
self.transport.loseConnection()
@ -246,14 +254,14 @@ class IRCBot(irc.IRCClient, Session):
self.sendLine("NAMES %s" % self.channel)
def irc_RPL_NAMREPLY(self, prefix, params):
"Handles IRC NAME request returns (nicklist)"
""""Handles IRC NAME request returns (nicklist)"""
channel = params[2].lower()
if channel != self.channel.lower():
return
self.nicklist += params[3].split(' ')
def irc_RPL_ENDOFNAMES(self, prefix, params):
"Called when the nicklist has finished being returned."
"""Called when the nicklist has finished being returned."""
channel = params[1].lower()
if channel != self.channel.lower():
return
@ -271,7 +279,6 @@ class IRCBot(irc.IRCClient, Session):
"""
self.data_in(text="", type="ping", user="server", channel=self.channel, timing=time)
def data_in(self, text=None, **kwargs):
"""
Data IRC -> Server.

View file

@ -37,9 +37,9 @@ if os.name == 'nt':
# For Windows we need to handle pid files manually.
PORTAL_PIDFILE = os.path.join(settings.GAME_DIR, "server", 'portal.pid')
#------------------------------------------------------------
# -------------------------------------------------------------
# Evennia Portal settings
#------------------------------------------------------------
# -------------------------------------------------------------
VERSION = get_evennia_version()
@ -76,6 +76,8 @@ AMP_ENABLED = AMP_HOST and AMP_PORT and AMP_INTERFACE
# Maintenance function - this is called repeatedly by the portal.
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
def _portal_maintenance():
"""
The maintenance function handles repeated checks and updates that
@ -94,12 +96,12 @@ def _portal_maintenance():
if _IDLE_TIMEOUT > 0:
# only start the maintenance task if we care about idling.
_maintenance_task = LoopingCall(_portal_maintenance)
_maintenance_task.start(60) # called every minute
_maintenance_task.start(60) # called every minute
#------------------------------------------------------------
# -------------------------------------------------------------
# Portal Service object
#------------------------------------------------------------
# -------------------------------------------------------------
class Portal(object):
"""
@ -180,11 +182,11 @@ class Portal(object):
self.shutdown_complete = True
reactor.callLater(0, reactor.stop)
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Start the Portal proxy server and add all active services
#
#------------------------------------------------------------
# -------------------------------------------------------------
# twistd requires us to define the variable 'application' so it knows
# what to execute from.

View file

@ -28,9 +28,11 @@ _CONNECTION_QUEUE = deque()
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
#------------------------------------------------------------
# -------------------------------------------------------------
# Portal-SessionHandler class
#------------------------------------------------------------
# -------------------------------------------------------------
class PortalSessionHandler(SessionHandler):
"""
This object holds the sessions connected to the portal at any time.
@ -95,7 +97,7 @@ class PortalSessionHandler(SessionHandler):
if len(_CONNECTION_QUEUE) > 1:
session.data_out(text=[["%s DoS protection is active. You are queued to connect in %g seconds ..." % (
settings.SERVERNAME,
len(_CONNECTION_QUEUE)*_MIN_TIME_BETWEEN_CONNECTS)],{}])
len(_CONNECTION_QUEUE)*_MIN_TIME_BETWEEN_CONNECTS)], {}])
now = time.time()
if (now - self.connection_last < _MIN_TIME_BETWEEN_CONNECTS) or not self.portal.amp_protocol:
if not session or not self.connection_task:
@ -176,8 +178,7 @@ class PortalSessionHandler(SessionHandler):
del self[session.sessid]
# Tell the Server to disconnect its version of the Session as well.
self.portal.amp_protocol.send_AdminPortal2Server(session,
operation=PDISCONN)
self.portal.amp_protocol.send_AdminPortal2Server(session, operation=PDISCONN)
def disconnect_all(self):
"""
@ -194,7 +195,7 @@ class PortalSessionHandler(SessionHandler):
# inform Server; wait until finished sending before we continue
# removing all the sessions.
self.portal.amp_protocol.send_AdminPortal2Server(DUMMYSESSION,
operation=PDISCONNALL).addCallback(_callback, self)
operation=PDISCONNALL).addCallback(_callback, self)
def server_connect(self, protocol_path="", config=dict()):
"""
@ -233,8 +234,8 @@ class PortalSessionHandler(SessionHandler):
Called by server to force a disconnect by sessid.
Args:
sessid (int): Session id to disconnect.
reason (str, optional): Motivation for disconect.
session (portalsession): Session to disconnect.
reason (str, optional): Motivation for disconnect.
"""
if session:
@ -335,7 +336,7 @@ class PortalSessionHandler(SessionHandler):
"""
for session in self.values():
self.data_out(session, text=[[message],{}])
self.data_out(session, text=[[message], {}])
def data_in(self, session, **kwargs):
"""
@ -352,8 +353,8 @@ class PortalSessionHandler(SessionHandler):
Data is serialized before passed on.
"""
#from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "portalsessionhandler.data_in")
# from evennia.server.profiling.timetrace import timetrace # DEBUG
# text = timetrace(text, "portalsessionhandler.data_in") # DEBUG
try:
text = kwargs['text']
if (_MAX_CHAR_LIMIT > 0) and len(text) > _MAX_CHAR_LIMIT:
@ -365,16 +366,16 @@ class PortalSessionHandler(SessionHandler):
pass
if session:
now = time.time()
if self.command_counter > _MAX_COMMAND_RATE:
if self.command_counter > _MAX_COMMAND_RATE > 0:
# data throttle (anti DoS measure)
dT = now - self.command_counter_reset
delta_time = now - self.command_counter_reset
self.command_counter = 0
self.command_counter_reset = now
self.command_overflow = dT < 1.0
self.command_overflow = delta_time < 1.0
if self.command_overflow:
reactor.callLater(1.0, self.data_in, None)
if self.command_overflow:
self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW],{}])
self.data_out(session, text=[[_ERROR_COMMAND_OVERFLOW], {}])
return
# scrub data
kwargs = self.clean_senddata(session, kwargs)
@ -385,7 +386,7 @@ class PortalSessionHandler(SessionHandler):
self.portal.amp_protocol.send_MsgPortal2Server(session,
**kwargs)
else:
# called by the callLater callback
# called by the callLater callback
if self.command_overflow:
self.command_overflow = False
reactor.callLater(1.0, self.data_in, None)
@ -405,8 +406,8 @@ class PortalSessionHandler(SessionHandler):
method exixts, it sends the data to a method send_default.
"""
#from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "portalsessionhandler.data_out")
# from evennia.server.profiling.timetrace import timetrace # DEBUG
# text = timetrace(text, "portalsessionhandler.data_out") # DEBUG
# distribute outgoing data to the correct session methods.
if session:

View file

@ -32,7 +32,7 @@ packages).
try:
from twisted.conch.ssh.keys import Key
except ImportError:
raise ImportError (_SSH_IMPORT_ERROR)
raise ImportError(_SSH_IMPORT_ERROR)
from twisted.conch.ssh.userauth import SSHUserAuthServer
from twisted.conch.ssh import common
@ -49,7 +49,7 @@ from evennia.players.models import PlayerDB
from evennia.utils import ansi
from evennia.utils.utils import to_str
_RE_N = re.compile(r"\{n$")
_RE_N = re.compile(r"\|n$")
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
_GAME_DIR = settings.GAME_DIR
@ -206,7 +206,7 @@ class SshProtocol(Manhole, session.Session):
"""
for line in string.split('\n'):
#this is the telnet-specific method for sending
# the telnet-specific method for sending
self.terminal.write(line)
self.terminal.nextLine()
@ -255,7 +255,7 @@ class SshProtocol(Manhole, session.Session):
Note that it must be actively turned back on again!
"""
#print "telnet.send_text", args,kwargs
# print "telnet.send_text", args,kwargs # DEBUG
text = args[0] if args else ""
if text is None:
return
@ -268,8 +268,8 @@ class SshProtocol(Manhole, session.Session):
useansi = options.get("ansi", flags.get('ANSI', True))
raw = options.get("raw", flags.get("RAW", False))
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
#echo = options.get("echo", None)
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
# echo = options.get("echo", None) # DEBUG
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
if screenreader:
# screenreader mode cleans up output
@ -283,7 +283,8 @@ class SshProtocol(Manhole, session.Session):
else:
# we need to make sure to kill the color at the end in order
# to match the webclient output.
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256, mxp=False)
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"),
strip_ansi=nocolor, xterm256=xterm256, mxp=False)
self.sendLine(linetosend)
def send_prompt(self, *args, **kwargs):
@ -453,11 +454,10 @@ def makeFactory(configdict):
factory.publicKeys = {'ssh-rsa': publicKey}
factory.privateKeys = {'ssh-rsa': privateKey}
except Exception as err:
print ( "getKeyPair error: {err}\n WARNING: Evennia could not " \
"auto-generate SSH keypair. Using conch default keys instead.\n" \
"If this error persists, create {pub} and " \
"{priv} yourself using third-party tools.".format(
err=err, pub=pubkeyfile, priv=privkeyfile))
print("getKeyPair error: {err}\n WARNING: Evennia could not "
"auto-generate SSH keypair. Using conch default keys instead.\n"
"If this error persists, create {pub} and "
"{priv} yourself using third-party tools.".format(err=err, pub=pubkeyfile, priv=privkeyfile))
factory.services = factory.services.copy()
factory.services['ssh-userauth'] = ExtraInfoAuthServer

View file

@ -54,6 +54,7 @@ class SSLProtocol(TelnetProtocol):
super(SSLProtocol, self).__init__(*args, **kwargs)
self.protocol_name = "ssl"
def verify_SSL_key_and_cert(keyfile, certfile):
"""
This function looks for RSA key and certificate in the current
@ -82,7 +83,7 @@ def verify_SSL_key_and_cert(keyfile, certfile):
# try to create the certificate
CERT_EXPIRE = 365 * 20 # twenty years validity
# default:
#openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
# openssl req -new -x509 -key ssl.key -out ssl.cert -days 7300
exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
try:
subprocess.call(exestring)

View file

@ -19,12 +19,13 @@ from evennia.server.portal.mxp import Mxp, mxp_parse
from evennia.utils import ansi
from evennia.utils.utils import to_str
_RE_N = re.compile(r"\{n$")
_RE_N = re.compile(r"\|n$")
_RE_LEND = re.compile(r"\n$|\r$|\r\n$|\r\x00$|", re.MULTILINE)
_RE_LINEBREAK = re.compile(r"\n\r|\r\n|\n|\r", re.DOTALL + re.MULTILINE)
_RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
_IDLE_COMMAND = settings.IDLE_COMMAND + "\n"
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
"""
Each player connecting over telnet (ie using most traditional mud
@ -46,7 +47,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
client_address = client_address[0] if client_address else None
# this number is counted down for every handshake that completes.
# when it reaches 0 the portal/server syncs their data
self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
self.init_session(self.protocol_name, client_address, self.factory.sessionhandler)
# negotiate client size
@ -79,7 +80,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.toggle_nop_keepalive()
def _send_nop_keepalive(self):
"Send NOP keepalive unless flag is set"
"""Send NOP keepalive unless flag is set"""
if self.protocol_flags.get("NOPKEEPALIVE"):
self._write(IAC + NOP)
@ -140,7 +141,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
enable (bool): If this option should be enabled.
"""
return (option == MCCP or option==ECHO)
return option == MCCP or option == ECHO
def disableLocal(self, option):
"""
@ -178,7 +179,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
directly.
Args:
string (str): Incoming data.
data (str): Incoming data.
"""
if not data:
@ -188,7 +189,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# legacy clients. There should never be a reason to send a
# lone NULL character so this seems to be a safe thing to
# support for backwards compatibility. It also stops the
# NULL from continously popping up as an unknown command.
# NULL from continuously popping up as an unknown command.
data = [_IDLE_COMMAND]
else:
data = _RE_LINEBREAK.split(data)
@ -205,7 +206,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.data_in(text=dat + "\n")
def _write(self, data):
"hook overloading the one used in plain telnet"
"""hook overloading the one used in plain telnet"""
data = data.replace('\n', '\r\n').replace('\r\r\n', '\r\n')
super(TelnetProtocol, self)._write(mccp_compress(self, data))
@ -217,24 +218,23 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
line (str): Line to send.
"""
#escape IAC in line mode, and correctly add \r\n
# escape IAC in line mode, and correctly add \r\n
line += self.delimiter
line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n')
return self.transport.write(mccp_compress(self, line))
# Session hooks
def disconnect(self, reason=None):
def disconnect(self, reason=""):
"""
generic hook for the engine to call in order to
disconnect this protocol.
Args:
reason (str): Reason for disconnecting.
reason (str, optional): Reason for disconnecting.
"""
self.data_out(text=((reason or "",), {}))
self.data_out(text=((reason,), {}))
self.connectionLost(reason)
def data_in(self, **kwargs):
@ -245,8 +245,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
kwargs (any): Options from the protocol.
"""
#from evennia.server.profiling.timetrace import timetrace
#text = timetrace(text, "telnet.data_in")
# from evennia.server.profiling.timetrace import timetrace # DEBUG
# text = timetrace(text, "telnet.data_in") # DEBUG
self.sessionhandler.data_in(self, **kwargs)
@ -297,7 +297,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
echo = options.get("echo", None)
mxp = options.get("mxp", flags.get("MXP", False))
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
screenreader = options.get("screenreader", flags.get("SCREENREADER", False))
if screenreader:
# screenreader mode cleans up output
@ -306,9 +306,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if options.get("send_prompt"):
# send a prompt instead.
prompt = text
if not raw:
# processing
prompt = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256)
prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + ("|n" if prompt[-1] != "|" else "||n"),
strip_ansi=nocolor, xterm256=xterm256)
if mxp:
prompt = mxp_parse(prompt)
prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n')
@ -335,7 +337,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
else:
# we need to make sure to kill the color at the end in order
# to match the webclient output.
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + "{n", strip_ansi=nocolor, xterm256=xterm256, mxp=mxp)
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"),
strip_ansi=nocolor, xterm256=xterm256, mxp=mxp)
if mxp:
linetosend = mxp_parse(linetosend)
self.sendLine(linetosend)
@ -348,7 +351,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
kwargs["options"].update({"send_prompt": True})
self.send_text(*args, **kwargs)
def send_default(self, cmdname, *args, **kwargs):
"""
Send other oob data

View file

@ -32,12 +32,12 @@ from evennia.utils.utils import to_str
# MSDP-relevant telnet cmd/opt-codes
MSDP = chr(69)
MSDP_VAR = chr(1) #^A
MSDP_VAL = chr(2) #^B
MSDP_TABLE_OPEN = chr(3) #^C
MSDP_TABLE_CLOSE = chr(4) #^D
MSDP_ARRAY_OPEN = chr(5) #^E
MSDP_ARRAY_CLOSE = chr(6) #^F
MSDP_VAR = chr(1) # ^A
MSDP_VAL = chr(2) # ^B
MSDP_TABLE_OPEN = chr(3) # ^C
MSDP_TABLE_CLOSE = chr(4) # ^D
MSDP_ARRAY_OPEN = chr(5) # ^E
MSDP_ARRAY_CLOSE = chr(6) # ^F
# GMCP
GMCP = chr(201)
@ -51,13 +51,15 @@ force_str = lambda inp: to_str(inp, force_string=True)
# pre-compiled regexes
# returns 2-tuple
msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
MSDP_TABLE_OPEN,
MSDP_TABLE_CLOSE))
msdp_regex_table = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s"
% (MSDP_VAR, MSDP_VAL,
MSDP_TABLE_OPEN,
MSDP_TABLE_CLOSE))
# returns 2-tuple
msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s" % (MSDP_VAR, MSDP_VAL,
MSDP_ARRAY_OPEN,
MSDP_ARRAY_CLOSE))
msdp_regex_array = re.compile(r"%s\s*(\w*?)\s*%s\s*%s(.*?)%s"
% (MSDP_VAR, MSDP_VAL,
MSDP_ARRAY_OPEN,
MSDP_ARRAY_CLOSE))
msdp_regex_var = re.compile(r"%s" % MSDP_VAR)
msdp_regex_val = re.compile(r"%s" % MSDP_VAL)
@ -67,7 +69,8 @@ EVENNIA_TO_GMCP = {"client_options": "Core.Supports.Get",
"repeat": "Char.Repeat.Update",
"monitor": "Char.Monitor.Update"}
# Msdp object handler
# MSDP/GMCP communication handler
class TelnetOOB(object):
"""
@ -100,7 +103,7 @@ class TelnetOOB(object):
Client reports No msdp supported or wanted.
Args:
options (Option): Not used.
option (Option): Not used.
"""
# no msdp, check GMCP
@ -173,7 +176,7 @@ class TelnetOOB(object):
if not (args or kwargs):
return msdp_cmdname
#print "encode_msdp in:", cmdname, args, kwargs
# print("encode_msdp in:", cmdname, args, kwargs) # DEBUG
msdp_args = ''
if args:
@ -182,30 +185,30 @@ class TelnetOOB(object):
msdp_args += args[0]
else:
msdp_args += "{msdp_array_open}" \
"{msdp_args}" \
"{msdp_array_close}".format(
msdp_array_open=MSDP_ARRAY_OPEN,
msdp_array_close=MSDP_ARRAY_CLOSE,
msdp_args= "".join("%s%s" % (
MSDP_VAL, json.dumps(val))
for val in args))
"{msdp_args}" \
"{msdp_array_close}".format(
msdp_array_open=MSDP_ARRAY_OPEN,
msdp_array_close=MSDP_ARRAY_CLOSE,
msdp_args="".join("%s%s"
% (MSDP_VAL, json.dumps(val))
for val in args))
msdp_kwargs = ""
if kwargs:
msdp_kwargs = msdp_cmdname
msdp_kwargs += "{msdp_table_open}" \
"{msdp_kwargs}" \
"{msdp_table_close}".format(
msdp_table_open=MSDP_TABLE_OPEN,
msdp_table_close=MSDP_TABLE_CLOSE,
msdp_kwargs = "".join("%s%s%s%s" % (
MSDP_VAR, key, MSDP_VAL, json.dumps(val))
for key, val in kwargs.iteritems()))
"{msdp_kwargs}" \
"{msdp_table_close}".format(
msdp_table_open=MSDP_TABLE_OPEN,
msdp_table_close=MSDP_TABLE_CLOSE,
msdp_kwargs="".join("%s%s%s%s"
% (MSDP_VAR, key, MSDP_VAL,
json.dumps(val))
for key, val in kwargs.iteritems()))
msdp_string = msdp_args + msdp_kwargs
#print "msdp_string:", msdp_string
# print("msdp_string:", msdp_string) # DEBUG
return msdp_string
def encode_gmcp(self, cmdname, *args, **kwargs):
@ -238,10 +241,10 @@ class TelnetOOB(object):
gmcp_string = "%s %s" % (cmdname, json.dumps([args, kwargs]))
else:
gmcp_string = "%s %s" % (cmdname, json.dumps(args))
else: # only kwargs
else: # only kwargs
gmcp_string = "%s %s" % (cmdname, json.dumps(kwargs))
#print "gmcp string", gmcp_string
# print("gmcp string", gmcp_string) # DEBUG
return gmcp_string
def decode_msdp(self, data):
@ -271,7 +274,7 @@ class TelnetOOB(object):
if hasattr(data, "__iter__"):
data = "".join(data)
#print "decode_msdp in:", data
# print("decode_msdp in:", data) # DEBUG
tables = {}
arrays = {}
@ -279,7 +282,7 @@ class TelnetOOB(object):
# decode tables
for key, table in msdp_regex_table.findall(data):
tables[key] = {} if not key in tables else tables[key]
tables[key] = {} if key not in tables else tables[key]
for varval in msdp_regex_var.split(table)[1:]:
var, val = msdp_regex_val.split(varval, 1)
if var:
@ -288,7 +291,7 @@ class TelnetOOB(object):
# decode arrays from all that was not a table
data_no_tables = msdp_regex_table.sub("", data)
for key, array in msdp_regex_array.findall(data_no_tables):
arrays[key] = [] if not key in arrays else arrays[key]
arrays[key] = [] if key not in arrays else arrays[key]
parts = msdp_regex_val.split(array)
if len(parts) == 2:
arrays[key].append(parts[1])
@ -326,10 +329,9 @@ class TelnetOOB(object):
for key, var in variables.iteritems():
cmds[key] = [[var], {}]
#print "msdp data in:", cmds
# print("msdp data in:", cmds) # DEBUG
self.protocol.data_in(**cmds)
def decode_gmcp(self, data):
"""
Decodes incoming GMCP data on the form 'varname <structure>'.
@ -353,7 +355,7 @@ class TelnetOOB(object):
if hasattr(data, "__iter__"):
data = "".join(data)
#print "decode_gmcp in:", data
# print("decode_gmcp in:", data) # DEBUG
if data:
try:
cmdname, structure = data.split(None, 1)
@ -368,7 +370,7 @@ class TelnetOOB(object):
args, kwargs = [], {}
if hasattr(structure, "__iter__"):
if isinstance(structure, dict):
kwargs = {key: value for key, value in structure.iteritems() if key }
kwargs = {key: value for key, value in structure.iteritems() if key}
else:
args = list(structure)
else:

View file

@ -27,6 +27,7 @@ MTTS = [(128, 'PROXY'),
(2, 'VT100'),
(1, 'ANSI')]
class Ttype(object):
"""
Handles ttype negotiations. Called and initiated by the
@ -104,22 +105,21 @@ class Ttype(object):
# use name to identify support for xterm256. Many of these
# only support after a certain version, but all support
# it since at least 4 years. We assume recent client here for now.
xterm256 = False
cupper = clientname.upper()
if cupper.startswith("MUDLET"):
# supports xterm256 stably since 1.1 (2010?)
xterm256 = cupper.split("MUDLET",1)[1].strip() >= "1.1"
xterm256 = cupper.split("MUDLET", 1)[1].strip() >= "1.1"
else:
xterm256 = (cupper.startswith("XTERM") or
cupper.endswith("-256COLOR") or
cupper in ("ATLANTIS", # > 0.9.9.0 (aug 2009)
"CMUD", # > 3.04 (mar 2009)
"KILDCLIENT", # > 2.2.0 (sep 2005)
"MUDLET", # > beta 15 (sep 2009)
"MUSHCLIENT", # > 4.02 (apr 2007)
"PUTTY", # > 0.58 (apr 2005)
"BEIP", # > 2.00.206 (late 2009) (BeipMu)
"POTATO")) # > 2.00 (maybe earlier)
"CMUD", # > 3.04 (mar 2009)
"KILDCLIENT", # > 2.2.0 (sep 2005)
"MUDLET", # > beta 15 (sep 2009)
"MUSHCLIENT", # > 4.02 (apr 2007)
"PUTTY", # > 0.58 (apr 2005)
"BEIP", # > 2.00.206 (late 2009) (BeipMu)
"POTATO")) # > 2.00 (maybe earlier)
# all clients supporting TTYPE at all seem to support ANSI
self.protocol.protocol_flags['ANSI'] = True

View file

@ -32,8 +32,9 @@ from django.utils.translation import ugettext as _
# Handlers for Session.db/ndb operation
class NDbHolder(object):
"Holder for allowing property access of attributes"
"""Holder for allowing property access of attributes"""
def __init__(self, obj, name, manager_name='attributes'):
_SA(self, name, _GA(obj, manager_name))
_SA(self, 'name', name)
@ -145,9 +146,9 @@ class NAttributeHandler(object):
return [key for key in self._store if not key.startswith("_")]
#------------------------------------------------------------
# -------------------------------------------------------------
# Server Session
#------------------------------------------------------------
# -------------------------------------------------------------
class ServerSession(Session):
"""
@ -160,7 +161,7 @@ class ServerSession(Session):
"""
def __init__(self):
"Initiate to avoid AttributeErrors down the line"
"""Initiate to avoid AttributeErrors down the line"""
self.puppet = None
self.player = None
self.cmdset_storage_string = ""
@ -203,7 +204,7 @@ class ServerSession(Session):
obj.player = self.player
self.puid = obj.id
self.puppet = obj
#obj.scripts.validate()
# obj.scripts.validate()
obj.locks.cache_lock_bypass(obj)
def at_login(self, player):
@ -264,7 +265,6 @@ class ServerSession(Session):
MONITOR_HANDLER.remove(player, "_saved_webclient_options",
self.sessid)
def get_player(self):
"""
Get the player associated with this session
@ -364,7 +364,6 @@ class ServerSession(Session):
self.protocol_flags.update(kwargs)
self.sessionhandler.session_portal_sync(self)
def data_out(self, **kwargs):
"""
Sending data from Evennia->Client
@ -437,7 +436,7 @@ class ServerSession(Session):
self.sessionhandler.data_in(self, **kwargs)
def __eq__(self, other):
"Handle session comparisons"
"""Handle session comparisons"""
try:
return self.address == other.address
except AttributeError:
@ -462,11 +461,9 @@ class ServerSession(Session):
return "%s%s@%s" % (self.uname, symbol, address)
def __unicode__(self):
"Unicode representation"
"""Unicode representation"""
return u"%s" % str(self)
# Dummy API hooks for use during non-loggedin operation
def at_cmdset_get(self, **kwargs):
@ -488,7 +485,7 @@ class ServerSession(Session):
def attributes(self):
return self.nattributes
#@property
# @property
def ndb_get(self):
"""
A non-persistent store (ndb: NonDataBase). Everything stored
@ -503,7 +500,7 @@ class ServerSession(Session):
self._ndb_holder = NDbHolder(self, "nattrhandler", manager_name="nattributes")
return self._ndb_holder
#@ndb.setter
# @ndb.setter
def ndb_set(self, value):
"""
Stop accidentally replacing the db object
@ -516,9 +513,9 @@ class ServerSession(Session):
string += "Use ndb.attr=value instead."
raise Exception(string)
#@ndb.deleter
# @ndb.deleter
def ndb_del(self):
"Stop accidental deletion."
"""Stop accidental deletion."""
raise Exception("Cannot delete the ndb object!")
ndb = property(ndb_get, ndb_set, ndb_del)
db = property(ndb_get, ndb_set, ndb_del)
@ -526,5 +523,5 @@ class ServerSession(Session):
# Mock access method for the session (there is no lock info
# at this stage, so we just present a uniform API)
def access(self, *args, **kwargs):
"Dummy method to mimic the logged-in API."
"""Dummy method to mimic the logged-in API."""
return True

View file

@ -23,6 +23,8 @@ from twisted.web.wsgi import WSGIResource
from django.conf import settings
from django.core.handlers.wsgi import WSGIHandler
from evennia.utils import logger
_UPSTREAM_IPS = settings.UPSTREAM_IPS
_DEBUG = settings.DEBUG
@ -70,6 +72,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
resource (EvenniaReverseProxyResource): A proxy resource.
"""
request.notifyFinish().addErrback(lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f))
return EvenniaReverseProxyResource(
self.host, self.port, self.path + '/' + urlquote(path, safe=""),
self.reactor)
@ -98,6 +101,8 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
request.getAllHeaders(), request.content.read(), request)
clientFactory.noisy = False
self.reactor.connectTCP(self.host, self.port, clientFactory)
# don't trigger traceback if connection is lost before request finish.
request.notifyFinish().addErrback(lambda f: f.cancel())
return NOT_DONE_YET

View file

@ -24,11 +24,12 @@ from evennia.utils.utils import lazy_property, to_str, make_iter
_TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Attributes
#
#------------------------------------------------------------
# -------------------------------------------------------------
class Attribute(SharedMemoryModel):
"""
@ -90,7 +91,7 @@ class Attribute(SharedMemoryModel):
'date_created', editable=False, auto_now_add=True)
# Database manager
#objects = managers.AttributeManager()
# objects = managers.AttributeManager()
@lazy_property
def locks(self):
@ -110,12 +111,15 @@ class Attribute(SharedMemoryModel):
def __lock_storage_get(self):
return self.db_lock_storage
def __lock_storage_set(self, value):
self.db_lock_storage = value
self.save(update_fields=["db_lock_storage"])
def __lock_storage_del(self):
self.db_lock_storage = ""
self.save(update_fields=["db_lock_storage"])
lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del)
# Wrapper properties to easily set database fields. These are
@ -127,7 +131,7 @@ class Attribute(SharedMemoryModel):
# is the object in question).
# value property (wraps db_value)
#@property
# @property
def __value_get(self):
"""
Getter. Allows for `value = self.value`.
@ -137,19 +141,19 @@ class Attribute(SharedMemoryModel):
"""
return from_pickle(self.db_value, db_obj=self)
#@value.setter
# @value.setter
def __value_set(self, new_value):
"""
Setter. Allows for self.value = value. We cannot cache here,
see self.__value_get.
"""
self.db_value = to_pickle(new_value)
#print "value_set, self.db_value:", repr(self.db_value)
# print("value_set, self.db_value:", repr(self.db_value)) # DEBUG
self.save(update_fields=["db_value"])
#@value.deleter
# @value.deleter
def __value_del(self):
"Deleter. Allows for del attr.value. This removes the entire attribute."
"""Deleter. Allows for del attr.value. This removes the entire attribute."""
self.delete()
value = property(__value_get, __value_set, __value_del)
@ -163,7 +167,7 @@ class Attribute(SharedMemoryModel):
return smart_str("%s(%s)" % (self.db_key, self.id))
def __unicode__(self):
return u"%s(%s)" % (self.db_key,self.id)
return u"%s(%s)" % (self.db_key, self.id)
def access(self, accessing_obj, access_type='read', default=False, **kwargs):
"""
@ -202,7 +206,7 @@ class AttributeHandler(object):
_attrtype = None
def __init__(self, obj):
"Initialize handler."
"""Initialize handler."""
self.obj = obj
self._objid = obj.id
self._model = to_str(obj.__dbclass__.__name__.lower())
@ -213,16 +217,16 @@ class AttributeHandler(object):
self._cache_complete = False
def _fullcache(self):
"Cache all attributes of this object"
query = {"%s__id" % self._model : self._objid,
"attribute__db_model" : self._model,
"attribute__db_attrtype" : self._attrtype}
"""Cache all attributes of this object"""
query = {"%s__id" % self._model: self._objid,
"attribute__db_model": self._model,
"attribute__db_attrtype": self._attrtype}
attrs = [conn.attribute for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(),
attr.db_category.lower() if attr.db_category else None),
attr) for attr in attrs)
self._cache_complete = True
def _getcache(self, key=None, category=None):
"""
Retrieve from cache or database (always caches)
@ -264,15 +268,15 @@ class AttributeHandler(object):
del self._cache[cachekey]
if cachefound:
if attr:
return [attr] # return cached entity
return [attr] # return cached entity
else:
return [] # no such attribute: return an empty list
else:
query = {"%s__id" % self._model : self._objid,
"attribute__db_model" : self._model,
"attribute__db_attrtype" : self._attrtype,
"attribute__db_key__iexact" : key.lower(),
"attribute__db_category__iexact" : category.lower() if category else None}
query = {"%s__id" % self._model: self._objid,
"attribute__db_model": self._model,
"attribute__db_attrtype": self._attrtype,
"attribute__db_key__iexact": key.lower(),
"attribute__db_category__iexact": category.lower() if category else None}
conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)
if conn:
attr = conn[0].attribute
@ -290,15 +294,15 @@ class AttributeHandler(object):
# for this category before
catkey = "-%s" % category
if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache:
return [attr for key, attr in self._cache.items() if key.endswith(catkey)]
return [attr for key, attr in self._cache.items() if key.endswith(catkey) and attr]
else:
# we have to query to make this category up-date in the cache
query = {"%s__id" % self._model : self._objid,
"attribute__db_model" : self._model,
"attribute__db_attrtype" : self._attrtype,
"attribute__db_category__iexact" : category.lower() if category else None}
attrs = [conn.attribute for conn in getattr(self.obj,
self._m2m_fieldname).through.objects.filter(**query)]
query = {"%s__id" % self._model: self._objid,
"attribute__db_model": self._model,
"attribute__db_attrtype": self._attrtype,
"attribute__db_category__iexact": category.lower() if category else None}
attrs = [conn.attribute for conn
in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
for attr in attrs:
if attr.pk:
cachekey = "%s-%s" % (attr.db_key, category)
@ -306,7 +310,6 @@ class AttributeHandler(object):
# mark category cache as up-to-date
self._catcache[catkey] = True
return attrs
return []
def _setcache(self, key, category, attr_obj):
"""
@ -318,7 +321,7 @@ class AttributeHandler(object):
attr_obj (Attribute): The newly saved attribute
"""
if not key: # don't allow an empty key in cache
if not key: # don't allow an empty key in cache
return
cachekey = "%s-%s" % (key, category)
catkey = "-%s" % category
@ -342,7 +345,7 @@ class AttributeHandler(object):
self._cache.pop(cachekey, None)
else:
self._cache = {key: attrobj for key, attrobj in
self._cache.items() if not key.endswith(catkey)}
self._cache.items() if not key.endswith(catkey)}
# mark that the category cache is no longer up-to-date
self._catcache.pop(catkey, None)
self._cache_complete = False
@ -402,6 +405,7 @@ class AttributeHandler(object):
accessing_obj (object, optional): If set, an `attrread`
permission lock will be checked before returning each
looked-after Attribute.
default_access (bool, optional):
Returns:
result (any, Attribute or list): This will be the value of the found
@ -416,7 +420,7 @@ class AttributeHandler(object):
"""
class RetDefault(object):
"Holds default values"
"""Holds default values"""
def __init__(self):
self.key = None
self.value = default
@ -444,8 +448,7 @@ class AttributeHandler(object):
ret = ret if return_obj else [attr.value for attr in ret if attr]
if not ret:
return ret if len(key) > 1 else default
return ret[0] if len(ret)==1 else ret
return ret[0] if len(ret) == 1 else ret
def add(self, key, value, category=None, lockstring="",
strattr=False, accessing_obj=None, default_access=True):
@ -470,8 +473,7 @@ class AttributeHandler(object):
`attrcreate` is defined on the Attribute in question.
"""
if accessing_obj and not self.obj.access(accessing_obj,
self._attrcreate, default=default_access):
if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access):
# check create access
return
@ -494,21 +496,20 @@ class AttributeHandler(object):
attr_obj.value = value
else:
# create a new Attribute (no OOB handlers can be notified)
kwargs = {"db_key" : keystr,
"db_category" : category,
"db_model" : self._model,
"db_attrtype" : self._attrtype,
"db_value" : None if strattr else to_pickle(value),
"db_strvalue" : value if strattr else None}
kwargs = {"db_key": keystr,
"db_category": category,
"db_model": self._model,
"db_attrtype": self._attrtype,
"db_value": None if strattr else to_pickle(value),
"db_strvalue": value if strattr else None}
new_attr = Attribute(**kwargs)
new_attr.save()
getattr(self.obj, self._m2m_fieldname).add(new_attr)
# update cache
self._setcache(keystr, category, new_attr)
def batch_add(self, key, value, category=None, lockstring="",
strattr=False, accessing_obj=None, default_access=True):
strattr=False, accessing_obj=None, default_access=True):
"""
Batch-version of `add()`. This is more efficient than
repeat-calling add when having many Attributes to add.
@ -535,8 +536,7 @@ class AttributeHandler(object):
RuntimeError: If `key` and `value` lists are not of the
same lengths.
"""
if accessing_obj and not self.obj.access(accessing_obj,
self._attrcreate, default=default_access):
if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access):
# check create access
return
@ -564,12 +564,12 @@ class AttributeHandler(object):
attr_obj.value = new_value
else:
# create a new Attribute (no OOB handlers can be notified)
kwargs = {"db_key" : keystr,
"db_category" : category,
kwargs = {"db_key": keystr,
"db_category": category,
"db_model": self._model,
"db_attrtype" : self._attrtype,
"db_value" : None if strattr else to_pickle(new_value),
"db_strvalue" : value if strattr else None}
"db_attrtype": self._attrtype,
"db_value": None if strattr else to_pickle(new_value),
"db_strvalue": value if strattr else None}
new_attr = Attribute(**kwargs)
new_attr.save()
new_attrobjs.append(new_attr)
@ -578,7 +578,6 @@ class AttributeHandler(object):
# Add new objects to m2m field all at once
getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs)
def remove(self, key, raise_exception=False, category=None,
accessing_obj=None, default_access=True):
"""
@ -664,7 +663,7 @@ class AttributeHandler(object):
key=lambda o: o.id)
if accessing_obj:
return [attr for attr in attrs
if attr.access(accessing_obj, self._attredit, default=default_access)]
if attr.access(accessing_obj, self._attredit, default=default_access)]
else:
return attrs
@ -729,7 +728,6 @@ def initialize_nick_templates(in_template, out_template):
"""
# create the regex for in_template
regex_string = fnmatch.translate(in_template)
# we must account for a possible line break coming over the wire
@ -876,12 +874,12 @@ class NickHandler(AttributeHandler):
nicks = {}
for category in make_iter(categories):
nicks.update({nick.key: nick
for nick in make_iter(self.get(category=category, return_obj=True)) if nick and nick.key})
for nick in make_iter(self.get(category=category, return_obj=True)) if nick and nick.key})
if include_player and self.obj.has_player:
for category in make_iter(categories):
nicks.update({nick.key: nick
for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True))
if nick and nick.key})
for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True))
if nick and nick.key})
for key, nick in nicks.iteritems():
nick_regex, template, _, _ = nick.value
regex = self._regex_cache.get(nick_regex)

View file

@ -5,12 +5,15 @@ Use the codes defined in ANSIPARSER in your text
to apply colour to text according to the ANSI standard.
Examples:
This is %crRed text%cn and this is normal again.
This is {rRed text{n and this is normal again.
This is |rRed text|n and this is normal again.
This is {rRed text{n and this is normal again. # soon to be depreciated
This is %crRed text%cn and this is normal again. # depreciated
Mostly you should not need to call parse_ansi() explicitly;
it is run by Evennia just before returning data to/from the
user.
user. Depreciated example forms are available by extending
the ansi mapping.
"""
from builtins import object, range
@ -133,7 +136,7 @@ class ANSIParser(object):
rgbtag = rgbmatch.group()[1:]
background = rgbtag[0] == '['
grayscale = rgbtag[0 + int(background)] == '='
grayscale = rgbtag[0 + int(background)] == '='
if not grayscale:
# 6x6x6 color-cube (xterm indexes 16-231)
if background:
@ -143,9 +146,9 @@ class ANSIParser(object):
else:
# grayscale values (xterm indexes 0, 232-255, 15) for full spectrum
letter = rgbtag[int(background) + 1]
if (letter == 'a'):
if letter == 'a':
colval = 16 # pure black @ index 16 (first color cube entry)
elif (letter == 'z'):
elif letter == 'z':
colval = 231 # pure white @ index 231 (last color cube entry)
else:
# letter in range [b..y] (exactly 24 values!)
@ -161,8 +164,8 @@ class ANSIParser(object):
colval = 16 + (red * 36) + (green * 6) + blue
return "\033[%s8;5;%sm" % (3 + int(background), colval)
# replaced since some cliens (like Potato) does not accept codes with leading zeroes, see issue #1024.
#return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10)
# replaced since some clients (like Potato) does not accept codes with leading zeroes, see issue #1024.
# return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10)
else:
# xterm256 not supported, convert the rgb value to ansi instead
@ -289,7 +292,7 @@ class ANSIParser(object):
in_string = utils.to_str(string)
# do string replacement
parsed_string = ""
parsed_string = ""
parts = self.ansi_escapes.split(in_string) + [" "]
for part, sep in zip(parts[::2], parts[1::2]):
pstring = self.xterm256_sub.sub(do_xterm256, part)
@ -307,7 +310,7 @@ class ANSIParser(object):
# cache and crop old cache
_PARSE_CACHE[cachekey] = parsed_string
if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE:
_PARSE_CACHE.popitem(last=False)
_PARSE_CACHE.popitem(last=False)
return parsed_string
@ -321,8 +324,8 @@ class ANSIParser(object):
(r'{/', ANSI_RETURN), # line break
(r'{-', ANSI_TAB), # tab
(r'{_', ANSI_SPACE), # space
(r'{*', ANSI_INVERSE), # invert
(r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'{*', ANSI_INVERSE), # invert
(r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'{u', ANSI_UNDERLINE), # underline
(r'{r', hilite + ANSI_RED),
@ -366,14 +369,14 @@ class ANSIParser(object):
(r'{[W', ANSI_BACK_WHITE), # light grey background
(r'{[X', ANSI_BACK_BLACK), # pure black background
## alternative |-format
# alternative |-format
(r'|n', ANSI_NORMAL), # reset
(r'|/', ANSI_RETURN), # line break
(r'|-', ANSI_TAB), # tab
(r'|_', ANSI_SPACE), # space
(r'|*', ANSI_INVERSE), # invert
(r'|^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'|*', ANSI_INVERSE), # invert
(r'|^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'|u', ANSI_UNDERLINE), # underline
(r'|r', hilite + ANSI_RED),
@ -433,8 +436,7 @@ class ANSIParser(object):
(r'{[w', r'{[555'), # white background
(r'{[x', r'{[222'), # dark grey background
## |-style variations
# |-style variations
(r'|[r', r'|[500'),
(r'|[g', r'|[050'),
(r'|[y', r'|[550'),
@ -448,13 +450,13 @@ class ANSIParser(object):
# the sub_xterm256 method
xterm256_map = [
(r'\{[0-5]{3}', ""), # {123 - foreground colour
(r'\{[0-5]{3}', ""), # {123 - foreground colour
(r'\{\[[0-5]{3}', ""), # {[123 - background colour
## |-style
(r'\|[0-5]{3}', ""), # |123 - foreground colour
(r'\|\[[0-5]{3}', ""), # |[123 - background colour
# |-style
(r'\|[0-5]{3}', ""), # |123 - foreground colour
(r'\|\[[0-5]{3}', ""), # |[123 - background colour
## grayscale entries including ansi extremes: {=a .. {=z
# grayscale entries including ansi extremes: {=a .. {=z
(r'\{=[a-z]', ""),
(r'\{\[=[a-z]', ""),
(r'\|=[a-z]', ""),
@ -512,6 +514,7 @@ def strip_ansi(string, parser=ANSI_PARSER):
markup.
Args:
string (str): The string to strip.
parser (ansi.AnsiParser, optional): The parser to use.
Returns:
@ -520,6 +523,7 @@ def strip_ansi(string, parser=ANSI_PARSER):
"""
return parser.parse_ansi(string, strip_ansi=True)
def strip_raw_ansi(string, parser=ANSI_PARSER):
"""
Remove raw ansi codes from string. This assumes pure
@ -544,7 +548,7 @@ def raw(string):
string (str): The raw, escaped string.
"""
return string.replace('{', '{{')
return string.replace('{', '{{').replace('|', '||')
def group(lst, n):
@ -1056,7 +1060,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
res.append(self[start:len(self)])
if drop_spaces:
return [part for part in res if part != ""]
return [part for part in res if part != ""]
return res
def rsplit(self, by=None, maxsplit=-1):
@ -1129,7 +1133,6 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
rstripped = rstripped[::-1]
return ANSIString(lstripped + raw[ir1:ir2+1] + rstripped)
def lstrip(self, chars=None):
"""
Strip from the left, taking ANSI markers into account.

View file

@ -183,12 +183,13 @@ _ENCODINGS = settings.ENCODINGS
_RE_INSERT = re.compile(r"^\#INSERT (.*)", re.MULTILINE)
_RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
_RE_CMD_SPLIT = re.compile(r"^\#.*?$", re.MULTILINE)
_RE_CODE_OR_HEADER = re.compile(r"(\A|^\#CODE|^\#HEADER).*?$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)", re.MULTILINE + re.DOTALL)
_RE_CODE_OR_HEADER = re.compile(r"(\A|^\#CODE|^\#HEADER).*?$(.*?)(?=^#CODE.*?$|^#HEADER.*?$|\Z)",
re.MULTILINE + re.DOTALL)
#------------------------------------------------------------
# -------------------------------------------------------------
# Helper function
#------------------------------------------------------------
# -------------------------------------------------------------
def read_batchfile(pythonpath, file_ending='.py'):
"""
@ -212,8 +213,7 @@ def read_batchfile(pythonpath, file_ending='.py'):
"""
# find all possible absolute paths
abspaths = utils.pypath_to_realpath(pythonpath,
file_ending, settings.BASE_BATCHPROCESS_PATHS)
abspaths = utils.pypath_to_realpath(pythonpath, file_ending, settings.BASE_BATCHPROCESS_PATHS)
if not abspaths:
raise IOError
text = None
@ -237,11 +237,11 @@ def read_batchfile(pythonpath, file_ending='.py'):
return text
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Batch-command processor
#
#------------------------------------------------------------
# -------------------------------------------------------------
class BatchCommandProcessor(object):
"""
@ -271,35 +271,35 @@ class BatchCommandProcessor(object):
text = "".join(read_batchfile(pythonpath, file_ending='.ev'))
def replace_insert(match):
"Map replace entries"
"""Map replace entries"""
return "\n#".join(self.parse_file(match.group(1)))
# insert commands from inserted files
text = _RE_INSERT.sub(replace_insert, text)
#text = re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
# re.sub(r"^\#INSERT (.*?)", replace_insert, text, flags=re.MULTILINE)
# get all commands
commands = _RE_CMD_SPLIT.split(text)
#commands = re.split(r"^\#.*?$", text, flags=re.MULTILINE)
#remove eventual newline at the end of commands
# re.split(r"^\#.*?$", text, flags=re.MULTILINE)
# remove eventual newline at the end of commands
commands = [c.strip('\r\n') for c in commands]
commands = [c for c in commands if c]
return commands
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Batch-code processor
#
#------------------------------------------------------------
# -------------------------------------------------------------
def tb_filename(tb):
"Helper to get filename from traceback"
"""Helper to get filename from traceback"""
return tb.tb_frame.f_code.co_filename
def tb_iter(tb):
"Traceback iterator."
"""Traceback iterator."""
while tb is not None:
yield tb
tb = tb.tb_next
@ -341,7 +341,7 @@ class BatchCodeProcessor(object):
text = "".join(read_batchfile(pythonpath, file_ending='.py'))
def replace_insert(match):
"Run parse_file on the import before sub:ing it into this file"
"""Run parse_file on the import before sub:ing it into this file"""
path = match.group(1)
return "# batchcode insert (%s):" % path + "\n".join(self.parse_file(path))
@ -356,7 +356,7 @@ class BatchCodeProcessor(object):
code = text[istart:iend]
if mtype == "#HEADER":
headers.append(code)
else: # either #CODE or matching from start of file
else: # either #CODE or matching from start of file
codes.append(code)
# join all headers together to one
@ -365,7 +365,6 @@ class BatchCodeProcessor(object):
codes = ["%s# batchcode code:\n%s" % (header, code) for code in codes]
return codes
def code_exec(self, code, extra_environ=None, debug=False):
"""
Execute a single code block, including imports and appending
@ -406,7 +405,7 @@ class BatchCodeProcessor(object):
err = ""
for iline, line in enumerate(code.split("\n")):
if iline == lineno:
err += "\n{w%02i{n: %s" % (iline + 1, line)
err += "\n|w%02i|n: %s" % (iline + 1, line)
elif lineno - 5 < iline < lineno + 5:
err += "\n%02i: %s" % (iline + 1, line)

View file

@ -50,11 +50,11 @@ _GA = object.__getattribute__
#
# Game Object creation
#
def create_object(typeclass=None, key=None, location=None,
home=None, permissions=None, locks=None,
aliases=None, tags=None, destination=None, report_to=None, nohome=False):
def create_object(typeclass=None, key=None, location=None, home=None,
permissions=None, locks=None, aliases=None, tags=None,
destination=None, report_to=None, nohome=False):
"""
Create a new in-game object.
@ -110,26 +110,24 @@ def create_object(typeclass=None, key=None, location=None,
# create new instance
new_object = typeclass(db_key=key, db_location=location,
db_destination=destination, db_home=home,
db_typeclass_path=typeclass.path)
db_destination=destination, db_home=home,
db_typeclass_path=typeclass.path)
# store the call signature for the signal
new_object._createdict = {"key":key, "location":location, "destination":destination,
"home":home, "typeclass":typeclass.path, "permissions":permissions,
"locks":locks, "aliases":aliases, "tags": tags,
"report_to":report_to, "nohome":nohome}
new_object._createdict = dict(key=key, location=location, destination=destination, home=home,
typeclass=typeclass.path, permissions=permissions, locks=locks,
aliases=aliases, tags=tags, report_to=report_to, nohome=nohome)
# this will trigger the save signal which in turn calls the
# at_first_save hook on the typeclass, where the _createdict can be
# used.
new_object.save()
return new_object
#alias for create_object
# alias for create_object
object = create_object
#
# Script creation
#
def create_script(typeclass=None, key=None, obj=None, player=None, locks=None,
interval=None, start_delay=None, repeats=None,
@ -194,19 +192,16 @@ def create_script(typeclass=None, key=None, obj=None, player=None, locks=None,
new_script = typeclass(**kwarg)
# store the call signature for the signal
new_script._createdict = {"key":key, "obj":obj, "player":player,
"locks":locks, "interval":interval,
"start_delay":start_delay, "repeats":repeats,
"persistent":persistent, "autostart":autostart,
"report_to":report_to}
new_script._createdict = dict(key=key, obj=obj, player=player, locks=locks, interval=interval,
start_delay=start_delay, repeats=repeats, persistent=persistent,
autostart=autostart, report_to=report_to)
# this will trigger the save signal which in turn calls the
# at_first_save hook on the typeclass, where the _createdict
# can be used.
new_script.save()
return new_script
#alias
# alias
script = create_script
@ -227,6 +222,7 @@ def create_help_entry(key, entrytext, category="General", locks=None, aliases=No
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.
aliases (list of str): List of alternative (likely shorter) keynames.
Returns:
help (HelpEntry): A newly created help entry.
@ -260,10 +256,8 @@ help_entry = create_help_entry
#
# Comm system methods
#
def create_message(senderobj, message, channels=None,
receivers=None, locks=None, header=None):
def create_message(senderobj, message, channels=None, receivers=None, locks=None, header=None):
"""
Create a new communication Msg. Msgs represent a unit of
database-persistent communication between entites.
@ -325,7 +319,7 @@ def create_channel(key, aliases=None, desc=None,
key (str): This must be unique.
Kwargs:
aliases (list): List of alternative (likely shorter) keynames.
aliases (list of str): 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.
@ -346,8 +340,7 @@ def create_channel(key, aliases=None, desc=None,
new_channel = typeclass(db_key=key)
# store call signature for the signal
new_channel._createdict = {"key":key, "aliases":aliases,
"desc":desc, "locks":locks, "keep_log":keep_log}
new_channel._createdict = dict(key=key, aliases=aliases, desc=desc, locks=locks, keep_log=keep_log)
# this will trigger the save signal which in turn calls the
# at_first_save hook on the typeclass, where the _createdict can be
@ -358,11 +351,11 @@ def create_channel(key, aliases=None, desc=None,
channel = create_channel
#
# Player creation methods
#
def create_player(key, email, password,
typeclass=None,
is_superuser=False,
@ -376,7 +369,7 @@ def create_player(key, email, password,
key (str): The player's name. This should be unique.
email (str): Email on valid addr@addr.domain form. This is
technically required but if set to `None`, an email of
`dummy@dummy.com` will be used as a placeholder.
`dummy@example.com` will be used as a placeholder.
password (str): Password in cleartext.
Kwargs:
@ -411,7 +404,7 @@ def create_player(key, email, password,
# correctly when each object is recovered).
if not email:
email = "dummy@dummy.com"
email = "dummy@example.com"
if _PlayerDB.objects.filter(username__iexact=key):
raise ValueError("A Player with the name '%s' already exists." % key)
@ -426,8 +419,7 @@ def create_player(key, email, password,
is_staff=is_superuser, is_superuser=is_superuser,
last_login=now, date_joined=now)
new_player.set_password(password)
new_player._createdict = {"locks":locks, "permissions":permissions,
"report_to":report_to}
new_player._createdict = dict(locks=locks, permissions=permissions, report_to=report_to)
# saving will trigger the signal that calls the
# at_first_save hook on the typeclass, where the _createdict
# can be used.

View file

@ -33,10 +33,11 @@ from evennia.utils.utils import to_str, uses_database
from evennia.utils import logger
__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle",
"dbserialize", "dbunserialize")
"dbserialize", "dbunserialize")
PICKLE_PROTOCOL = 2
def _get_mysql_db_version():
"""
This is a helper method for specifically getting the version
@ -93,7 +94,7 @@ def _TO_DATESTRING(obj):
def _init_globals():
"Lazy importing to avoid circular import issues"
"""Lazy importing to avoid circular import issues"""
global _FROM_MODEL_MAP, _TO_MODEL_MAP, _SESSION_HANDLER
if not _FROM_MODEL_MAP:
_FROM_MODEL_MAP = defaultdict(str)
@ -110,7 +111,7 @@ def _init_globals():
def _save(method):
"method decorator that saves data to Attribute"
"""method decorator that saves data to Attribute"""
def save_wrapper(self, *args, **kwargs):
self.__doc__ = method.__doc__
ret = method(self, *args, **kwargs)
@ -127,17 +128,17 @@ class _SaverMutable(object):
will not save the updated value to the database.
"""
def __init__(self, *args, **kwargs):
"store all properties for tracking the tree"
"""store all properties for tracking the tree"""
self._parent = kwargs.pop("_parent", None)
self._db_obj = kwargs.pop("_db_obj", None)
self._data = None
def __nonzero__(self):
"Make sure to evaluate as False if empty"
"""Make sure to evaluate as False if empty"""
return bool(self._data)
def _save_tree(self):
"recursively traverse back up the tree, save when we reach the root"
"""recursively traverse back up the tree, save when we reach the root"""
if self._parent:
self._parent._save_tree()
elif self._db_obj:
@ -146,9 +147,9 @@ class _SaverMutable(object):
logger.log_err("_SaverMutable %s has no root Attribute to save to." % self)
def _convert_mutables(self, data):
"converts mutables to Saver* variants and assigns ._parent property"
"""converts mutables to Saver* variants and assigns ._parent property"""
def process_tree(item, parent):
"recursively populate the tree, storing parents"
"""recursively populate the tree, storing parents"""
dtype = type(item)
if dtype in (basestring, int, float, bool, tuple):
return item
@ -218,7 +219,6 @@ class _SaverList(_SaverMutable, MutableSequence):
return self._data.index(value, *args)
class _SaverDict(_SaverMutable, MutableMapping):
"""
A dict that stores changes to an Attribute when updated
@ -290,8 +290,10 @@ class _SaverDeque(_SaverMutable):
# maxlen property
def _getmaxlen(self):
return self._data.maxlen
def _setmaxlen(self, value):
self._data.maxlen = value
def _delmaxlen(self):
del self._data.maxlen
maxlen = property(_getmaxlen, _setmaxlen, _delmaxlen)
@ -314,7 +316,7 @@ class _SaverDeque(_SaverMutable):
#
# serialization helpers
#
def pack_dbobj(item):
"""
@ -335,7 +337,7 @@ def pack_dbobj(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
_TO_DATESTRING(obj), _GA(obj, "id")) or item
def unpack_dbobj(item):
@ -391,10 +393,11 @@ def pack_session(item):
# to be accepted as actually being a session (sessids gets
# reused all the time).
return item.conn_time and item.sessid and ('__packed_session__',
_GA(item, "sessid"),
_GA(item, "conn_time"))
_GA(item, "sessid"),
_GA(item, "conn_time"))
return None
def unpack_session(item):
"""
Check and convert internal representations back to Sessions.
@ -419,7 +422,7 @@ def unpack_session(item):
#
# Access methods
#
def to_pickle(data):
"""
@ -437,7 +440,7 @@ def to_pickle(data):
"""
def process_item(item):
"Recursive processor and identification of data"
"""Recursive processor and identification of data"""
dtype = type(item)
if dtype in (basestring, int, float, bool):
return item
@ -466,7 +469,7 @@ def to_pickle(data):
return process_item(data)
#@transaction.autocommit
# @transaction.autocommit
def from_pickle(data, db_obj=None):
"""
This should be fed a just de-pickled data object. It will be converted back
@ -489,7 +492,7 @@ def from_pickle(data, db_obj=None):
"""
def process_item(item):
"Recursive processor and identification of data"
"""Recursive processor and identification of data"""
dtype = type(item)
if dtype in (basestring, int, float, bool):
return item
@ -518,7 +521,7 @@ def from_pickle(data, db_obj=None):
return item
def process_tree(item, parent):
"Recursive processor, building a parent-tree from iterable data"
"""Recursive processor, building a parent-tree from iterable data"""
dtype = type(item)
if dtype in (basestring, int, float, bool):
return item
@ -534,7 +537,7 @@ def from_pickle(data, db_obj=None):
elif dtype == dict:
dat = _SaverDict(_parent=parent)
dat._data.update((process_item(key), process_tree(val, dat))
for key, val in item.items())
for key, val in item.items())
return dat
elif dtype == set:
dat = _SaverSet(_parent=parent)
@ -543,7 +546,7 @@ def from_pickle(data, db_obj=None):
elif dtype == OrderedDict:
dat = _SaverOrderedDict(_parent=parent)
dat._data.update((process_item(key), process_tree(val, dat))
for key, val in item.items())
for key, val in item.items())
return dat
elif dtype == deque:
dat = _SaverDeque(_parent=parent)
@ -571,7 +574,7 @@ def from_pickle(data, db_obj=None):
elif dtype == dict:
dat = _SaverDict(_db_obj=db_obj)
dat._data.update((process_item(key), process_tree(val, dat))
for key, val in data.items())
for key, val in data.items())
return dat
elif dtype == set:
dat = _SaverSet(_db_obj=db_obj)
@ -580,7 +583,7 @@ def from_pickle(data, db_obj=None):
elif dtype == OrderedDict:
dat = _SaverOrderedDict(_db_obj=db_obj)
dat._data.update((process_item(key), process_tree(val, dat))
for key, val in data.items())
for key, val in data.items())
return dat
elif dtype == deque:
dat = _SaverDeque(_db_obj=db_obj)
@ -590,20 +593,20 @@ def from_pickle(data, db_obj=None):
def do_pickle(data):
"Perform pickle to string"
"""Perform pickle to string"""
return to_str(dumps(data, protocol=PICKLE_PROTOCOL))
def do_unpickle(data):
"Retrieve pickle from pickled string"
"""Retrieve pickle from pickled string"""
return loads(to_str(data))
def dbserialize(data):
"Serialize to pickled form in one step"
"""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."
"""Un-serialize in one step. See from_pickle for help db_obj."""
return from_pickle(do_unpickle(data), db_obj=db_obj)

View file

@ -60,14 +60,13 @@ _RE_GROUP = re.compile(r"\".*?\"|\'.*?\'|\S*")
# use NAWS in the future?
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
#------------------------------------------------------------
# -------------------------------------------------------------
#
# texts
#
#------------------------------------------------------------
# -------------------------------------------------------------
_HELP_TEXT = \
"""
_HELP_TEXT = """
<txt> - any non-command is appended to the end of the buffer.
: <l> - view buffer or only line(s) <l>
:: <l> - raw-view buffer or only line(s) <l>
@ -105,31 +104,27 @@ _HELP_TEXT = \
:echo - turn echoing of the input on/off (helpful for some clients)
"""
_HELP_LEGEND = \
"""
_HELP_LEGEND = """
Legend:
<l> - line number, like '5' or range, like '3:7'.
<w> - a single word, or multiple words with quotes around them.
<txt> - longer string, usually not needing quotes.
"""
_HELP_CODE = \
"""
_HELP_CODE = """
:! - Execute code buffer without saving
:< - Decrease the level of automatic indentation for the next lines
:> - Increase the level of automatic indentation for the next lines
:= - Switch automatic indentation on/off
""".lstrip("\n")
_ERROR_LOADFUNC = \
"""
_ERROR_LOADFUNC = """
{error}
|rBuffer load function error. Could not load initial data.|n
"""
_ERROR_SAVEFUNC = \
"""
_ERROR_SAVEFUNC = """
{error}
|rSave function returned an error. Buffer not saved.|n
@ -140,15 +135,13 @@ _ERROR_NO_SAVEFUNC = "|rNo save function defined. Buffer cannot be saved.|n"
_MSG_SAVE_NO_CHANGE = "No changes need saving"
_DEFAULT_NO_QUITFUNC = "Exited editor."
_ERROR_QUITFUNC = \
"""
_ERROR_QUITFUNC = """
{error}
|rQuit function gave an error. Skipping.|n
"""
_ERROR_PERSISTENT_SAVING = \
"""
_ERROR_PERSISTENT_SAVING = """
{error}
|rThe editor state could not be saved for persistent mode. Switching
@ -157,9 +150,9 @@ an eventual server reload - so save often!)|n
"""
_TRACE_PERSISTENT_SAVING = \
"EvEditor persistent-mode error. Commonly, this is because one or " \
"more of the EvEditor callbacks could not be pickled, for example " \
"because it's a class method or is defined inside another function."
"EvEditor persistent-mode error. Commonly, this is because one or " \
"more of the EvEditor callbacks could not be pickled, for example " \
"because it's a class method or is defined inside another function."
_MSG_NO_UNDO = "Nothing to undo."
@ -167,11 +160,12 @@ _MSG_NO_REDO = "Nothing to redo."
_MSG_UNDO = "Undid one step."
_MSG_REDO = "Redid one step."
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Handle yes/no quit question
#
#------------------------------------------------------------
# -------------------------------------------------------------
class CmdSaveYesNo(Command):
"""
@ -185,7 +179,7 @@ class CmdSaveYesNo(Command):
help_cateogory = "LineEditor"
def func(self):
"Implement the yes/no choice."
"""Implement the yes/no choice."""
# this is only called from inside the lineeditor
# so caller.ndb._lineditor must be set.
@ -200,21 +194,21 @@ class CmdSaveYesNo(Command):
class SaveYesNoCmdSet(CmdSet):
"Stores the yesno question"
"""Stores the yesno question"""
key = "quitsave_yesno"
priority = 1
mergetype = "Replace"
def at_cmdset_creation(self):
"at cmdset creation"
"""at cmdset creation"""
self.add(CmdSaveYesNo())
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Editor commands
#
#------------------------------------------------------------
# -------------------------------------------------------------
class CmdEditorBase(Command):
"""
@ -239,7 +233,6 @@ class CmdEditorBase(Command):
txt - extra text (string), could be encased in quotes.
"""
linebuffer = []
editor = self.caller.ndb._eveditor
if not editor:
# this will completely replace the editor
@ -297,11 +290,7 @@ class CmdEditorBase(Command):
arglist = arglist[1:]
# nicer output formatting of the line range.
lstr = ""
if not linerange or lstart + 1 == lend:
lstr = "line %i" % (lstart + 1)
else:
lstr = "lines %i-%i" % (lstart + 1, lend)
lstr = "line %i" % (lstart + 1) if not linerange or lstart + 1 == lend else "lines %i-%i" % (lstart + 1, lend)
# arg1 and arg2 is whatever arguments. Line numbers or -ranges are
# never included here.
@ -369,25 +358,14 @@ class CmdLineInput(CmdEditorBase):
"""
caller = self.caller
editor = caller.ndb._eveditor
buf = editor.get_buffer()
# add a line of text to buffer
line = self.raw_string.strip("\r\n")
if not editor._codefunc:
if not buf:
buf = line
else:
buf = buf + "\n%s" % line
else:
if editor._codefunc and editor._indent >= 0:
# if automatic indentation is active, add spaces
if editor._indent >= 0:
line = editor.deduce_indent(line, buf)
if not buf:
buf = line
else:
buf = buf + "\n%s" % line
line = editor.deduce_indent(line, buf)
buf = line if not buf else buf + "\n%s" % line
self.editor.update_buffer(buf)
if self.editor._echo_mode:
# need to do it here or we will be off one line
@ -398,11 +376,10 @@ class CmdLineInput(CmdEditorBase):
if indent < 0:
indent = "off"
self.caller.msg("{b%02i|{n ({g%s{n) %s" % (
self.caller.msg("|b%02i|||n (|g%s|n) %s" % (
cline, indent, line))
else:
self.caller.msg("{b%02i|{n %s" % (cline, self.args))
self.caller.msg("|b%02i|||n %s" % (cline, self.args))
class CmdEditorGroup(CmdEditorBase):
@ -410,7 +387,7 @@ class CmdEditorGroup(CmdEditorBase):
Commands for the editor
"""
key = ":editor_command_group"
aliases = [":","::", ":::", ":h", ":w", ":wq", ":q", ":q!", ":u", ":uu", ":UU",
aliases = [":", "::", ":::", ":h", ":w", ":wq", ":q", ":q!", ":u", ":uu", ":UU",
":dd", ":dw", ":DD", ":y", ":x", ":p", ":i", ":j",
":r", ":I", ":A", ":s", ":S", ":f", ":fi", ":fd", ":echo",
":!", ":<", ":>", ":="]
@ -443,9 +420,9 @@ class CmdEditorGroup(CmdEditorBase):
buf = linebuffer[lstart:lend]
editor.display_buffer(buf=buf,
offset=lstart,
linenums=False, options={"raw":True})
linenums=False, options={"raw": True})
else:
editor.display_buffer(linenums=False, options={"raw":True})
editor.display_buffer(linenums=False, options={"raw": True})
elif cmd == ":::":
# Insert single colon alone on a line
editor.update_buffer(editor.buffer + "\n:")
@ -485,7 +462,7 @@ class CmdEditorGroup(CmdEditorBase):
# :dd <l> - delete line <l>
buf = linebuffer[:lstart] + linebuffer[lend:]
editor.update_buffer(buf)
caller.msg("Deleted %s." % (self.lstr))
caller.msg("Deleted %s." % self.lstr)
elif cmd == ":dw":
# :dw <w> - delete word in entire buffer
# :dw <l> <w> delete word only on line(s) <l>
@ -556,7 +533,8 @@ class CmdEditorGroup(CmdEditorBase):
if not self.raw_string and not editor._codefunc:
caller.msg("You need to enter text to insert.")
else:
buf = linebuffer[:lstart] + ["%s%s" % (self.args, line) for line in linebuffer[lstart:lend]] + linebuffer[lend:]
buf = linebuffer[:lstart] + ["%s%s" % (self.args, line)
for line in linebuffer[lstart:lend]] + linebuffer[lend:]
editor.update_buffer(buf)
caller.msg("Inserted text at beginning of %s." % self.lstr)
elif cmd == ":A":
@ -564,7 +542,8 @@ class CmdEditorGroup(CmdEditorBase):
if not self.args:
caller.msg("You need to enter text to append.")
else:
buf = linebuffer[:lstart] + ["%s%s" % (line, self.args) for line in linebuffer[lstart:lend]] + linebuffer[lend:]
buf = linebuffer[:lstart] + ["%s%s" % (line, self.args)
for line in linebuffer[lstart:lend]] + linebuffer[lend:]
editor.update_buffer(buf)
caller.msg("Appended text to end of %s." % self.lstr)
elif cmd == ":s":
@ -576,7 +555,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange:
lstart = 0
lend = self.cline + 1
caller.msg("Search-replaced %s -> %s for lines %i-%i." % (self.arg1, self.arg2, lstart + 1 , lend))
caller.msg("Search-replaced %s -> %s for lines %i-%i." % (self.arg1, self.arg2, lstart + 1, lend))
else:
caller.msg("Search-replaced %s -> %s for %s." % (self.arg1, self.arg2, self.lstr))
sarea = "\n".join(linebuffer[lstart:lend])
@ -585,7 +564,8 @@ class CmdEditorGroup(CmdEditorBase):
regarg = self.arg1.strip("\'").strip('\"')
if " " in regarg:
regarg = regarg.replace(" ", " +")
sarea = re.sub(regex % (regarg, regarg, regarg, regarg, regarg), self.arg2.strip("\'").strip('\"'), sarea, re.MULTILINE)
sarea = re.sub(regex % (regarg, regarg, regarg, regarg, regarg), self.arg2.strip("\'").strip('\"'),
sarea, re.MULTILINE)
buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:]
editor.update_buffer(buf)
elif cmd == ":f":
@ -594,7 +574,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange:
lstart = 0
lend = self.cline + 1
caller.msg("Flood filled lines %i-%i." % (lstart + 1 , lend))
caller.msg("Flood filled lines %i-%i." % (lstart + 1, lend))
else:
caller.msg("Flood filled %s." % self.lstr)
fbuf = "\n".join(linebuffer[lstart:lend])
@ -605,7 +585,7 @@ class CmdEditorGroup(CmdEditorBase):
# :f <l> <w> justify buffer of <l> with <w> as align (one of
# f(ull), c(enter), r(ight) or l(left). Default is full.
align_map = {"full": "f", "f": "f", "center": "c", "c": "c",
"right": "r", "r": "r", "left": "l", "l": "l"}
"right": "r", "r": "r", "left": "l", "l": "l"}
align_name = {"f": "Full", "c": "Center", "l": "Left", "r": "Right"}
width = _DEFAULT_WIDTH
if self.arg1 and self.arg1.lower() not in align_map:
@ -628,7 +608,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange:
lstart = 0
lend = self.cline + 1
caller.msg("Indented lines %i-%i." % (lstart + 1 , lend))
caller.msg("Indented lines %i-%i." % (lstart + 1, lend))
else:
caller.msg("Indented %s." % self.lstr)
fbuf = [indent + line for line in linebuffer[lstart:lend]]
@ -639,7 +619,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange:
lstart = 0
lend = self.cline + 1
caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1 , lend))
caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1, lend))
else:
caller.msg("Removed left margin (dedented) %s." % self.lstr)
fbuf = "\n".join(linebuffer[lstart:lend])
@ -693,18 +673,20 @@ class CmdEditorGroup(CmdEditorBase):
class EvEditorCmdSet(CmdSet):
"CmdSet for the editor commands"
"""CmdSet for the editor commands"""
key = "editorcmdset"
mergetype = "Replace"
def at_cmdset_creation(self):
self.add(CmdLineInput())
self.add(CmdEditorGroup())
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Main Editor object
#
#------------------------------------------------------------
# -------------------------------------------------------------
class EvEditor(object):
"""
@ -790,12 +772,10 @@ class EvEditor(object):
if persistent:
# save in tuple {kwargs, other options}
try:
caller.attributes.add("_eveditor_saved",(
{"loadfunc":loadfunc, "savefunc": savefunc,
"quitfunc": quitfunc, "codefunc": codefunc,
"key": key, "persistent": persistent},
{"_pristine_buffer": self._pristine_buffer,
"_sep": self._sep}))
caller.attributes.add("_eveditor_saved", (
dict(loadfunc=loadfunc, savefunc=savefunc, quitfunc=quitfunc,
codefunc=codefunc, key=key, persistent=persistent),
dict(_pristine_buffer=self._pristine_buffer, _sep=self._sep)))
caller.attributes.add("_eveditor_buffer_temp", (self._buffer, self._undo_buffer))
caller.attributes.add("_eveditor_unsaved", False)
caller.attributes.add("_eveditor_indent", 0)
@ -923,7 +903,7 @@ class EvEditor(object):
self._undo_buffer = self._undo_buffer[:self._undo_pos + 1] + [self._buffer]
self._undo_pos = len(self._undo_buffer) - 1
def display_buffer(self, buf=None, offset=0, linenums=True, options={"raw":False}):
def display_buffer(self, buf=None, offset=0, linenums=True, options={"raw": False}):
"""
This displays the line editor buffer, or selected parts of it.
@ -933,7 +913,7 @@ class EvEditor(object):
`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
options: raw (bool, optional): Tell protocol to not parse
formatting information.
"""
@ -949,10 +929,10 @@ class EvEditor(object):
sep = self._sep
header = "|n" + sep * 10 + "Line Editor [%s]" % self._key + sep * (_DEFAULT_WIDTH-20-len(self._key))
footer = "|n" + sep * 10 + "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) \
+ sep * 12 + "(:h for help)" + sep * 28
footer = "|n" + sep * 10 +\
"[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) + sep * 12 + "(:h for help)" + sep * 28
if linenums:
main = "\n".join("{b%02i|{n %s" % (iline + 1 + offset, line) for iline, line in enumerate(lines))
main = "\n".join("|b%02i|||n %s" % (iline + 1 + offset, line) for iline, line in enumerate(lines))
else:
main = "\n".join(lines)
string = "%s\n%s\n%s" % (header, main, footer)
@ -1018,6 +998,7 @@ class EvEditor(object):
self._indent += 1
if self._persistent:
self._caller.attributes.add("_eveditor_indent", self._indent)
def swap_autoindent(self):
"""Swap automatic indentation on or off."""
if self._codefunc:

View file

@ -18,12 +18,12 @@ Example usage:
Where `caller` is the Object to use the menu on - it will get a new
cmdset while using the Menu. The menu_module_path is the python path
to a python module containing function defintions. By adjusting the
to a python module containing function definitions. By adjusting the
keyword options of the Menu() initialization call you can start the
menu at different places in the menu definition file, adjust if the
menu command should overload the normal commands or not, etc.
The `perstent` keyword will make the menu survive a server reboot.
The `persistent` keyword will make the menu survive a server reboot.
It is `False` by default. Note that if using persistent mode, every
node and callback in the menu must be possible to be *pickled*, this
excludes e.g. callables that are class methods or functions defined
@ -31,7 +31,7 @@ dynamically or as part of another function. In non-persistent mode
no such restrictions exist.
The menu is defined in a module (this can be the same module as the
command definition too) with function defintions:
command definition too) with function definitions:
```python
@ -181,8 +181,7 @@ _HELP_NO_OPTIONS = _("Commands: help, quit")
_HELP_NO_OPTIONS_NO_QUIT = _("Commands: help")
_HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.")
_ERROR_PERSISTENT_SAVING = \
"""
_ERROR_PERSISTENT_SAVING = """
{error}
|rThe menu state could not be saved for persistent mode. Switching
@ -190,10 +189,9 @@ to non-persistent mode (which means the menu session won't survive
an eventual server reload).|n
"""
_TRACE_PERSISTENT_SAVING = \
"EvMenu persistent-mode error. Commonly, this is because one or " \
"more of the EvEditor callbacks could not be pickled, for example " \
"because it's a class method or is defined inside another function."
_TRACE_PERSISTENT_SAVING = "EvMenu persistent-mode error. Commonly, this is because one or " \
"more of the EvEditor callbacks could not be pickled, for example " \
"because it's a class method or is defined inside another function."
class EvMenuError(RuntimeError):
@ -203,11 +201,12 @@ class EvMenuError(RuntimeError):
"""
pass
#------------------------------------------------------------
# -------------------------------------------------------------
#
# Menu command and command set
#
#------------------------------------------------------------
# -------------------------------------------------------------
class CmdEvMenuNode(Command):
"""
@ -227,7 +226,11 @@ class CmdEvMenuNode(Command):
# this will re-start a completely new evmenu call.
saved_options = caller.attributes.get("_menutree_saved")
if saved_options:
startnode, startnode_input = caller.attributes.get("_menutree_saved_startnode")
startnode_tuple = caller.attributes.get("_menutree_saved_startnode")
try:
startnode, startnode_input = startnode_tuple
except ValueError: # old form of startnode store
startnode, startnode_input = startnode_tuple, ""
if startnode:
saved_options[2]["startnode"] = startnode
saved_options[2]["startnode_input"] = startnode_input
@ -254,7 +257,7 @@ class CmdEvMenuNode(Command):
menu = caller.ndb._menutree
if not menu:
# can't restore from a session
err = "Menu object not found as %s.ndb._menutree!" % (orig_caller)
err = "Menu object not found as %s.ndb._menutree!" % orig_caller
orig_caller.msg(err) # don't give the session as a kwarg here, direct to original
raise EvMenuError(err)
# we must do this after the caller with the menui has been correctly identified since it
@ -287,7 +290,8 @@ class EvMenuCmdSet(CmdSet):
#
# Menu main class
#
#------------------------------------------------------------
# -------------------------------------------------------------
class EvMenu(object):
"""
@ -436,7 +440,17 @@ class EvMenu(object):
"persistent": persistent}
calldict.update(kwargs)
try:
caller.attributes.add("_menutree_saved", ( self.__class__, (menudata, ), calldict ))
caller.attributes.add("_menutree_saved",
((menudata, ),
{"startnode": startnode,
"cmdset_mergetype": cmdset_mergetype,
"cmdset_priority": cmdset_priority,
"auto_quit": auto_quit, "auto_look": auto_look, "auto_help": auto_help,
"cmd_on_exit": cmd_on_exit,
"nodetext_formatter": nodetext_formatter,
"options_formatter": options_formatter,
"node_formatter": node_formatter, "input_parser": input_parser,
"persistent": persistent, }))
caller.attributes.add("_menutree_saved_startnode", (startnode, startnode_input))
except Exception as err:
caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err), session=self._session)
@ -505,7 +519,6 @@ class EvMenu(object):
# format the entire node
return self.node_formatter(nodetext, optionstext)
def _execute_node(self, nodename, raw_string):
"""
Execute a node.
@ -542,15 +555,12 @@ class EvMenu(object):
raise
return nodetext, options
def display_nodetext(self):
self.caller.msg(self.nodetext, session=self._session)
def display_helptext(self):
self.caller.msg(self.helptext, session=self._session)
def callback_goto(self, callback, goto, raw_string):
"""
Call callback and goto in sequence.
@ -860,25 +870,29 @@ class CmdGetInput(Command):
aliases = _CMD_NOINPUT
def func(self):
"This is called when user enters anything."
"""This is called when user enters anything."""
caller = self.caller
callback = caller.ndb._getinput._callback
if not callback:
# this can be happen if called from a player-command when IC
caller = self.player
callback = caller.ndb._getinput._callback
if not callback:
raise RuntimeError("No input callback found.")
try:
getinput = caller.ndb._getinput
if not getinput and hasattr(caller, "player"):
getinput = caller.player.ndb._getinput
caller = caller.player
callback = getinput._callback
caller.ndb._getinput._session = self.session
prompt = caller.ndb._getinput._prompt
result = self.raw_string.strip() # we strip the ending line break caused by sending
caller.ndb._getinput._session = self.session
prompt = caller.ndb._getinput._prompt
result = self.raw_string.strip() # we strip the ending line break caused by sending
ok = not callback(caller, prompt, result)
if ok:
# only clear the state if the callback does not return
# anything
del caller.ndb._getinput
ok = not callback(caller, prompt, result)
if ok:
# only clear the state if the callback does not return
# anything
del caller.ndb._getinput
caller.cmdset.remove(InputCmdSet)
except Exception:
# make sure to clean up cmdset if something goes wrong
caller.msg("|rError in get_input. Choice not confirmed (report to admin)|n")
logger.log_trace("Error in get_input")
caller.cmdset.remove(InputCmdSet)
@ -894,12 +908,12 @@ class InputCmdSet(CmdSet):
no_channels = False
def at_cmdset_creation(self):
"called once at creation"
"""called once at creation"""
self.add(CmdGetInput())
class _Prompt(object):
"Dummy holder"
"""Dummy holder"""
pass
@ -958,11 +972,11 @@ def get_input(caller, prompt, callback, session=None):
caller.msg(prompt, session=session)
#------------------------------------------------------------
# -------------------------------------------------------------
#
# test menu strucure and testing command
#
#------------------------------------------------------------
# -------------------------------------------------------------
def test_start_node(caller):
menu = caller.ndb._menutree
@ -978,17 +992,17 @@ def test_start_node(caller):
The menu was initialized with two variables: %s and %s.
""" % (menu.testval, menu.testval2)
options = ({"key": ("{yS{net", "s"),
options = ({"key": ("|yS|net", "s"),
"desc": "Set an attribute on yourself.",
"exec": lambda caller: caller.attributes.add("menuattrtest", "Test value"),
"goto": "test_set_node"},
{"key": ("{yL{nook", "l"),
{"key": ("|yL|nook", "l"),
"desc": "Look and see a custom message.",
"goto": "test_look_node"},
{"key": ("{yV{niew", "v"),
{"key": ("|yV|niew", "v"),
"desc": "View your own name",
"goto": "test_view_node"},
{"key": ("{yQ{nuit", "quit", "q", "Q"),
{"key": ("|yQ|nuit", "quit", "q", "Q"),
"desc": "Quit this menu example.",
"goto": "test_end_node"},
{"key": "_default",
@ -998,16 +1012,17 @@ def test_start_node(caller):
def test_look_node(caller):
text = ""
options = {"key": ("{yL{nook", "l"),
options = {"key": ("|yL|nook", "l"),
"desc": "Go back to the previous menu.",
"goto": "test_start_node"}
return text, options
def test_set_node(caller):
text = ("""
The attribute 'menuattrtest' was set to
{w%s{n
|w%s|n
(check it with examine after quitting the menu).
@ -1015,9 +1030,8 @@ def test_set_node(caller):
string "_default", meaning it will catch any input, in this case
to return to the main menu. So you can e.g. press <return> to go
back now.
""" % caller.db.menuattrtest,
# optional help text for this node
"""
""" % caller.db.menuattrtest, # optional help text for this node
"""
This is the help entry for this node. It is created by returning
the node text as a tuple - the second string in that tuple will be
used as the help text.
@ -1031,7 +1045,7 @@ def test_set_node(caller):
def test_view_node(caller):
text = """
Your name is {g%s{n!
Your name is |g%s|n!
click |lclook|lthere|le to trigger a look command under MXP.
This node's option has no explicit key (nor the "_default" key
@ -1044,11 +1058,11 @@ def test_view_node(caller):
return text, options
def test_displayinput_node(caller, raw_string):
def test_displayinput_node(caller, raw_string):
text = """
You entered the text:
"{w%s{n"
"|w%s|n"
... which could now be handled or stored here in some way if this
was not just an example.
@ -1059,7 +1073,7 @@ def test_displayinput_node(caller, raw_string):
to the start node.
""" % raw_string
options = {"key": "_default",
"goto": "test_start_node"}
"goto": "test_start_node"}
return text, options
@ -1089,5 +1103,5 @@ class CmdTestMenu(Command):
self.caller.msg("Usage: testmenu menumodule")
return
# start menu
EvMenu(self.caller, self.args.strip(), startnode="test_start_node", persistent=True, cmdset_mergetype="Replace",
testval="val", testval2="val2")
EvMenu(self.caller, self.args.strip(), startnode="test_start_node", persistent=True,
cmdset_mergetype="Replace", testval="val", testval2="val2")

View file

@ -181,7 +181,7 @@ class EvMore(object):
lines.append(line)
# always limit number of chars to 10 000 per page
height = min(10000 // width, height)
height = min(10000 // max(1, width), height)
self._pages = ["\n".join(lines[i:i+height]) for i in range(0, len(lines), height)]
self._npages = len(self._pages)

View file

@ -126,6 +126,7 @@ from evennia.utils.ansi import ANSIString
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
def _to_ansi(obj):
"""
convert to ANSIString.
@ -142,6 +143,8 @@ def _to_ansi(obj):
_unicode = unicode
_whitespace = '\t\n\x0b\x0c\r '
class ANSITextWrapper(TextWrapper):
"""
This is a wrapper work class for handling strings with ANSI tags
@ -158,8 +161,8 @@ class ANSITextWrapper(TextWrapper):
becomes " foo bar baz".
"""
return text
##TODO: Ignore expand_tabs/replace_whitespace until ANSISTring handles them.
## - don't remove this code. /Griatch
# TODO: Ignore expand_tabs/replace_whitespace until ANSIString handles them.
# - don't remove this code. /Griatch
# if self.expand_tabs:
# text = text.expandtabs()
# if self.replace_whitespace:
@ -169,7 +172,6 @@ class ANSITextWrapper(TextWrapper):
# text = text.translate(self.unicode_whitespace_trans)
# return text
def _split(self, text):
"""_split(text : string) -> [string]
@ -289,6 +291,7 @@ def wrap(text, width=_DEFAULT_WIDTH, **kwargs):
w = ANSITextWrapper(width=width, **kwargs)
return w.wrap(text)
def fill(text, width=_DEFAULT_WIDTH, **kwargs):
"""Fill a single paragraph of text, returning a new string.
@ -311,6 +314,7 @@ def fill(text, width=_DEFAULT_WIDTH, **kwargs):
# EvCell class (see further down for the EvTable itself)
class EvCell(object):
"""
Holds a single data cell for the table. A cell has a certain width
@ -384,7 +388,7 @@ class EvCell(object):
padwidth = int(padwidth) if padwidth is not None else None
self.pad_left = int(kwargs.get("pad_left", padwidth if padwidth is not None else 1))
self.pad_right = int(kwargs.get("pad_right", padwidth if padwidth is not None else 1))
self.pad_top = int( kwargs.get("pad_top", padwidth if padwidth is not None else 0))
self.pad_top = int(kwargs.get("pad_top", padwidth if padwidth is not None else 0))
self.pad_bottom = int(kwargs.get("pad_bottom", padwidth if padwidth is not None else 0))
self.enforce_size = kwargs.get("enforce_size", False)
@ -429,7 +433,7 @@ class EvCell(object):
self.align = kwargs.get("align", "l")
self.valign = kwargs.get("valign", "c")
#self.data = self._split_lines(unicode(data))
# self.data = self._split_lines(unicode(data))
self.data = self._split_lines(_to_ansi(data))
self.raw_width = max(m_len(line) for line in self.data)
self.raw_height = len(self.data)
@ -442,20 +446,20 @@ class EvCell(object):
if "width" in kwargs:
width = kwargs.pop("width")
self.width = width - self.pad_left - self.pad_right - self.border_left - self.border_right
if self.width <= 0 and self.raw_width > 0:
if self.width <= 0 < self.raw_width:
raise Exception("Cell width too small - no space for data.")
else:
self.width = self.raw_width
if "height" in kwargs:
height = kwargs.pop("height")
self.height = height - self.pad_top - self.pad_bottom - self.border_top - self.border_bottom
if self.height <= 0 and self.raw_height > 0:
if self.height <= 0 < self.raw_height:
raise Exception("Cell height too small - no space for data.")
else:
self.height = self.raw_height
# prepare data
#self.formatted = self._reformat()
# self.formatted = self._reformat()
def _crop(self, text, width):
"""
@ -512,8 +516,8 @@ class EvCell(object):
if 0 < width < m_len(line):
# replace_whitespace=False, expand_tabs=False is a
# fix for ANSIString not supporting expand_tabs/translate
adjusted_data.extend([ANSIString(part + ANSIString("{n"))
for part in wrap(line, width=width, drop_whitespace=False)])
adjusted_data.extend([ANSIString(part + ANSIString("|n"))
for part in wrap(line, width=width, drop_whitespace=False)])
else:
adjusted_data.append(line)
if self.enforce_size:
@ -526,7 +530,7 @@ class EvCell(object):
adjusted_data[-1] = adjusted_data[-1][:-2] + ".."
elif excess < 0:
# too few lines. Fill to height.
adjusted_data.extend(["" for i in range(excess)])
adjusted_data.extend(["" for _ in range(excess)])
return adjusted_data
@ -577,11 +581,14 @@ class EvCell(object):
hfill_char = self.hfill_char
width = self.width
if align == "l":
lines= [(line.lstrip(" ") + " " if line.startswith(" ") and not line.startswith(" ") else line) + hfill_char * (width - m_len(line)) for line in data]
lines = [(line.lstrip(" ") + " " if line.startswith(" ") and not line.startswith(" ")
else line) + hfill_char * (width - m_len(line)) for line in data]
return lines
elif align == "r":
return [hfill_char * (width - m_len(line)) + (" " + line.rstrip(" ") if line.endswith(" ") and not line.endswith(" ") else line) for line in data]
else: # center, 'c'
return [hfill_char * (width - m_len(line)) + (" " + line.rstrip(" ")
if line.endswith(" ") and not line.endswith(" ")
else line) for line in data]
else: # center, 'c'
return [self._center(line, self.width, self.hfill_char) for line in data]
def _valign(self, data):
@ -605,11 +612,11 @@ class EvCell(object):
return data
# only care if we need to add new lines
if valign == 't':
return data + [padline for i in range(excess)]
return data + [padline for _ in range(excess)]
elif valign == 'b':
return [padline for i in range(excess)] + data
else: # center
narrowside = [padline for i in range(excess // 2)]
return [padline for _ in range(excess)] + data
else: # center
narrowside = [padline for _ in range(excess // 2)]
widerside = narrowside + [padline]
if excess % 2:
# uneven padding
@ -635,8 +642,8 @@ class EvCell(object):
left = self.hpad_char * self.pad_left
right = self.hpad_char * self.pad_right
vfill = (self.width + self.pad_left + self.pad_right) * self.vpad_char
top = [vfill for i in range(self.pad_top)]
bottom = [vfill for i in range(self.pad_bottom)]
top = [vfill for _ in range(self.pad_top)]
bottom = [vfill for _ in range(self.pad_bottom)]
return top + [left + line + right for line in data] + bottom
def _border(self, data):
@ -654,18 +661,17 @@ class EvCell(object):
left = self.border_left_char * self.border_left + ANSIString('|n')
right = ANSIString('|n') + self.border_right_char * self.border_right
cwidth = self.width + self.pad_left + self.pad_right + \
max(0,self.border_left-1) + max(0, self.border_right-1)
cwidth = self.width + self.pad_left + self.pad_right + max(0, self.border_left-1) + max(0, self.border_right-1)
vfill = self.corner_top_left_char if left else ""
vfill += cwidth * self.border_top_char
vfill += self.corner_top_right_char if right else ""
top = [vfill for i in range(self.border_top)]
top = [vfill for _ in range(self.border_top)]
vfill = self.corner_bottom_left_char if left else ""
vfill += cwidth * self.border_bottom_char
vfill += self.corner_bottom_right_char if right else ""
bottom = [vfill for i in range(self.border_bottom)]
bottom = [vfill for _ in range(self.border_bottom)]
return top + [left + line + right for line in data] + bottom
@ -699,7 +705,7 @@ class EvCell(object):
natural_height (int): Height of cell.
"""
return len(self.formatted) #if self.formatted else 0
return len(self.formatted) # if self.formatted else 0
def get_width(self):
"""
@ -709,7 +715,7 @@ class EvCell(object):
natural_width (int): Width of cell.
"""
return m_len(self.formatted[0]) #if self.formatted else 0
return m_len(self.formatted[0]) # if self.formatted else 0
def replace_data(self, data, **kwargs):
"""
@ -723,7 +729,7 @@ class EvCell(object):
`EvCell.__init__`.
"""
#self.data = self._split_lines(unicode(data))
# self.data = self._split_lines(unicode(data))
self.data = self._split_lines(_to_ansi(data))
self.raw_width = max(m_len(line) for line in self.data)
self.raw_height = len(self.data)
@ -746,7 +752,7 @@ class EvCell(object):
padwidth = int(padwidth) if padwidth is not None else None
self.pad_left = int(kwargs.pop("pad_left", padwidth if padwidth is not None else self.pad_left))
self.pad_right = int(kwargs.pop("pad_right", padwidth if padwidth is not None else self.pad_right))
self.pad_top = int( kwargs.pop("pad_top", padwidth if padwidth is not None else self.pad_top))
self.pad_top = int(kwargs.pop("pad_top", padwidth if padwidth is not None else self.pad_top))
self.pad_bottom = int(kwargs.pop("pad_bottom", padwidth if padwidth is not None else self.pad_bottom))
self.enforce_size = kwargs.get("enforce_size", False)
@ -764,22 +770,34 @@ class EvCell(object):
self.vfill_char = vfill_char[0] if vfill_char else self.vfill_char
borderwidth = kwargs.get("border_width", None)
self.border_left = kwargs.pop("border_left", borderwidth if borderwidth is not None else self.border_left)
self.border_right = kwargs.pop("border_right", borderwidth if borderwidth is not None else self.border_right)
self.border_top = kwargs.pop("border_top", borderwidth if borderwidth is not None else self.border_top)
self.border_bottom = kwargs.pop("border_bottom", borderwidth if borderwidth is not None else self.border_bottom)
self.border_left = kwargs.pop(
"border_left", borderwidth if borderwidth is not None else self.border_left)
self.border_right = kwargs.pop(
"border_right", borderwidth if borderwidth is not None else self.border_right)
self.border_top = kwargs.pop(
"border_top", borderwidth if borderwidth is not None else self.border_top)
self.border_bottom = kwargs.pop(
"border_bottom", borderwidth if borderwidth is not None else self.border_bottom)
borderchar = kwargs.get("border_char", None)
self.border_left_char = kwargs.pop("border_left_char", borderchar if borderchar else self.border_left_char)
self.border_right_char = kwargs.pop("border_right_char", borderchar if borderchar else self.border_right_char)
self.border_top_char = kwargs.pop("border_topchar", borderchar if borderchar else self.border_top_char)
self.border_bottom_char = kwargs.pop("border_bottom_char", borderchar if borderchar else self.border_bottom_char)
self.border_left_char = kwargs.pop(
"border_left_char", borderchar if borderchar else self.border_left_char)
self.border_right_char = kwargs.pop(
"border_right_char", borderchar if borderchar else self.border_right_char)
self.border_top_char = kwargs.pop(
"border_topchar", borderchar if borderchar else self.border_top_char)
self.border_bottom_char = kwargs.pop(
"border_bottom_char", borderchar if borderchar else self.border_bottom_char)
corner_char = kwargs.get("corner_char", None)
self.corner_top_left_char = kwargs.pop("corner_top_left", corner_char if corner_char is not None else self.corner_top_left_char)
self.corner_top_right_char = kwargs.pop("corner_top_right", corner_char if corner_char is not None else self.corner_top_right_char)
self.corner_bottom_left_char = kwargs.pop("corner_bottom_left", corner_char if corner_char is not None else self.corner_bottom_left_char)
self.corner_bottom_right_char = kwargs.pop("corner_bottom_right", corner_char if corner_char is not None else self.corner_bottom_right_char)
self.corner_top_left_char = kwargs.pop(
"corner_top_left", corner_char if corner_char is not None else self.corner_top_left_char)
self.corner_top_right_char = kwargs.pop(
"corner_top_right", corner_char if corner_char is not None else self.corner_top_right_char)
self.corner_bottom_left_char = kwargs.pop(
"corner_bottom_left", corner_char if corner_char is not None else self.corner_bottom_left_char)
self.corner_bottom_right_char = kwargs.pop(
"corner_bottom_right", corner_char if corner_char is not None else self.corner_bottom_right_char)
# this is used by the table to adjust size of cells with borders in the middle
# of the table
@ -793,13 +811,16 @@ class EvCell(object):
# Handle sizes
if "width" in kwargs:
width = kwargs.pop("width")
self.width = width - self.pad_left - self.pad_right - self.border_left - self.border_right + self.trim_horizontal
if self.width <= 0 and self.raw_width > 0:
self.width = width - self.pad_left - self.pad_right\
- self.border_left - self.border_right + self.trim_horizontal
# if self.width <= 0 and self.raw_width > 0:
if self.width <= 0 < self.raw_width:
raise Exception("Cell width too small, no room for data.")
if "height" in kwargs:
height = kwargs.pop("height")
self.height = height - self.pad_top - self.pad_bottom - self.border_top - self.border_bottom + self.trim_vertical
if self.height <= 0 and self.raw_height > 0:
self.height = height - self.pad_top - self.pad_bottom\
- self.border_top - self.border_bottom + self.trim_vertical
if self.height <= 0 < self.raw_height:
raise Exception("Cell height too small, no room for data.")
# reformat (to new sizes, padding, header and borders)
@ -868,8 +889,8 @@ class EvColumn(object):
col = self.column
kwargs.update(self.options)
# use fixed width or adjust to the largest cell
if not "width" in kwargs:
[cell.reformat() for cell in col] # this is necessary to get initial widths of all cells
if "width" not in kwargs:
[cell.reformat() for cell in col] # this is necessary to get initial widths of all cells
kwargs["width"] = max(cell.get_width() for cell in col) if col else 0
[cell.reformat(**kwargs) for cell in col]
@ -900,11 +921,11 @@ class EvColumn(object):
ypos = min(len(self.column)-1, max(0, int(ypos)))
new_cells = [EvCell(data, **self.options) for data in args]
self.column = self.column[:ypos] + new_cells + self.column[ypos:]
#self._balance(**kwargs)
# self._balance(**kwargs)
def reformat(self, **kwargs):
"""
Change the options for the collumn.
Change the options for the column.
Kwargs:
Keywords as per `EvCell.__init__`.
@ -930,19 +951,24 @@ class EvColumn(object):
def __repr__(self):
return "<EvColumn\n %s>" % ("\n ".join([repr(cell) for cell in self.column]))
def __len__(self):
return len(self.column)
def __iter__(self):
return iter(self.column)
def __getitem__(self, index):
return self.column[index]
def __setitem__(self, index, value):
self.column[index] = value
def __delitem__(self, index):
del self.column[index]
## Main Evtable class
# Main Evtable class
class EvTable(object):
"""
@ -998,7 +1024,7 @@ class EvTable(object):
height (int, optional): Fixed height of table. Defaults to being unset. Width is
still given precedence. If given, table cells will crop text rather
than expand vertically.
evenwidth (bool, optional): Used with the `width` keyword. Adjusts collumns to have as even width as
evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as even width as
possible. This often looks best also for mixed-length tables. Default is `False`.
maxwidth (int, optional): This will set a maximum width
of the table while allowing it to be smaller. Only if it grows wider than this
@ -1025,10 +1051,10 @@ class EvTable(object):
excess = len(header) - len(table)
if excess > 0:
# header bigger than table
table.extend([] for i in range(excess))
table.extend([] for _ in range(excess))
elif excess < 0:
# too short header
header.extend(_to_ansi(["" for i in range(abs(excess))]))
header.extend(_to_ansi(["" for _ in range(abs(excess))]))
for ix, heading in enumerate(header):
table[ix].insert(0, heading)
else:
@ -1043,7 +1069,7 @@ class EvTable(object):
border = kwargs.pop("border", "tablecols")
if border is None:
border = "none"
if not border in ("none", "table", "tablecols",
if border not in ("none", "table", "tablecols",
"header", "incols", "cols", "rows", "cells"):
raise Exception("Unsupported border type: '%s'" % border)
self.border = border
@ -1052,10 +1078,14 @@ class EvTable(object):
self.border_width = kwargs.get("border_width", 1)
self.corner_char = kwargs.get("corner_char", "+")
pcorners = kwargs.pop("pretty_corners", False)
self.corner_top_left_char = _to_ansi(kwargs.pop("corner_top_left_char", '.' if pcorners else self.corner_char))
self.corner_top_right_char = _to_ansi(kwargs.pop("corner_top_right_char", '.' if pcorners else self.corner_char))
self.corner_bottom_left_char = _to_ansi(kwargs.pop("corner_bottom_left_char", ' ' if pcorners else self.corner_char))
self.corner_bottom_right_char = _to_ansi(kwargs.pop("corner_bottom_right_char", ' ' if pcorners else self.corner_char))
self.corner_top_left_char = _to_ansi(kwargs.pop(
"corner_top_left_char", '.' if pcorners else self.corner_char))
self.corner_top_right_char = _to_ansi(kwargs.pop(
"corner_top_right_char", '.' if pcorners else self.corner_char))
self.corner_bottom_left_char = _to_ansi(kwargs.pop(
"corner_bottom_left_char", ' ' if pcorners else self.corner_char))
self.corner_bottom_right_char = _to_ansi(kwargs.pop(
"corner_bottom_right_char", ' ' if pcorners else self.corner_char))
self.width = kwargs.pop("width", None)
self.height = kwargs.pop("height", None)
@ -1079,7 +1109,7 @@ class EvTable(object):
self.worktable = None
# balance the table
#self._balance()
# self._balance()
def _cellborders(self, ix, iy, nx, ny, **kwargs):
"""
@ -1114,7 +1144,7 @@ class EvTable(object):
headchar = self.header_line_char
def corners(ret):
"Handle corners of table"
"""Handle corners of table"""
if ix == 0 and iy == 0:
ret["corner_top_left_char"] = self.corner_top_left_char
if ix == nx and iy == 0:
@ -1126,47 +1156,47 @@ class EvTable(object):
return ret
def left_edge(ret):
"add vertical border along left table edge"
"""add vertical border along left table edge"""
if ix == 0:
ret["border_left"] = bwidth
#ret["trim_horizontal"] = bwidth
# ret["trim_horizontal"] = bwidth
return ret
def top_edge(ret):
"add border along top table edge"
"""add border along top table edge"""
if iy == 0:
ret["border_top"] = bwidth
#ret["trim_vertical"] = bwidth
# ret["trim_vertical"] = bwidth
return ret
def right_edge(ret):
"add vertical border along right table edge"
if ix == nx:# and 0 < iy < ny:
"""add vertical border along right table edge"""
if ix == nx: # and 0 < iy < ny:
ret["border_right"] = bwidth
#ret["trim_horizontal"] = 0
# ret["trim_horizontal"] = 0
return ret
def bottom_edge(ret):
"add border along bottom table edge"
"""add border along bottom table edge"""
if iy == ny:
ret["border_bottom"] = bwidth
#ret["trim_vertical"] = bwidth
# ret["trim_vertical"] = bwidth
return ret
def cols(ret):
"Adding vertical borders inside the table"
"""Adding vertical borders inside the table"""
if 0 <= ix < nx:
ret["border_right"] = bwidth
return ret
def rows(ret):
"Adding horizontal borders inside the table"
"""Adding horizontal borders inside the table"""
if 0 <= iy < ny:
ret["border_bottom"] = bwidth
return ret
def head(ret):
"Add header underline"
"""Add header underline"""
if iy == 0:
# put different bottom line for header
ret["border_bottom"] = bwidth
@ -1176,15 +1206,15 @@ class EvTable(object):
# use the helper functions to define various
# table "styles"
if border in ("table", "tablecols","cells"):
if border in ("table", "tablecols", "cells"):
ret = bottom_edge(right_edge(top_edge(left_edge(corners(ret)))))
if border in ("cols", "tablecols", "cells"):
ret = cols(right_edge(left_edge(ret)))
if border in ("incols"):
if border in "incols":
ret = cols(ret)
if border in ("rows", "cells"):
ret = rows(bottom_edge(top_edge(ret)))
if header and not border in ("none", None):
if header and border not in ("none", None):
ret = head(ret)
return ret
@ -1197,7 +1227,7 @@ class EvTable(object):
options = self.options
for ix, col in enumerate(self.worktable):
for iy, cell in enumerate(col):
col.reformat_cell(iy, **self._cellborders(ix,iy,nx,ny,**options))
col.reformat_cell(iy, **self._cellborders(ix, iy, nx, ny, **options))
def _balance(self):
"""
@ -1211,6 +1241,8 @@ class EvTable(object):
# actual table. This allows us to add columns/rows
# and re-balance over and over without issue.
self.worktable = deepcopy(self.table)
# self._borders()
# return
options = copy(self.options)
# balance number of rows to make a rectangular table
@ -1222,7 +1254,7 @@ class EvTable(object):
self.worktable[icol].reformat(**options)
if nrow < nrowmax:
# add more rows to too-short columns
empty_rows = ["" for i in range(nrowmax-nrow)]
empty_rows = ["" for _ in range(nrowmax-nrow)]
self.worktable[icol].add_rows(*empty_rows)
self.ncols = ncols
self.nrows = nrowmax
@ -1251,16 +1283,16 @@ class EvTable(object):
excess = width - cwmin
if self.evenwidth:
# make each collumn of equal width
for i in range(excess):
# flood-fill the minimum table starting with the smallest collumns
# make each column of equal width
for _ in range(excess):
# flood-fill the minimum table starting with the smallest columns
ci = cwidths_min.index(min(cwidths_min))
cwidths_min[ci] += 1
cwidths = cwidths_min
else:
# make each collumn expand more proportional to their data size
for i in range(excess):
# fill wider collumns first
# make each column expand more proportional to their data size
for _ in range(excess):
# fill wider columns first
ci = cwidths.index(max(cwidths))
cwidths_min[ci] += 1
cwidths[ci] -= 3
@ -1280,8 +1312,9 @@ class EvTable(object):
# if we are fixing the table height, it means cells must crop text instead of resizing.
if nrowmax:
# get minimum possible cell heights for each collumn
cheights_min = [max(cell.get_min_height() for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)]
# get minimum possible cell heights for each column
cheights_min = [max(cell.get_min_height()
for cell in (col[iy] for col in self.worktable)) for iy in range(nrowmax)]
chmin = sum(cheights_min)
if chmin > self.height:
@ -1294,9 +1327,9 @@ class EvTable(object):
excess = self.height - chmin
even = self.height % 2 == 0
for i in range(excess):
for position in range(excess):
# expand the cells with the most rows first
if 0 <= i < nrowmax and nrowmax > 1:
if 0 <= position < nrowmax and nrowmax > 1:
# avoid adding to header first round (looks bad on very small tables)
ci = cheights[1:].index(max(cheights[1:])) + 1
else:
@ -1318,7 +1351,7 @@ class EvTable(object):
col.reformat_cell(iy, height=cheights[iy], **options)
except Exception as e:
msg = "ix=%s, iy=%s, height=%s: %s" % (ix, iy, cheights[iy], e.message)
raise Exception ("Error in vertical allign:\n %s" % msg)
raise Exception("Error in vertical align:\n %s" % msg)
# calculate actual table width/height in characters
self.cwidth = sum(cwidths)
@ -1387,12 +1420,12 @@ class EvTable(object):
if excess > 0:
# we need to add new rows to table
for col in self.table:
empty_rows = ["" for i in range(excess)]
empty_rows = ["" for _ in range(excess)]
col.add_rows(*empty_rows, **options)
self.nrows += excess
elif excess < 0:
# we need to add new rows to new column
empty_rows = ["" for i in range(abs(excess))]
empty_rows = ["" for _ in range(abs(excess))]
column.add_rows(*empty_rows, **options)
self.nrows -= excess
@ -1411,7 +1444,7 @@ class EvTable(object):
xpos = min(wtable-1, max(0, int(xpos)))
self.table.insert(xpos, column)
self.ncols += 1
#self._balance()
# self._balance()
def add_row(self, *args, **kwargs):
"""
@ -1444,12 +1477,12 @@ class EvTable(object):
if excess > 0:
# we need to add new empty columns to table
empty_rows = ["" for i in range(htable)]
self.table.extend([EvColumn(*empty_rows, **options) for i in range(excess)])
empty_rows = ["" for _ in range(htable)]
self.table.extend([EvColumn(*empty_rows, **options) for _ in range(excess)])
self.ncols += excess
elif excess < 0:
# we need to add more cells to row
row.extend(["" for i in range(abs(excess))])
row.extend(["" for _ in range(abs(excess))])
self.ncols -= excess
if ypos is None or ypos > htable - 1:
@ -1462,7 +1495,7 @@ class EvTable(object):
for icol, col in enumerate(self.table):
col.add_rows(row[icol], ypos=ypos, **options)
self.nrows += 1
#self._balance()
# self._balance()
def reformat(self, **kwargs):
"""
@ -1523,16 +1556,17 @@ class EvTable(object):
return [line for line in self._generate_lines()]
def __str__(self):
"print table (this also balances it)"
return str(unicode(ANSIString("\n").join([line for line in self._generate_lines()])))
"""print table (this also balances it)"""
return str(unicode(ANSIString("\n").join([line for line in self._generate_lines()])))
def __unicode__(self):
return unicode(ANSIString("\n").join([line for line in self._generate_lines()]))
return unicode(ANSIString("\n").join([line for line in self._generate_lines()]))
def _test():
"Test"
table = EvTable("{yHeading1{n", "{gHeading2{n", table=[[1,2,3],[4,5,6],[7,8,9]], border="cells", align="l")
table.add_column("{rThis is long data{n", "{bThis is even longer data{n")
"""Test"""
table = EvTable("|yHeading1|n", "|gHeading2|n", table=[[1, 2, 3], [4, 5, 6], [7, 8, 9]], border="cells", align="l")
table.add_column("|rThis is long data|n", "|bThis is even longer data|n")
table.add_row("This is a single row")
print(unicode(table))
table.reformat(width=50)
@ -1541,5 +1575,9 @@ def _test():
print(unicode(table))
return table
def _test2():
table = EvTable("|yHeading1|n", "|B|[GHeading2|n", "Heading3")
for i in range(100):
table.add_row("This is col 0, row %i" % i, "|gThis is col 1, row |w%i|n|g.|n" % i, "This is col 2, row %i" % i)
return table

View file

@ -39,6 +39,7 @@ _GAME_EPOCH = None
# Helper Script dealing in gametime (created by `schedule` function
# below).
class TimeScript(DefaultScript):
"""Gametime-sensitive script."""
@ -60,6 +61,7 @@ class TimeScript(DefaultScript):
# Access functions
def runtime():
"""
Get the total runtime of the server since first start (minus
@ -134,8 +136,9 @@ def gametime(absolute=False):
gtime = epoch + (runtime() - GAME_TIME_OFFSET) * TIMEFACTOR
return gtime
def real_seconds_until(sec=None, min=None, hour=None, day=None,
month=None, year=None):
def real_seconds_until(sec=None, min=None, hour=None,
day=None, month=None, year=None):
"""
Return the real seconds until game time.
@ -187,8 +190,9 @@ def real_seconds_until(sec=None, min=None, hour=None, day=None,
seconds = (projected - current).total_seconds()
return seconds / TIMEFACTOR
def schedule(callback, repeat=False, sec=None, min=None, hour=None,
day=None, month=None, year=None):
def schedule(callback, repeat=False, sec=None, min=None,
hour=None, day=None, month=None, year=None):
"""
Call a callback at a given in-game time.
@ -212,12 +216,12 @@ def schedule(callback, repeat=False, sec=None, min=None, hour=None,
schedule(func, min=5, sec=0) # Will call 5 minutes past the next (in-game) hour.
schedule(func, hour=2, min=30, sec=0) # Will call the next (in-game) day at 02:30.
"""
seconds = real_seconds_until(sec=sec, min=min, hour=hour, day=day,
month=month, year=year)
seconds = real_seconds_until(sec=sec, min=min, hour=hour,
day=day, month=month, year=year)
script = create_script("evennia.utils.gametime.TimeScript",
key="TimeScript", desc="A gametime-sensitive script",
interval=seconds, start_delay=True,
repeats=-1 if repeat else 1)
key="TimeScript", desc="A gametime-sensitive script",
interval=seconds, start_delay=True,
repeats=-1 if repeat else 1)
script.db.callback = callback
script.db.gametime = {
"sec": sec,
@ -229,6 +233,7 @@ def schedule(callback, repeat=False, sec=None, min=None, hour=None,
}
return script
def reset_gametime():
"""
Resets the game time to make it start from the current time. Note that
@ -238,5 +243,3 @@ def reset_gametime():
global GAME_TIME_OFFSET
GAME_TIME_OFFSET = runtime()
ServerConfig.objects.conf("gametime_offset", GAME_TIME_OFFSET)

View file

@ -26,6 +26,7 @@ from twisted.internet.threads import deferToThread
_LOGDIR = None
_TIMEZONE = None
def timeformat(when=None):
"""
This helper function will format the current time in the same
@ -88,7 +89,7 @@ def log_err(errmsg):
Prints/logs an error message to the server log.
Args:
errormsg (str): The message to be logged.
errmsg (str): The message to be logged.
"""
try:
@ -97,7 +98,7 @@ def log_err(errmsg):
errmsg = str(e)
for line in errmsg.splitlines():
log.msg('[EE] %s' % line)
#log.err('ERROR: %s' % (errormsg,))
# log.err('ERROR: %s' % (errmsg,))
log_errmsg = log_err
@ -115,7 +116,7 @@ def log_warn(warnmsg):
warnmsg = str(e)
for line in warnmsg.splitlines():
log.msg('[WW] %s' % line)
#log.msg('WARNING: %s' % (warnmsg,))
# log.msg('WARNING: %s' % (warnmsg,))
log_warnmsg = log_warn
@ -152,7 +153,8 @@ log_depmsg = log_dep
# Arbitrary file logger
_LOG_FILE_HANDLES = {} # holds open log handles
_LOG_FILE_HANDLES = {} # holds open log handles
def _open_log_file(filename):
"""
@ -171,7 +173,7 @@ def _open_log_file(filename):
return _LOG_FILE_HANDLES[filename]
else:
try:
filehandle = open(filename, "a+") # append mode + reading
filehandle = open(filename, "a+") # append mode + reading
_LOG_FILE_HANDLES[filename] = filehandle
return filehandle
except IOError:
@ -184,13 +186,14 @@ def log_file(msg, filename="game.log"):
Arbitrary file logger using threads.
Args:
msg (str): String to append to logfile.
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.
"""
def callback(filehandle, msg):
"Writing to file and flushing result"
"""Writing to file and flushing result"""
msg = "\n%s [-] %s" % (timeformat(), msg.strip())
filehandle.write(msg)
# since we don't close the handle, we need to flush
@ -199,7 +202,7 @@ def log_file(msg, filename="game.log"):
filehandle.flush()
def errback(failure):
"Catching errors to normal log"
"""Catching errors to normal log"""
log_trace()
# save to server/logs/ directory
@ -230,7 +233,7 @@ def tail_log_file(filename, offset, nlines, callback=None):
"""
def seek_file(filehandle, offset, nlines, callback):
"step backwards in chunks and stop only when we have enough lines"
"""step backwards in chunks and stop only when we have enough lines"""
lines_found = []
buffer_size = 4098
block_count = -1
@ -254,7 +257,7 @@ def tail_log_file(filename, offset, nlines, callback=None):
return lines_found
def errback(failure):
"Catching errors to normal log"
"""Catching errors to normal log"""
log_trace()
filehandle = _open_log_file(filename)

View file

@ -65,10 +65,7 @@ else:
from cgi import escape
# hrule styles
FRAME = 0
ALL = 1
NONE = 2
HEADER = 3
FRAME, ALL, NONE, HEADER = range(4)
# Table styles
DEFAULT = 10
@ -78,12 +75,13 @@ RANDOM = 20
_re = re.compile("\033\[[0-9;]*m")
def _ansi(method):
"decorator for converting ansi in input"
"""decorator for converting ansi in input"""
def wrapper(self, *args, **kwargs):
def convert(inp):
if isinstance(inp, basestring):
return parse_ansi("{n%s{n" % inp)
return parse_ansi("|n%s|n" % inp)
elif hasattr(inp, '__iter__'):
li = []
for element in inp:
@ -96,19 +94,20 @@ def _ansi(method):
return li
return inp
args = [convert(arg) for arg in args]
#kwargs = dict((key, convert(val)) for key, val in kwargs.items())
# kwargs = dict((key, convert(val)) for key, val in kwargs.items())
return method(self, *args, **kwargs)
return wrapper
def _get_size(text):
lines = text.split("\n")
height = len(lines)
width = max([_str_block_width(line) for line in lines])
return (width, height)
return width, height
class PrettyTable(object):
@_ansi
def __init__(self, field_names=None, **kwargs):
@ -153,9 +152,12 @@ class PrettyTable(object):
self._widths = []
# Options
self._options = "start end fields header border sortby reversesort sort_key attributes format hrules vrules".split()
self._options.extend("int_format float_format padding_width left_padding_width right_padding_width".split())
self._options.extend("vertical_char horizontal_char junction_char header_style valign xhtml print_empty".split())
self._options = "start end fields header border sortby reversesort" \
" sort_key attributes format hrules vrules".split()
self._options.extend("int_format float_format padding_width "
"left_padding_width right_padding_width".split())
self._options.extend("vertical_char horizontal_char junction_char"
" header_style valign xhtml print_empty".split())
for option in self._options:
if option in kwargs:
self._validate_option(option, kwargs[option])
@ -263,10 +265,10 @@ class PrettyTable(object):
if py3k:
def __str__(self):
return self.__unicode__()
return self.__unicode__()
else:
def __str__(self):
return self.__unicode__().encode(self.encoding)
return self.__unicode__().encode(self.encoding)
def __unicode__(self):
return self.get_string()
@ -283,31 +285,32 @@ class PrettyTable(object):
# Secondly, in the _get_options method, where keyword arguments are mixed with persistent settings
def _validate_option(self, option, val):
if option in ("field_names"):
if option in "field_names":
self._validate_field_names(val)
elif option in ("start", "end", "max_width", "padding_width", "left_padding_width", "right_padding_width", "format"):
elif option in ("start", "end", "max_width", "padding_width",
"left_padding_width", "right_padding_width", "format"):
self._validate_nonnegative_int(option, val)
elif option in ("sortby"):
elif option in "sortby":
self._validate_field_name(option, val)
elif option in ("sort_key"):
elif option in "sort_key":
self._validate_function(option, val)
elif option in ("hrules"):
elif option in "hrules":
self._validate_hrules(option, val)
elif option in ("vrules"):
elif option in "vrules":
self._validate_vrules(option, val)
elif option in ("fields"):
elif option in "fields":
self._validate_all_field_names(option, val)
elif option in ("header", "border", "reversesort", "xhtml", "print_empty"):
self._validate_true_or_false(option, val)
elif option in ("header_style"):
elif option in "header_style":
self._validate_header_style(val)
elif option in ("int_format"):
elif option in "int_format":
self._validate_int_format(option, val)
elif option in ("float_format"):
elif option in "float_format":
self._validate_float_format(option, val)
elif option in ("vertical_char", "horizontal_char", "junction_char"):
self._validate_single_char(option, val)
elif option in ("attributes"):
elif option in "attributes":
self._validate_attributes(option, val)
else:
raise Exception("Unrecognised option: %s!" % option)
@ -316,14 +319,16 @@ class PrettyTable(object):
# Check for appropriate length
if self._field_names:
try:
assert len(val) == len(self._field_names)
assert len(val) == len(self._field_names)
except AssertionError:
raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._field_names)))
raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)"
% (len(val), len(self._field_names)))
if self._rows:
try:
assert len(val) == len(self._rows[0])
assert len(val) == len(self._rows[0])
except AssertionError:
raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._rows[0])))
raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)"
% (len(val), len(self._rows[0])))
# Check for uniqueness
try:
assert len(val) == len(set(val))
@ -338,13 +343,13 @@ class PrettyTable(object):
def _validate_align(self, val):
try:
assert val in ["l","c","r"]
assert val in ["l", "c", "r"]
except AssertionError:
raise Exception("Alignment %s is invalid, use l, c or r!" % val)
def _validate_valign(self, val):
try:
assert val in ["t","m","b",None]
assert val in ["t", "m", "b", None]
except AssertionError:
raise Exception("Alignment %s is invalid, use t, m, b or None!" % val)
@ -436,6 +441,7 @@ class PrettyTable(object):
fields - list or tuple of field names"""
return self._field_names
def _set_field_names(self, val):
val = [self._unicode(x) for x in val]
self._validate_option("field_names", val)
@ -461,30 +467,37 @@ class PrettyTable(object):
else:
for field in self._field_names:
self._valign[field] = "t"
field_names = property(_get_field_names, _set_field_names)
def _get_align(self):
return self._align
def _set_align(self, val):
self._validate_align(val)
for field in self._field_names:
self._align[field] = val
align = property(_get_align, _set_align)
def _get_valign(self):
return self._valign
def _set_valign(self, val):
self._validate_valign(val)
for field in self._field_names:
self._valign[field] = val
valign = property(_get_valign, _set_valign)
def _get_max_width(self):
return self._max_width
def _set_max_width(self, val):
self._validate_option("max_width", val)
for field in self._field_names:
self._max_width[field] = val
max_width = property(_get_max_width, _set_max_width)
def _get_fields(self):
@ -494,9 +507,11 @@ class PrettyTable(object):
fields - list or tuple of field names to include in displays"""
return self._fields
def _set_fields(self, val):
self._validate_option("fields", val)
self._fields = val
fields = property(_get_fields, _set_fields)
def _get_start(self):
@ -510,6 +525,7 @@ class PrettyTable(object):
def _set_start(self, val):
self._validate_option("start", val)
self._start = val
start = property(_get_start, _set_start)
def _get_end(self):
@ -519,9 +535,11 @@ class PrettyTable(object):
end - index of last data row to include in output PLUS ONE (list slice style)"""
return self._end
def _set_end(self, val):
self._validate_option("end", val)
self._end = val
end = property(_get_end, _set_end)
def _get_sortby(self):
@ -531,9 +549,11 @@ class PrettyTable(object):
sortby - field name to sort by"""
return self._sortby
def _set_sortby(self, val):
self._validate_option("sortby", val)
self._sortby = val
sortby = property(_get_sortby, _set_sortby)
def _get_reversesort(self):
@ -543,9 +563,11 @@ class PrettyTable(object):
reveresort - set to True to sort by descending order, or False to sort by ascending order"""
return self._reversesort
def _set_reversesort(self, val):
self._validate_option("reversesort", val)
self._reversesort = val
reversesort = property(_get_reversesort, _set_reversesort)
def _get_sort_key(self):
@ -555,9 +577,11 @@ class PrettyTable(object):
sort_key - a function which takes one argument and returns something to be sorted"""
return self._sort_key
def _set_sort_key(self, val):
self._validate_option("sort_key", val)
self._sort_key = val
sort_key = property(_get_sort_key, _set_sort_key)
def _get_header(self):
@ -567,9 +591,11 @@ class PrettyTable(object):
header - print a header showing field names (True or False)"""
return self._header
def _set_header(self, val):
self._validate_option("header", val)
self._header = val
header = property(_get_header, _set_header)
def _get_header_style(self):
@ -579,9 +605,11 @@ class PrettyTable(object):
header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)"""
return self._header_style
def _set_header_style(self, val):
self._validate_header_style(val)
self._header_style = val
header_style = property(_get_header_style, _set_header_style)
def _get_border(self):
@ -591,9 +619,11 @@ class PrettyTable(object):
border - print a border around the table (True or False)"""
return self._border
def _set_border(self, val):
self._validate_option("border", val)
self._border = val
border = property(_get_border, _set_border)
def _get_hrules(self):
@ -603,9 +633,11 @@ class PrettyTable(object):
hrules - horizontal rules style. Allowed values: FRAME, ALL, HEADER, NONE"""
return self._hrules
def _set_hrules(self, val):
self._validate_option("hrules", val)
self._hrules = val
hrules = property(_get_hrules, _set_hrules)
def _get_vrules(self):
@ -615,9 +647,11 @@ class PrettyTable(object):
vrules - vertical rules style. Allowed values: FRAME, ALL, NONE"""
return self._vrules
def _set_vrules(self, val):
self._validate_option("vrules", val)
self._vrules = val
vrules = property(_get_vrules, _set_vrules)
def _get_int_format(self):
@ -626,10 +660,12 @@ class PrettyTable(object):
int_format - integer format string"""
return self._int_format
def _set_int_format(self, val):
# self._validate_option("int_format", val)
# self._validate_option("int_format", val)
for field in self._field_names:
self._int_format[field] = val
int_format = property(_get_int_format, _set_int_format)
def _get_float_format(self):
@ -638,10 +674,12 @@ class PrettyTable(object):
float_format - floating point format string"""
return self._float_format
def _set_float_format(self, val):
# self._validate_option("float_format", val)
# self._validate_option("float_format", val)
for field in self._field_names:
self._float_format[field] = val
float_format = property(_get_float_format, _set_float_format)
def _get_padding_width(self):
@ -651,9 +689,11 @@ class PrettyTable(object):
padding_width - number of spaces, must be a positive integer"""
return self._padding_width
def _set_padding_width(self, val):
self._validate_option("padding_width", val)
self._padding_width = val
padding_width = property(_get_padding_width, _set_padding_width)
def _get_left_padding_width(self):
@ -663,9 +703,11 @@ class PrettyTable(object):
left_padding - number of spaces, must be a positive integer"""
return self._left_padding_width
def _set_left_padding_width(self, val):
self._validate_option("left_padding_width", val)
self._left_padding_width = val
left_padding_width = property(_get_left_padding_width, _set_left_padding_width)
def _get_right_padding_width(self):
@ -675,9 +717,11 @@ class PrettyTable(object):
right_padding - number of spaces, must be a positive integer"""
return self._right_padding_width
def _set_right_padding_width(self, val):
self._validate_option("right_padding_width", val)
self._right_padding_width = val
right_padding_width = property(_get_right_padding_width, _set_right_padding_width)
def _get_vertical_char(self):
@ -687,10 +731,12 @@ class PrettyTable(object):
vertical_char - single character string used to draw vertical lines"""
return self._vertical_char
def _set_vertical_char(self, val):
val = self._unicode(val)
self._validate_option("vertical_char", val)
self._vertical_char = val
vertical_char = property(_get_vertical_char, _set_vertical_char)
def _get_horizontal_char(self):
@ -700,10 +746,12 @@ class PrettyTable(object):
horizontal_char - single character string used to draw horizontal lines"""
return self._horizontal_char
def _set_horizontal_char(self, val):
val = self._unicode(val)
self._validate_option("horizontal_char", val)
self._horizontal_char = val
horizontal_char = property(_get_horizontal_char, _set_horizontal_char)
def _get_junction_char(self):
@ -713,10 +761,12 @@ class PrettyTable(object):
junction_char - single character string used to draw line junctions"""
return self._junction_char
def _set_junction_char(self, val):
val = self._unicode(val)
self._validate_option("vertical_char", val)
self._junction_char = val
junction_char = property(_get_junction_char, _set_junction_char)
def _get_format(self):
@ -726,9 +776,11 @@ class PrettyTable(object):
format - True or False"""
return self._format
def _set_format(self, val):
self._validate_option("format", val)
self._format = val
format = property(_get_format, _set_format)
def _get_print_empty(self):
@ -738,9 +790,11 @@ class PrettyTable(object):
print_empty - True or False"""
return self._print_empty
def _set_print_empty(self, val):
self._validate_option("print_empty", val)
self._print_empty = val
print_empty = property(_get_print_empty, _set_print_empty)
def _get_attributes(self):
@ -750,9 +804,11 @@ class PrettyTable(object):
attributes - dictionary of attributes"""
return self._attributes
def _set_attributes(self, val):
self._validate_option("attributes", val)
self._attributes = val
attributes = property(_get_attributes, _set_attributes)
##############################
@ -825,8 +881,8 @@ class PrettyTable(object):
self.border = random.choice((True, False))
self._hrules = random.choice((ALL, FRAME, HEADER, NONE))
self._vrules = random.choice((ALL, FRAME, NONE))
self.left_padding_width = random.randint(0,5)
self.right_padding_width = random.randint(0,5)
self.left_padding_width = random.randint(0, 5)
self.right_padding_width = random.randint(0, 5)
self.vertical_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
self.horizontal_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
self.junction_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
@ -846,9 +902,10 @@ class PrettyTable(object):
has fields"""
if self._field_names and len(row) != len(self._field_names):
raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)" %(len(row),len(self._field_names)))
raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)"
% (len(row), len(self._field_names)))
if not self._field_names:
self.field_names = [("Field %d" % (n+1)) for n in range(0,len(row))]
self.field_names = [("Field %d" % (n+1)) for n in range(0, len(row))]
self._rows.append(list(row))
def del_row(self, row_index):
@ -860,7 +917,7 @@ class PrettyTable(object):
row_index - The index of the row you want to delete. Indexing starts at 0."""
if row_index > len(self._rows)-1:
raise Exception("Cant delete row at index %d, table only has %d rows!" % (row_index, len(self._rows)))
raise Exception("Can't delete row at index %d, table only has %d rows!" % (row_index, len(self._rows)))
del self._rows[row_index]
@_ansi
@ -1113,7 +1170,7 @@ class PrettyTable(object):
def _stringify_row(self, row, options):
for index, field, value, width, in zip(range(0,len(row)), self._field_names, row, self._widths):
for index, field, value, width, in zip(range(0, len(row)), self._field_names, row, self._widths):
# Enforce max widths
lines = value.split("\n")
new_lines = []
@ -1145,14 +1202,14 @@ class PrettyTable(object):
valign = self._valign[field]
lines = value.split("\n")
dHeight = row_height - len(lines)
if dHeight:
dheight = row_height - len(lines)
if dheight:
if valign == "m":
lines = [""] * (dHeight // 2) + lines + [""] * (dHeight - (dHeight // 2))
lines = [""] * (dheight // 2) + lines + [""] * (dheight - (dheight // 2))
elif valign == "b":
lines = [""] * dHeight + lines
lines = [""] * dheight + lines
else:
lines = lines + [""] * dHeight
lines += [""] * dheight
y = 0
for l in lines:
@ -1174,7 +1231,7 @@ class PrettyTable(object):
bits[y].pop()
bits[y].append(options["vertical_char"])
if options["border"] and options["hrules"]== ALL:
if options["border"] and options["hrules"] == ALL:
bits[row_height-1].append("\n")
bits[row_height-1].append(self._hrule)
@ -1227,8 +1284,7 @@ class PrettyTable(object):
else:
linebreak = "<br>"
open_tag = []
open_tag.append("<table")
open_tag = ["<table"]
if options["attributes"]:
for attr_name in options["attributes"]:
open_tag.append(" %s=\"%s\"" % (attr_name, options["attributes"][attr_name]))
@ -1268,8 +1324,7 @@ class PrettyTable(object):
else:
linebreak = "<br>"
open_tag = []
open_tag.append("<table")
open_tag = ["<table"]
if options["border"]:
if options["hrules"] == ALL and options["vrules"] == ALL:
open_tag.append(" frame=\"box\" rules=\"all\"")
@ -1297,7 +1352,9 @@ class PrettyTable(object):
for field in self._field_names:
if options["fields"] and field not in options["fields"]:
continue
lines.append(" <th style=\"padding-left: %dem; padding-right: %dem; text-align: center\">%s</th>" % (lpad, rpad, escape(field).replace("\n", linebreak)))
lines.append(" <th style=\"padding-left: %dem; padding-right: "
"%dem; text-align: center\">%s</th>"
% (lpad, rpad, escape(field).replace("\n", linebreak)))
lines.append(" </tr>")
# Data
@ -1306,14 +1363,16 @@ class PrettyTable(object):
aligns = []
valigns = []
for field in self._field_names:
aligns.append({ "l" : "left", "r" : "right", "c" : "center" }[self._align[field]])
valigns.append({"t" : "top", "m" : "middle", "b" : "bottom"}[self._valign[field]])
aligns.append(dict(l="left", r="right", c="center")[self._align[field]])
valigns.append(dict(t="top", m="middle", b="bottom")[self._valign[field]])
for row in formatted_rows:
lines.append(" <tr>")
for field, datum, align, valign in zip(self._field_names, row, aligns, valigns):
if options["fields"] and field not in options["fields"]:
continue
lines.append(" <td style=\"padding-left: %dem; padding-right: %dem; text-align: %s; vertical-align: %s\">%s</td>" % (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak)))
lines.append(" <td style=\"padding-left: %dem; padding-right: %dem; "
"text-align: %s; vertical-align: %s\">%s</td>"
% (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak)))
lines.append(" </tr>")
lines.append("</table>")
@ -1323,10 +1382,11 @@ class PrettyTable(object):
# UNICODE WIDTH FUNCTIONS #
##############################
def _char_block_width(char):
# Basic Latin, which is probably the most common case
#if char in xrange(0x0021, 0x007e):
#if char >= 0x0021 and char <= 0x007e:
# if char in xrange(0x0021, 0x007e):
# if char >= 0x0021 and char <= 0x007e:
if 0x0021 <= char <= 0x007e:
return 1
# Chinese, Japanese, Korean (common)
@ -1356,6 +1416,7 @@ def _char_block_width(char):
# Take a guess
return 1
def _str_block_width(val):
return sum(itermap(_char_block_width, itermap(ord, _re.sub("", val))))
@ -1364,7 +1425,8 @@ def _str_block_width(val):
# TABLE FACTORIES #
##############################
def from_csv(fp, field_names = None, **kwargs):
def from_csv(fp, field_names=None, **kwargs):
dialect = csv.Sniffer().sniff(fp.read(1024))
fp.seek(0)
@ -1381,6 +1443,7 @@ def from_csv(fp, field_names = None, **kwargs):
return table
def from_db_cursor(cursor, **kwargs):
if cursor.description:
@ -1390,6 +1453,7 @@ def from_db_cursor(cursor, **kwargs):
table.add_row(row)
return table
class TableHandler(HTMLParser):
def __init__(self, **kwargs):
@ -1403,12 +1467,12 @@ class TableHandler(HTMLParser):
self.last_content = ""
self.is_last_row_header = False
def handle_starttag(self,tag, attrs):
def handle_starttag(self, tag, attrs):
self.active = tag
if tag == "th":
self.is_last_row_header = True
def handle_endtag(self,tag):
def handle_endtag(self, tag):
if tag in ["th", "td"]:
stripped_content = self.last_content.strip()
self.last_row.append(stripped_content)
@ -1425,7 +1489,6 @@ class TableHandler(HTMLParser):
self.last_content = " "
self.active = None
def handle_data(self, data):
self.last_content += data
@ -1437,10 +1500,10 @@ class TableHandler(HTMLParser):
for row in self.rows:
if len(row[0]) < self.max_row_width:
appends = self.max_row_width - len(row[0])
for i in range(1,appends):
for _ in range(1, appends):
row[0].append("-")
if row[1] == True:
if row[1] is True:
self.make_fields_unique(row[0])
table.field_names = row[0]
else:
@ -1456,6 +1519,7 @@ class TableHandler(HTMLParser):
if fields[i] == fields[j]:
fields[j] += "'"
def from_html(html_code, **kwargs):
"""
Generates a list of PrettyTables from a string of HTML code. Each <table> in
@ -1466,6 +1530,7 @@ def from_html(html_code, **kwargs):
parser.feed(html_code)
return parser.tables
def from_html_one(html_code, **kwargs):
"""
Generates a PrettyTables from a string of HTML code which contains only a
@ -1483,6 +1548,7 @@ def from_html_one(html_code, **kwargs):
# MAIN (TEST FUNCTION) #
##############################
def main():
x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
@ -1490,7 +1556,7 @@ def main():
x.reversesort = True
x.int_format["Area"] = "04d"
x.float_format = "6.1f"
x.align["City name"] = "l" # Left align city names
x.align["City name"] = "l" # Left align city names
x.add_row(["Adelaide", 1295, 1158259, 600.5])
x.add_row(["Brisbane", 5905, 1857594, 1146.4])
x.add_row(["Darwin", 112, 120900, 1714.7])

View file

@ -46,9 +46,9 @@ HelpEntry = ContentType.objects.get(app_label="help", model="helpentry").model_c
Tag = ContentType.objects.get(app_label="typeclasses", model="tag").model_class()
#------------------------------------------------------------------
# -------------------------------------------------------------------
# Search manager-wrappers
#------------------------------------------------------------------
# -------------------------------------------------------------------
#
# Search objects as a character
@ -199,10 +199,16 @@ help_entries = search_help
def search_object_attribute(key=None, category=None, value=None, strvalue=None):
return ObjectDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue)
def search_player_attribute(key=None, category=None, value=None, strvalue=None):
return PlayerDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue)
def search_script_attribute(key=None, category=None, value=None, strvalue=None):
return ScriptDB.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue)
def search_channel_attribute(key=None, category=None, value=None, strvalue=None):
return Channel.objects.get_by_attribute(key=key, category=category, value=value, strvalue=strvalue)
@ -218,6 +224,8 @@ search_attribute_object = ObjectDB.objects.get_attribute
# Note that this returns the object attached to the tag, not the tag
# object itself (this is usually what you want)
def search_object_by_tag(key=None, category=None):
"""
Find object based on tag or category.
@ -235,7 +243,9 @@ def search_object_by_tag(key=None, category=None):
"""
return ObjectDB.objects.get_by_tag(key=key, category=category)
search_tag = search_object_by_tag # this is the most common case
search_tag = search_object_by_tag # this is the most common case
def search_player_tag(key=None, category=None):
"""
Find player based on tag or category.
@ -253,6 +263,8 @@ def search_player_tag(key=None, category=None):
"""
return PlayerDB.objects.get_by_tag(key=key, category=category)
def search_script_tag(key=None, category=None):
"""
Find script based on tag or category.
@ -270,6 +282,8 @@ def search_script_tag(key=None, category=None):
"""
return ScriptDB.objects.get_by_tag(key=key, category=category)
def search_channel_tag(key=None, category=None):
"""
Find channel based on tag or category.

View file

@ -82,9 +82,9 @@ many traits with a normal *goblin*.
from __future__ import print_function
import copy
#TODO
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
#os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
# TODO
# sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
# os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
from django.conf import settings
from random import randint
@ -131,9 +131,10 @@ def _get_prototype(dic, prot, protparents):
new_prot = _get_prototype(protparents.get(prototype, {}), prot, protparents)
prot.update(new_prot)
prot.update(dic)
prot.pop("prototype", None) # we don't need this anymore
prot.pop("prototype", None) # we don't need this anymore
return prot
def _batch_create_object(*objparams):
"""
This is a cut-down version of the create_object() function,
@ -141,7 +142,7 @@ def _batch_create_object(*objparams):
so make sure the spawned Typeclass works before using this!
Args:
objsparams (any): Aach argument should be a tuple of arguments
objsparams (any): Each argument should be a tuple of arguments
for the respective creation/add handlers in the following
order: (create, permissions, locks, aliases, nattributes,
attributes)
@ -153,8 +154,9 @@ def _batch_create_object(*objparams):
# bulk create all objects in one go
# unfortunately this doesn't work since bulk_create doesn't creates pks;
# the result are double objects at the next stage
#dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
# the result would be duplicate objects at the next stage, so we comment
# it out for now:
# dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams]
objs = []
@ -167,7 +169,7 @@ def _batch_create_object(*objparams):
"aliases": objparam[3],
"nattributes": objparam[4],
"attributes": objparam[5],
"tags":objparam[6]}
"tags": objparam[6]}
# this triggers all hooks
obj.save()
# run eventual extra code
@ -201,9 +203,9 @@ def spawn(*prototypes, **kwargs):
if not protmodules and hasattr(settings, "PROTOTYPE_MODULES"):
protmodules = make_iter(settings.PROTOTYPE_MODULES)
for prototype_module in protmodules:
protparents.update(dict((key, val)
for key, val in all_from_module(prototype_module).items() if isinstance(val, dict)))
#overload module's protparents with specifically given protparents
protparents.update(dict((key, val) for key, val in
all_from_module(prototype_module).items() if isinstance(val, dict)))
# overload module's protparents with specifically given protparents
protparents.update(kwargs.get("prototype_parents", {}))
for key, prototype in protparents.items():
_validate_prototype(key, prototype, protparents, [])
@ -223,10 +225,10 @@ def spawn(*prototypes, **kwargs):
# extract the keyword args we need to create the object itself. If we get a callable,
# call that to get the value (don't catch errors)
create_kwargs = {}
keyval = prot.pop("key", "Spawned Object %06i" % randint(1,100000))
keyval = prot.pop("key", "Spawned Object %06i" % randint(1, 100000))
create_kwargs["db_key"] = keyval() if callable(keyval) else keyval
locval = prot.pop("location", None)
locval = prot.pop("location", None)
create_kwargs["db_location"] = locval() if callable(locval) else _handle_dbref(locval)
homval = prot.pop("home", settings.DEFAULT_HOME)
@ -244,7 +246,7 @@ def spawn(*prototypes, **kwargs):
lockval = prot.pop("locks", "")
lock_string = lockval() if callable(lockval) else lockval
aliasval = prot.pop("aliases", "")
alias_string = aliasval() if callable(aliasval) else aliasval
alias_string = aliasval() if callable(aliasval) else aliasval
tagval = prot.pop("tags", "")
tags = tagval() if callable(tagval) else tagval
exval = prot.pop("exec", "")
@ -252,16 +254,16 @@ def spawn(*prototypes, **kwargs):
# extract ndb assignments
nattributes = dict((key.split("_", 1)[1], value() if callable(value) else value)
for key, value in prot.items() if key.startswith("ndb_"))
for key, value in prot.items() if key.startswith("ndb_"))
# the rest are attributes
attributes = dict((key, value() if callable(value) else value)
for key, value in prot.items()
if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_")))
for key, value in prot.items()
if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_")))
# pack for call into _batch_create_object
objsparams.append( (create_kwargs, permission_string, lock_string,
alias_string, nattributes, attributes, tags, execs) )
objsparams.append((create_kwargs, permission_string, lock_string,
alias_string, nattributes, attributes, tags, execs))
return _batch_create_object(*objsparams)
@ -271,33 +273,35 @@ if __name__ == "__main__":
protparents = {
"NOBODY": {},
#"INFINITE" : {
# "prototype":"INFINITE"
#},
"GOBLIN" : {
# "INFINITE" : {
# "prototype":"INFINITE"
# },
"GOBLIN": {
"key": "goblin grunt",
"health": lambda: randint(20,30),
"health": lambda: randint(20, 30),
"resists": ["cold", "poison"],
"attacks": ["fists"],
"weaknesses": ["fire", "light"]
},
"GOBLIN_WIZARD" : {
"GOBLIN_WIZARD": {
"prototype": "GOBLIN",
"key": "goblin wizard",
"spells": ["fire ball", "lighting bolt"]
},
"GOBLIN_ARCHER" : {
"GOBLIN_ARCHER": {
"prototype": "GOBLIN",
"key": "goblin archer",
"attacks": ["short bow"]
},
"ARCHWIZARD" : {
"ARCHWIZARD": {
"attacks": ["archwizard staff"],
},
"GOBLIN_ARCHWIZARD" : {
"GOBLIN_ARCHWIZARD": {
"key": "goblin archwizard",
"prototype" : ("GOBLIN_WIZARD", "ARCHWIZARD")
"prototype": ("GOBLIN_WIZARD", "ARCHWIZARD")
}
}
# test
print([o.key for o in spawn(protparents["GOBLIN"], protparents["GOBLIN_ARCHWIZARD"], prototype_parents=protparents)])
print([o.key for o in spawn(protparents["GOBLIN"],
protparents["GOBLIN_ARCHWIZARD"],
prototype_parents=protparents)])

View file

@ -31,13 +31,11 @@ class ANSIStringTestCase(TestCase):
"""
Make sure the ANSIString is always constructed correctly.
"""
clean = u'This isA{r testTest'
encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA{r test\x1b[0mTest\x1b[0m'
target = ANSIString(r'{gThis is{rA{{r test{nTest{n')
char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31,
32, 37, 38, 39, 40]
code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22,
23, 24, 33, 34, 35, 36, 41, 42, 43, 44]
clean = u'This isA|r testTest'
encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA|r test\x1b[0mTest\x1b[0m'
target = ANSIString(r'|gThis is|rA||r test|nTest|n')
char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 37, 38, 39, 40]
code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 16, 17, 18, 19, 20, 21, 22, 23, 24, 33, 34, 35, 36, 41, 42, 43, 44]
self.checker(target, encoded, clean)
self.table_check(target, char_table, code_table)
self.checker(ANSIString(target), encoded, clean)
@ -54,7 +52,7 @@ class ANSIStringTestCase(TestCase):
Verifies that slicing an ANSIString results in expected color code
distribution.
"""
target = ANSIString(r'{gTest{rTest{n')
target = ANSIString(r'|gTest|rTest|n')
result = target[:3]
self.checker(result, u'\x1b[1m\x1b[32mTes', u'Tes')
result = target[:4]
@ -80,7 +78,7 @@ class ANSIStringTestCase(TestCase):
Verifies that re.split and .split behave similarly and that color
codes end up where they should.
"""
target = ANSIString("{gThis is {nA split string{g")
target = ANSIString("|gThis is |nA split string|g")
first = (u'\x1b[1m\x1b[32mThis is \x1b[0m', u'This is ')
second = (u'\x1b[1m\x1b[32m\x1b[0m split string\x1b[1m\x1b[32m',
u' split string')
@ -96,9 +94,9 @@ class ANSIStringTestCase(TestCase):
Verify that joining a set of ANSIStrings works.
"""
# This isn't the desired behavior, but the expected one. Python
# concatinates the in-memory representation with the built-in string's
# concatenates the in-memory representation with the built-in string's
# join.
l = [ANSIString("{gTest{r") for s in range(0, 3)]
l = [ANSIString("|gTest|r") for _ in range(0, 3)]
# Force the generator to be evaluated.
result = "".join(l)
self.assertEqual(unicode(result), u'TestTestTest')
@ -112,14 +110,14 @@ class ANSIStringTestCase(TestCase):
Make sure that length reporting on ANSIStrings does not include
ANSI codes.
"""
self.assertEqual(len(ANSIString('{gTest{n')), 4)
self.assertEqual(len(ANSIString('|gTest|n')), 4)
def test_capitalize(self):
"""
Make sure that capitalization works. This is the simplest of the
_transform functions.
"""
target = ANSIString('{gtest{n')
target = ANSIString('|gtest|n')
result = u'\x1b[1m\x1b[32mTest\x1b[0m'
self.checker(target.capitalize(), result, u'Test')
@ -127,8 +125,8 @@ class ANSIStringTestCase(TestCase):
"""
Make sure MXP tags are not treated like ANSI codes, but normal text.
"""
mxp1 = "{lclook{ltat{le"
mxp2 = "Start to {lclook here{ltclick somewhere here{le first"
mxp1 = "|lclook|ltat|le"
mxp2 = "Start to |lclook here|ltclick somewhere here|le first"
self.assertEqual(15, len(ANSIString(mxp1)))
self.assertEqual(53, len(ANSIString(mxp2)))
# These would indicate an issue with the tables.
@ -139,17 +137,15 @@ class ANSIStringTestCase(TestCase):
def test_add(self):
"""
Verify concatination works correctly.
Verify concatenation works correctly.
"""
a = ANSIString("{gTest")
b = ANSIString("{cString{n")
a = ANSIString("|gTest")
b = ANSIString("|cString|n")
c = a + b
result = u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m'
self.checker(c, result, u'TestString')
char_table = [9, 10, 11, 12, 22, 23, 24, 25, 26, 27]
code_table = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31
]
code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31]
self.table_check(c, char_table, code_table)
def test_strip(self):
@ -166,7 +162,7 @@ class ANSIStringTestCase(TestCase):
class TestIsIter(TestCase):
def test_is_iter(self):
self.assertEqual(True, utils.is_iter([1,2,3,4]))
self.assertEqual(True, utils.is_iter([1, 2, 3, 4]))
self.assertEqual(False, utils.is_iter("This is not an iterable"))
@ -213,10 +209,10 @@ class TestListToString(TestCase):
[1,2,3] -> '"1", "2" and "3"'
"""
def test_list_to_string(self):
self.assertEqual('1, 2, 3', utils.list_to_string([1,2,3], endsep=""))
self.assertEqual('"1", "2", "3"', utils.list_to_string([1,2,3], endsep="", addquote=True))
self.assertEqual('1, 2 and 3', utils.list_to_string([1,2,3]))
self.assertEqual('"1", "2" and "3"', utils.list_to_string([1,2,3], endsep="and", addquote=True))
self.assertEqual('1, 2, 3', utils.list_to_string([1, 2, 3], endsep=""))
self.assertEqual('"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep="", addquote=True))
self.assertEqual('1, 2 and 3', utils.list_to_string([1, 2, 3]))
self.assertEqual('"1", "2" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True))
class TestMLen(TestCase):
@ -231,10 +227,10 @@ class TestMLen(TestCase):
self.assertEqual(utils.m_len('|lclook|ltat|le'), 2)
def test_mxp_ansi_string(self):
self.assertEqual(utils.m_len(ANSIString('|lcl|gook|ltat|le{n')), 2)
self.assertEqual(utils.m_len(ANSIString('|lcl|gook|ltat|le|n')), 2)
def test_non_mxp_ansi_string(self):
self.assertEqual(utils.m_len(ANSIString('{gHello{n')), 5)
self.assertEqual(utils.m_len(ANSIString('{gHello{n')), 5) # TODO - cause this to fail by default.
self.assertEqual(utils.m_len(ANSIString('|gHello|n')), 5)
def test_list(self):
@ -246,6 +242,7 @@ class TestMLen(TestCase):
from .text2html import TextToHTMLparser
class TestTextToHTMLparser(TestCase):
def setUp(self):
self.parser = TextToHTMLparser()
@ -255,71 +252,78 @@ class TestTextToHTMLparser(TestCase):
def test_url_scheme_ftp(self):
self.assertEqual(self.parser.convert_urls('ftp.example.com'),
'<a href="ftp.example.com" target="_blank">ftp.example.com</a>')
'<a href="ftp.example.com" target="_blank">ftp.example.com</a>')
def test_url_scheme_www(self):
self.assertEqual(self.parser.convert_urls('www.example.com'),
'<a href="www.example.com" target="_blank">www.example.com</a>')
'<a href="www.example.com" target="_blank">www.example.com</a>')
def test_url_scheme_ftpproto(self):
self.assertEqual(self.parser.convert_urls('ftp://ftp.example.com'),
'<a href="ftp://ftp.example.com" target="_blank">ftp://ftp.example.com</a>')
'<a href="ftp://ftp.example.com" target="_blank">ftp://ftp.example.com</a>')
def test_url_scheme_http(self):
self.assertEqual(self.parser.convert_urls('http://example.com'),
'<a href="http://example.com" target="_blank">http://example.com</a>')
'<a href="http://example.com" target="_blank">http://example.com</a>')
def test_url_scheme_https(self):
self.assertEqual(self.parser.convert_urls('https://example.com'),
'<a href="https://example.com" target="_blank">https://example.com</a>')
'<a href="https://example.com" target="_blank">https://example.com</a>')
def test_url_chars_slash(self):
self.assertEqual(self.parser.convert_urls('www.example.com/homedir'),
'<a href="www.example.com/homedir" target="_blank">www.example.com/homedir</a>')
'<a href="www.example.com/homedir" target="_blank">www.example.com/homedir</a>')
def test_url_chars_colon(self):
self.assertEqual(self.parser.convert_urls('https://example.com:8000/login/'),
'<a href="https://example.com:8000/login/" target="_blank">https://example.com:8000/login/</a>')
'<a href="https://example.com:8000/login/" target="_blank">'
'https://example.com:8000/login/</a>')
def test_url_chars_querystring(self):
self.assertEqual(self.parser.convert_urls('https://example.com/submitform?field1=val1+val3&field2=val2'),
'<a href="https://example.com/submitform?field1=val1+val3&field2=val2" target="_blank">https://example.com/submitform?field1=val1+val3&field2=val2</a>')
'<a href="https://example.com/submitform?field1=val1+val3&field2=val2" target="_blank">'
'https://example.com/submitform?field1=val1+val3&field2=val2</a>')
def test_url_chars_anchor(self):
self.assertEqual(self.parser.convert_urls('http://www.example.com/menu#section_1'),
'<a href="http://www.example.com/menu#section_1" target="_blank">http://www.example.com/menu#section_1</a>')
'<a href="http://www.example.com/menu#section_1" target="_blank">'
'http://www.example.com/menu#section_1</a>')
def test_url_chars_exclam(self):
self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve'),
'<a href="https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve" target="_blank">https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve</a>')
self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/'
'?fromgroups#!categories/evennia/ainneve'),
'<a href="https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve"'
' target="_blank">https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve</a>')
def test_url_edge_leadingw(self):
self.assertEqual(self.parser.convert_urls('wwww.example.com'),
'w<a href="www.example.com" target="_blank">www.example.com</a>')
'w<a href="www.example.com" target="_blank">www.example.com</a>')
def test_url_edge_following_period_eol(self):
self.assertEqual(self.parser.convert_urls('www.example.com.'),
'<a href="www.example.com" target="_blank">www.example.com</a>.')
'<a href="www.example.com" target="_blank">www.example.com</a>.')
def test_url_edge_following_period(self):
self.assertEqual(self.parser.convert_urls('see www.example.com. '),
'see <a href="www.example.com" target="_blank">www.example.com</a>. ')
'see <a href="www.example.com" target="_blank">www.example.com</a>. ')
def test_url_edge_brackets(self):
self.assertEqual(self.parser.convert_urls('[http://example.com/]'),
'[<a href="http://example.com/" target="_blank">http://example.com/</a>]')
'[<a href="http://example.com/" target="_blank">http://example.com/</a>]')
def test_url_edge_multiline(self):
self.assertEqual(self.parser.convert_urls(' * http://example.com/info\n * bullet'),
' * <a href="http://example.com/info" target="_blank">http://example.com/info</a>\n * bullet')
' * <a href="http://example.com/info" target="_blank">'
'http://example.com/info</a>\n * bullet')
def test_url_edge_following_htmlentity(self):
self.assertEqual(self.parser.convert_urls('http://example.com/info&lt;span&gt;'),
'<a href="http://example.com/info" target="_blank">http://example.com/info</a>&lt;span&gt;')
'<a href="http://example.com/info" target="_blank">http://example.com/info</a>&lt;span&gt;')
def test_url_edge_surrounded_spans(self):
self.assertEqual(self.parser.convert_urls('</span>http://example.com/<span class="red">'),
'</span><a href="http://example.com/" target="_blank">http://example.com/</a><span class="red">')
'</span><a href="http://example.com/" target="_blank">'
'http://example.com/</a><span class="red">')
from evennia.utils import evmenu
from mock import Mock
@ -338,8 +342,9 @@ class TestEvMenu(TestCase):
from evennia.utils import inlinefuncs
class TestInlineFuncs(TestCase):
"Test the nested inlinefunc module"
"""Test the nested inlinefunc module"""
def test_nofunc(self):
self.assertEqual(inlinefuncs.parse_inlinefunc(
"as$382ewrw w we w werw,|44943}"),
@ -372,12 +377,146 @@ class TestInlineFuncs(TestCase):
from evennia.utils import evform
class TestEvForm(TestCase):
def test_form(self):
self.maxDiff = None
self.assertEqual(evform._test(),
u'.------------------------------------------------.\n| |\n| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m Player: \x1b[0m\x1b[1m\x1b[33mGriatch \x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m |\n| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n| |\n >----------------------------------------------<\n| |\n| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0mfellow\x1b[0m \x1b[0m INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n| |\n >----------.-----------------------------------<\n| | |\n| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m | \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m | \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m | \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m | \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n| | |\n -----------`-------------------------------------\n')
u'.------------------------------------------------.\n'
u'| |\n'
u'| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b'
u'[1m\x1b[32mthe\x1b[1m\x1b[32m \x1b[0m \x1b[0m '
u'Player: \x1b[0m\x1b[1m\x1b[33mGriatch '
u'\x1b[0m\x1b[0m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[1m\x1b[32m\x1b[0m\x1b[0m '
u'|\n'
u'| \x1b[0m\x1b[1m\x1b[32mBouncer\x1b[0m \x1b[0m |\n'
u'| |\n'
u' >----------------------------------------------<\n'
u'| |\n'
u'| Desc: \x1b[0mA sturdy \x1b[0m \x1b[0m'
u' STR: \x1b[0m12 \x1b[0m\x1b[0m\x1b[0m\x1b[0m'
u' DEX: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
u'| \x1b[0mfellow\x1b[0m \x1b[0m'
u' INT: \x1b[0m5 \x1b[0m\x1b[0m\x1b[0m\x1b[0m'
u' STA: \x1b[0m18 \x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
u'| \x1b[0m \x1b[0m'
u' LUC: \x1b[0m10 \x1b[0m\x1b[0m\x1b[0m'
u' MAG: \x1b[0m3 \x1b[0m\x1b[0m\x1b[0m |\n'
u'| |\n'
u' >----------.-----------------------------------<\n'
u'| | |\n'
u'| \x1b[0mHP\x1b[0m|\x1b[0mMV \x1b[0m|\x1b[0mMP\x1b[0m '
u'| \x1b[0mSkill \x1b[0m|\x1b[0mValue \x1b[0m'
u'|\x1b[0mExp \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
u'| ~~+~~~+~~ | ~~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~ |\n'
u'| \x1b[0m**\x1b[0m|\x1b[0m***\x1b[0m\x1b[0m|\x1b[0m**\x1b[0m\x1b[0m '
u'| \x1b[0mShooting \x1b[0m|\x1b[0m12 \x1b[0m'
u'|\x1b[0m550/1200 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
u'| \x1b[0m \x1b[0m|\x1b[0m**\x1b[0m \x1b[0m|\x1b[0m*\x1b[0m \x1b[0m '
u'| \x1b[0mHerbalism \x1b[0m|\x1b[0m14 \x1b[0m'
u'|\x1b[0m990/1400 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
u'| \x1b[0m \x1b[0m|\x1b[0m \x1b[0m|\x1b[0m \x1b[0m '
u'| \x1b[0mSmithing \x1b[0m|\x1b[0m9 \x1b[0m'
u'|\x1b[0m205/900 \x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m\x1b[0m |\n'
u'| | |\n'
u' -----------`-------------------------------------\n')
def test_ansi_escape(self):
# note that in a msg() call, the result would be the correct |-----,
# in a print, ansi only gets called once, so ||----- is the result
self.assertEqual(unicode(evform.EvForm(form={"FORM":"\n||-----"})), "||-----")
class TestTimeformat(TestCase):
"""
Default function header from utils.py:
time_format(seconds, style=0)
"""
def test_style_0(self):
"""Test the style 0 of time_format."""
self.assertEqual(utils.time_format(0, 0), "00:00")
self.assertEqual(utils.time_format(28, 0), "00:00")
self.assertEqual(utils.time_format(92, 0), "00:01")
self.assertEqual(utils.time_format(300, 0), "00:05")
self.assertEqual(utils.time_format(660, 0), "00:11")
self.assertEqual(utils.time_format(3600, 0), "01:00")
self.assertEqual(utils.time_format(3725, 0), "01:02")
self.assertEqual(utils.time_format(86350, 0), "23:59")
self.assertEqual(utils.time_format(86800, 0), "1d 00:06")
self.assertEqual(utils.time_format(130800, 0), "1d 12:20")
self.assertEqual(utils.time_format(530800, 0), "6d 03:26")
def test_style_1(self):
"""Test the style 1 of time_format."""
self.assertEqual(utils.time_format(0, 1), "0s")
self.assertEqual(utils.time_format(28, 1), "28s")
self.assertEqual(utils.time_format(92, 1), "1m")
self.assertEqual(utils.time_format(300, 1), "5m")
self.assertEqual(utils.time_format(660, 1), "11m")
self.assertEqual(utils.time_format(3600, 1), "1h")
self.assertEqual(utils.time_format(3725, 1), "1h")
self.assertEqual(utils.time_format(86350, 1), "23h")
self.assertEqual(utils.time_format(86800, 1), "1d")
self.assertEqual(utils.time_format(130800, 1), "1d")
self.assertEqual(utils.time_format(530800, 1), "6d")
def test_style_2(self):
"""Test the style 2 of time_format."""
self.assertEqual(utils.time_format(0, 2), "0 minutes")
self.assertEqual(utils.time_format(28, 2), "0 minutes")
self.assertEqual(utils.time_format(92, 2), "1 minute")
self.assertEqual(utils.time_format(300, 2), "5 minutes")
self.assertEqual(utils.time_format(660, 2), "11 minutes")
self.assertEqual(utils.time_format(3600, 2), "1 hour, 0 minutes")
self.assertEqual(utils.time_format(3725, 2), "1 hour, 2 minutes")
self.assertEqual(utils.time_format(86350, 2), "23 hours, 59 minutes")
self.assertEqual(utils.time_format(86800, 2),
"1 day, 0 hours, 6 minutes")
self.assertEqual(utils.time_format(130800, 2),
"1 day, 12 hours, 20 minutes")
self.assertEqual(utils.time_format(530800, 2),
"6 days, 3 hours, 26 minutes")
def test_style_3(self):
"""Test the style 3 of time_format."""
self.assertEqual(utils.time_format(0, 3), "")
self.assertEqual(utils.time_format(28, 3), "28 seconds")
self.assertEqual(utils.time_format(92, 3), "1 minute 32 seconds")
self.assertEqual(utils.time_format(300, 3), "5 minutes 0 seconds")
self.assertEqual(utils.time_format(660, 3), "11 minutes 0 seconds")
self.assertEqual(utils.time_format(3600, 3),
"1 hour, 0 minutes")
self.assertEqual(utils.time_format(3725, 3),
"1 hour, 2 minutes 5 seconds")
self.assertEqual(utils.time_format(86350, 3),
"23 hours, 59 minutes 10 seconds")
self.assertEqual(utils.time_format(86800, 3),
"1 day, 0 hours, 6 minutes 40 seconds")
self.assertEqual(utils.time_format(130800, 3),
"1 day, 12 hours, 20 minutes 0 seconds")
self.assertEqual(utils.time_format(530800, 3),
"6 days, 3 hours, 26 minutes 40 seconds")
def test_style_4(self):
"""Test the style 4 of time_format."""
self.assertEqual(utils.time_format(0, 4), "0 seconds")
self.assertEqual(utils.time_format(28, 4), "28 seconds")
self.assertEqual(utils.time_format(92, 4), "a minute")
self.assertEqual(utils.time_format(300, 4), "5 minutes")
self.assertEqual(utils.time_format(660, 4), "11 minutes")
self.assertEqual(utils.time_format(3600, 4), "an hour")
self.assertEqual(utils.time_format(3725, 4), "an hour")
self.assertEqual(utils.time_format(86350, 4), "23 hours")
self.assertEqual(utils.time_format(86800, 4), "a day")
self.assertEqual(utils.time_format(130800, 4), "a day")
self.assertEqual(utils.time_format(530800, 4), "6 days")
self.assertEqual(utils.time_format(3030800, 4), "a month")
self.assertEqual(utils.time_format(7030800, 4), "2 months")
self.assertEqual(utils.time_format(40030800, 4), "a year")
self.assertEqual(utils.time_format(90030800, 4), "2 years")
def test_unknown_format(self):
"""Test that unknown formats raise exceptions."""
self.assertRaises(ValueError, utils.time_format, 0, 5)
self.assertRaises(ValueError, utils.time_format, 0, "u")

View file

@ -44,6 +44,7 @@ _DA = object.__delattr__
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
def is_iter(iterable):
"""
Checks if an object behaves iterably.
@ -62,6 +63,7 @@ def is_iter(iterable):
"""
return hasattr(iterable, '__iter__')
def make_iter(obj):
"""
Makes sure that the object is always iterable.
@ -201,7 +203,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
distribute odd spaces randomly to one of the gaps.
"""
line_rest = width - (wlen + ngaps)
gap = " " # minimum gap between words
gap = " " # minimum gap between words
if line_rest > 0:
if align == 'l':
line[-1] += " " * line_rest
@ -211,7 +213,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
pad = " " * (line_rest // 2)
line[0] = pad + line[0]
line[-1] = line[-1] + pad + " " * (line_rest % 2)
else: # align 'f'
else: # align 'f'
gap += " " * (line_rest // max(1, ngaps))
rest_gap = line_rest % max(1, ngaps)
for i in range(rest_gap):
@ -250,8 +252,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
wlen += word[1]
ngaps += 1
if line: # catch any line left behind
if line: # catch any line left behind
lines.append(_process_line(line))
indentstring = " " * indent
return "\n".join([indentstring + line for line in lines])
@ -344,6 +345,7 @@ def time_format(seconds, style=0):
1. "1d"
2. "1 day, 8 hours, 30 minutes"
3. "1 day, 8 hours, 30 minutes, 10 seconds"
4. highest unit (like "3 years" or "8 months" or "1 second")
Returns:
timeformatted (str): A pretty time string.
"""
@ -360,7 +362,7 @@ def time_format(seconds, style=0):
minutes = seconds // 60
seconds -= minutes * 60
retval = ""
retval = ""
if style == 0:
"""
Standard colon-style output.
@ -432,6 +434,38 @@ def time_format(seconds, style=0):
else:
seconds_str = '%i seconds ' % seconds
retval = '%s%s%s%s' % (days_str, hours_str, minutes_str, seconds_str)
elif style == 4:
"""
Only return the highest unit.
"""
if days >= 730: # Several years
return "{} years".format(days // 365)
elif days >= 365: # One year
return "a year"
elif days >= 62: # Several months
return "{} months".format(days // 31)
elif days >= 31: # One month
return "a month"
elif days >= 2: # Several days
return "{} days".format(days)
elif days > 0:
return "a day"
elif hours >= 2: # Several hours
return "{} hours".format(hours)
elif hours > 0: # One hour
return "an hour"
elif minutes >= 2: # Several minutes
return "{} minutes".format(minutes)
elif minutes > 0: # One minute
return "a minute"
elif seconds >= 2: # Several seconds
return "{} seconds".format(seconds)
elif seconds == 1:
return "a second"
else:
return "0 seconds"
else:
raise ValueError("Unknown style for time format: %s" % style)
return retval.strip()
@ -522,13 +556,13 @@ def pypath_to_realpath(python_path, file_ending='.py', pypath_prefixes=None):
"""
path = python_path.strip().split('.')
plong = osjoin(*path) + file_ending
pshort = osjoin(*path[1:]) + file_ending if len(path) > 1 else plong # in case we had evennia. or mygame.
pshort = osjoin(*path[1:]) + file_ending if len(path) > 1 else plong # in case we had evennia. or mygame.
prefixlong = [osjoin(*ppath.strip().split('.'))
for ppath in make_iter(pypath_prefixes)] \
if pypath_prefixes else []
for ppath in make_iter(pypath_prefixes)] \
if pypath_prefixes else []
prefixshort = [osjoin(*ppath.strip().split('.')[1:])
for ppath in make_iter(pypath_prefixes) if len(ppath.strip().split('.')) > 1] \
if pypath_prefixes else []
for ppath in make_iter(pypath_prefixes) if len(ppath.strip().split('.')) > 1]\
if pypath_prefixes else []
paths = [plong] + \
[osjoin(_EVENNIA_DIR, prefix, plong) for prefix in prefixlong] + \
[osjoin(_GAME_DIR, prefix, plong) for prefix in prefixlong] + \
@ -616,6 +650,7 @@ dbid_to_obj = dbref_to_obj
_UNICODE_MAP = {"EM DASH": "-", "FIGURE DASH": "-", "EN DASH": "-", "HORIZONTAL BAR": "-",
"HORIZONTAL ELLIPSIS": "...", "RIGHT SINGLE QUOTATION MARK": "'"}
def latinify(unicode_string, default='?', pure_ascii=False):
"""
Convert a unicode string to "safe" ascii/latin-1 characters.
@ -643,7 +678,7 @@ def latinify(unicode_string, default='?', pure_ascii=False):
# point name; e.g., since `name(u'á') == 'LATIN SMALL
# LETTER A WITH ACUTE'` translate `á` to `a`. However, in
# some cases the unicode name is still "LATIN LETTER"
# although no direct equivalent in the Latin alphabeth
# although no direct equivalent in the Latin alphabet
# exists (e.g., Þ, "LATIN CAPITAL LETTER THORN") -- we can
# avoid these cases by checking that the letter name is
# composed of one letter only.
@ -910,6 +945,8 @@ def delay(timedelay, callback, *args, **kwargs):
_TYPECLASSMODELS = None
_OBJECTMODELS = None
def clean_object_caches(obj):
"""
Clean all object caches on the given object.
@ -1146,8 +1183,6 @@ def all_from_module(module):
# module if available (try to avoid not imports)
members = getmembers(mod, predicate=lambda obj: getmodule(obj) in (mod, None))
return dict((key, val) for key, val in members if not key.startswith("_"))
#return dict((key, val) for key, val in mod.__dict__.items()
# if not (key.startswith("_") or ismodule(val)))
def callables_from_module(module):
@ -1209,7 +1244,7 @@ def variable_from_module(module, variable=None, default=None):
else:
# get all
result = [val for key, val in mod.__dict__.items()
if not (key.startswith("_") or ismodule(val))]
if not (key.startswith("_") or ismodule(val))]
if len(result) == 1:
return result[0]
@ -1348,6 +1383,7 @@ def class_from_module(path, defaultpaths=None):
# alias
object_from_module = class_from_module
def init_new_player(player):
"""
Deprecated.
@ -1442,7 +1478,7 @@ def string_partial_matching(alternatives, inp, ret_index=True):
# (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)]
if alt_word.startswith(inp_word)]
if submatch:
last_index = min(submatch) + 1
score += 1
@ -1488,7 +1524,7 @@ def format_table(table, extra_space=1):
for ir, row in enumarate(ftable):
if ir == 0:
# make first row white
string += "\n{w" + ""join(row) + "{n"
string += "\n|w" + ""join(row) + "|n"
else:
string += "\n" + "".join(row)
print string
@ -1539,6 +1575,8 @@ def get_evennia_pids():
from gc import get_referents
from sys import getsizeof
def deepsize(obj, max_depth=4):
"""
Get not only size of the given object, but also the size of
@ -1562,21 +1600,22 @@ def deepsize(obj, max_depth=4):
"""
def _recurse(o, dct, depth):
if max_depth >= 0 and depth > max_depth:
if 0 <= max_depth < depth:
return
for ref in get_referents(o):
idr = id(ref)
if not idr in dct:
if idr not in dct:
dct[idr] = (ref, getsizeof(ref, default=0))
_recurse(ref, dct, depth+1)
sizedict = {}
_recurse(obj, sizedict, 0)
#count = len(sizedict) + 1
size = getsizeof(obj) + sum([p[1] for p in sizedict.values()])
return size
# lazy load handler
_missing = object()
class lazy_property(object):
"""
Delays loading of property until first access. Credit goes to the
@ -1597,14 +1636,14 @@ class lazy_property(object):
"""
def __init__(self, func, name=None, doc=None):
"Store all properties for now"
"""Store all properties for now"""
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func
def __get__(self, obj, type=None):
"Triggers initialization"
"""Triggers initialization"""
if obj is None:
return self
value = obj.__dict__.get(self.__name__, _missing)
@ -1614,7 +1653,9 @@ class lazy_property(object):
return value
_STRIP_ANSI = None
_RE_CONTROL_CHAR = re.compile('[%s]' % re.escape(''.join([unichr(c) for c in range(0,32)])))# + range(127,160)])))
_RE_CONTROL_CHAR = re.compile('[%s]' % re.escape(''.join([unichr(c) for c in range(0, 32)]))) # + range(127,160)])))
def strip_control_sequences(string):
"""
Remove non-print text sequences.
@ -1675,12 +1716,12 @@ def m_len(target):
return len(ANSI_PARSER.strip_mxp(target))
return len(target)
#------------------------------------------------------------------
# -------------------------------------------------------------------
# Search handler function
#------------------------------------------------------------------
# -------------------------------------------------------------------
#
# Replace this hook function by changing settings.SEARCH_AT_RESULT.
#
def at_search_result(matches, caller, query="", quiet=False, **kwargs):
"""
@ -1696,7 +1737,7 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
should the result pass through.
caller (Object): The object performing the search and/or which should
receive error messages.
query (str, optional): The search query used to produce `matches`.
query (str, optional): The search query used to produce `matches`.
quiet (bool, optional): If `True`, no messages will be echoed to caller
on errors.
@ -1774,6 +1815,7 @@ class LimitedSizeOrderedDict(OrderedDict):
super(LimitedSizeOrderedDict, self).update(*args, **kwargs)
self._check_size()
def get_game_dir_path():
"""
This is called by settings_default in order to determine the path
@ -1784,7 +1826,7 @@ def get_game_dir_path():
"""
# current working directory, assumed to be somewhere inside gamedir.
for i in range(10):
for _ in range(10):
gpath = os.getcwd()
if "server" in os.listdir(gpath):
if os.path.isfile(os.path.join("server", "conf", "settings.py")):