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 # 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: ## Feb 2015:
Development currently in devel/ branch. Moved typeclasses to use 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}) " \ _ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
"reached for '{raw_string}' ({cmdclass})." "reached for '{raw_cmdname}' ({cmdclass})."
def _msg_err(receiver, stringtuple): 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: for lobj in local_objlist:
try: try:
# call hook in case we need to do dynamic changing to cmdset # 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: except Exception:
logger.log_trace() logger.log_trace()
# the call-type lock is checked here, it makes sure a player # 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 @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. 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. precendence for same-name and same-prio commands.
session (Session, optional): Relevant if callertype is "player" - the session will help session (Session, optional): Relevant if callertype is "player" - the session will help
retrieve the correct cmdsets from puppeted objects. 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:
kwargs (any): other keyword arguments will be assigned as named variables on the 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 @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 Helper function: This initializes and runs the Command
instance once the parser has identified it as either a normal 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: Args:
cmd (Command): Command object cmd (Command): Command object
cmdname (str): Name of command cmdname (str): Name of command
args (str): extra text entered after the identified command
raw_cmdname (str): Name of Command, unaffected by eventual raw_cmdname (str): Name of Command, unaffected by eventual
prefix-stripping (if no prefix-stripping, this is the same prefix-stripping (if no prefix-stripping, this is the same
as cmdname). 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: Returns:
deferred (Deferred): this will fire with the return of the 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 _COMMAND_NESTING[called_by] += 1
if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT: if _COMMAND_NESTING[called_by] > _COMMAND_RECURSION_LIMIT:
err = _ERROR_RECURSION_LIMIT.format(recursion_limit=_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__) cmdclass=cmd.__class__)
raise RuntimeError(err) 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 bugs in cmdhandler itself
try: # catch special-type commands 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, else:
callertype, raw_string) # no explicit cmdobject given, figure it out
if not cmdset: cmdset = yield get_and_merge_cmdsets(caller, session, player, obj,
# this is bad and shouldn't happen. callertype, raw_string)
raise NoCmdSets if not cmdset:
# store the completely unmodified raw string - including # this is bad and shouldn't happen.
# whitespace and eventual prefixes-to-be-stripped. raise NoCmdSets
unformatted_raw_string = raw_string # store the completely unmodified raw string - including
raw_string = raw_string.strip() # whitespace and eventual prefixes-to-be-stripped.
if not raw_string: unformatted_raw_string = raw_string
# Empty input. Test for system command instead. raw_string = raw_string.strip()
syscmd = yield cmdset.get(CMD_NOINPUT) if not raw_string:
sysarg = "" # Empty input. Test for system command instead.
raise ExecSystemCommand(syscmd, sysarg) syscmd = yield cmdset.get(CMD_NOINPUT)
# Parse the input string and match to available cmdset. sysarg = ""
# This also checks for permissions, so all commands in match raise ExecSystemCommand(syscmd, sysarg)
# are commands the caller is allowed to call. # Parse the input string and match to available cmdset.
matches = yield _COMMAND_PARSER(raw_string, cmdset, caller) # 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: if len(matches) > 1:
# We have a multiple-match # We have a multiple-match
syscmd = yield cmdset.get(CMD_MULTIMATCH) syscmd = yield cmdset.get(CMD_MULTIMATCH)
sysarg = _("There were multiple matches.") sysarg = _("There were multiple matches.")
if syscmd: if syscmd:
# use custom CMD_MULTIMATCH # use custom CMD_MULTIMATCH
syscmd.matches = matches 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)
else: else:
sysarg += _(" Type \"help\" for help.") # fall back to default error handling
raise ExecSystemCommand(syscmd, sysarg) 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. cmdname, args, cmd = "", "", None
if hasattr(cmd, 'is_channel') and cmd.is_channel: if len(matches) == 1:
# even if a user-defined syscmd is not defined, the # We have a unique command match. But it may still be invalid.
# found cmd is already a system command in its own right. match = matches[0]
syscmd = yield cmdset.get(CMD_CHANNEL) cmdname, args, cmd, raw_cmdname = match[0], match[1], match[2], match[5]
if syscmd:
# replace system command with custom version if not matches:
cmd = syscmd # No commands match our entered command
cmd.session = session syscmd = yield cmdset.get(CMD_NOMATCH)
sysarg = "%s:%s" % (cmdname, args) if syscmd:
raise ExecSystemCommand(cmd, sysarg) # 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. # 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) returnValue(ret)
except ErrorReported as exc: except ErrorReported as exc:
@ -629,7 +653,8 @@ def cmdhandler(called_by, raw_string, _testing=False, callertype="session", sess
sysarg = exc.sysarg sysarg = exc.sysarg
if syscmd: 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) returnValue(ret)
elif sysarg: elif sysarg:
# return system arg # return system arg

View file

@ -35,7 +35,7 @@ def _init_command(cls, **kwargs):
if cls.aliases and not is_iter(cls.aliases): if cls.aliases and not is_iter(cls.aliases):
try: try:
cls.aliases = [str(alias).strip().lower() cls.aliases = [str(alias).strip().lower()
for alias in cls.aliases.split(',')] for alias in cls.aliases.split(',')]
except Exception: except Exception:
cls.aliases = [] cls.aliases = []
cls.aliases = list(set(alias for alias in 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'): if not hasattr(cls, 'locks'):
# default if one forgets to define completely # default if one forgets to define completely
cls.locks = "cmd:all()" cls.locks = "cmd:all()"
if not "cmd:" in cls.locks: if "cmd:" not in cls.locks:
cls.locks = "cmd:all();" + cls.locks cls.locks = "cmd:all();" + cls.locks
for lockstring in cls.locks.split(';'): for lockstring in cls.locks.split(';'):
if lockstring and not ':' in lockstring: if lockstring and not ':' in lockstring:
@ -197,7 +197,6 @@ class Command(with_metaclass(CommandMeta, object)):
try: try:
# first assume input is a command (the most common case) # first assume input is a command (the most common case)
return self._matchset.intersection(cmd._matchset) return self._matchset.intersection(cmd._matchset)
#return cmd.key in self._matchset
except AttributeError: except AttributeError:
# probably got a string # probably got a string
return cmd in self._matchset return cmd in self._matchset
@ -211,9 +210,8 @@ class Command(with_metaclass(CommandMeta, object)):
""" """
try: try:
return self._matchset.isdisjoint(cmd._matchset) return self._matchset.isdisjoint(cmd._matchset)
#return not cmd.key in self._matcheset
except AttributeError: except AttributeError:
return not cmd in self._matchset return cmd not in self._matchset
def __contains__(self, query): 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, def msg(self, text=None, to_obj=None, from_obj=None,
session=None, **kwargs): 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 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. 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 # a simple test command to show the available properties
string = "-" * 50 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 += "-" * 50
string += "\nname of cmd (self.key): {w%s{n\n" % self.key 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 aliases (self.aliases): |w%s|n\n" % self.aliases
string += "cmd locks (self.locks): {w%s{n\n" % self.locks 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 += "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 calling (self.caller): |w%s|n\n" % self.caller
string += "object storing cmdset (self.obj): {w%s{n\n" % self.obj 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 += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring
# show cmdset.key instead of cmdset to shorten output # 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) 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 django.conf import settings
from evennia.server.sessionhandler import SESSIONS from evennia.server.sessionhandler import SESSIONS
from evennia.server.models import ServerConfig 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) COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
@ -40,7 +40,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
help_category = "Admin" help_category = "Admin"
def func(self): def func(self):
"Implementing the function" """Implementing the function"""
caller = self.caller caller = self.caller
args = self.args args = self.args
@ -86,7 +86,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
# Carry out the booting of the sessions in the boot list. # Carry out the booting of the sessions in the boot list.
feedback = None feedback = None
if not 'quiet' in self.switches: if 'quiet' not in self.switches:
feedback = "You have been disconnected by %s.\n" % caller.name feedback = "You have been disconnected by %s.\n" % caller.name
if reason: if reason:
feedback += "\nReason given: %s" % reason feedback += "\nReason given: %s" % reason
@ -108,13 +108,12 @@ def list_bans(banlist):
if not banlist: if not banlist:
return "No active bans were found." 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): for inum, ban in enumerate(banlist):
table.add_row([str(inum + 1), table.add_row(str(inum + 1),
ban[0] and ban[0] or ban[1], ban[0] and ban[0] or ban[1],
ban[3], ban[4]]) ban[3], ban[4])
string = "{wActive bans:{n\n%s" % table return "|wActive bans:|n\n%s" % table
return string
class CmdBan(COMMAND_DEFAULT_CLASS): class CmdBan(COMMAND_DEFAULT_CLASS):
@ -174,7 +173,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
if not self.args or (self.switches if not self.args or (self.switches
and not any(switch in ('ip', 'name') and not any(switch in ('ip', 'name')
for switch in self.switches)): for switch in self.switches)):
self.caller.msg(list_bans(banlist)) self.caller.msg(list_bans(banlist))
return return
@ -202,7 +201,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
# save updated banlist # save updated banlist
banlist.append(bantup) banlist.append(bantup)
ServerConfig.objects.conf('server_bans', banlist) 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): class CmdUnban(COMMAND_DEFAULT_CLASS):
@ -223,7 +222,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
help_category = "Admin" help_category = "Admin"
def func(self): def func(self):
"Implement unbanning" """Implement unbanning"""
banlist = ServerConfig.objects.conf('server_bans') banlist = ServerConfig.objects.conf('server_bans')
@ -240,14 +239,14 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
if not banlist: if not banlist:
self.caller.msg("There are no bans to clear.") self.caller.msg("There are no bans to clear.")
elif not (0 < num < len(banlist) + 1): 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: else:
# all is ok, clear ban # all is ok, clear ban
ban = banlist[num - 1] ban = banlist[num - 1]
del banlist[num - 1] del banlist[num - 1]
ServerConfig.objects.conf('server_bans', banlist) ServerConfig.objects.conf('server_bans', banlist)
self.caller.msg("Cleared ban %s: %s" % 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): class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
@ -270,7 +269,7 @@ class CmdDelPlayer(COMMAND_DEFAULT_CLASS):
help_category = "Admin" help_category = "Admin"
def func(self): def func(self):
"Implements the command." """Implements the command."""
caller = self.caller caller = self.caller
args = self.args args = self.args
@ -346,7 +345,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
help_category = "Admin" help_category = "Admin"
def func(self): def func(self):
"Implement the command" """Implement the command"""
caller = self.caller caller = self.caller
args = self.args args = self.args
@ -382,7 +381,7 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
obj = caller.search(objname, global_search=True) obj = caller.search(objname, global_search=True)
if not obj: if not obj:
return 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) caller.msg("%s is not a room. Ignored." % objname)
continue continue
if players_only and not obj.has_player: if players_only and not obj.has_player:
@ -414,7 +413,7 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
help_category = "Admin" help_category = "Admin"
def func(self): def func(self):
"Implement the function." """Implement the function."""
caller = self.caller caller = self.caller
@ -455,7 +454,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
help_category = "Admin" help_category = "Admin"
def func(self): def func(self):
"Implement function" """Implement function"""
caller = self.caller caller = self.caller
switches = self.switches switches = self.switches
@ -481,36 +480,37 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
caller.msg("You are not allowed to examine this object.") caller.msg("You are not allowed to examine this object.")
return return
string = "Permissions on {w%s{n: " % obj.key string = "Permissions on |w%s|n: " % obj.key
if not obj.permissions.all(): if not obj.permissions.all():
string += "<None>" string += "<None>"
else: else:
string += ", ".join(obj.permissions.all()) string += ", ".join(obj.permissions.all())
if (hasattr(obj, 'player') and if (hasattr(obj, 'player') and
hasattr(obj.player, 'is_superuser') 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 += "\n(... but this object is currently controlled by a SUPERUSER! "
string += "All access checks are passed automatically.)" string += "All access checks are passed automatically.)"
caller.msg(string) caller.msg(string)
return return
# we supplied an argument on the form obj = perm # we supplied an argument on the form obj = perm
locktype = "edit" if playermode else "control"
if not obj.access(caller, 'control'): if not obj.access(caller, locktype):
caller.msg("You are not allowed to edit this object's permissions.") caller.msg("You are not allowed to edit this %s's permissions."
% ("player" if playermode else "object"))
return return
cstring = "" caller_result = []
tstring = "" target_result = []
if 'del' in switches: if 'del' in switches:
# delete the given permission(s) from object. # delete the given permission(s) from object.
for perm in self.rhslist: for perm in self.rhslist:
obj.permissions.remove(perm) obj.permissions.remove(perm)
if obj.permissions.get(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: else:
cstring += "\nPermission %s removed from %s (if they existed)." % (perm, obj.name) caller_result.append("\nPermission %s removed from %s (if they existed)." % (perm, obj.name))
tstring += "\n%s revokes the permission(s) %s from you." % (caller.name, perm) target_result.append("\n%s revokes the permission(s) %s from you." % (caller.name, perm))
else: else:
# add a new permission # add a new permission
permissions = obj.permissions.all() permissions = obj.permissions.all()
@ -525,15 +525,16 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
return return
if perm in permissions: 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: else:
obj.permissions.add(perm) obj.permissions.add(perm)
plystring = "the Player" if playermode else "the Object/Character" plystring = "the Player" if playermode else "the Object/Character"
cstring += "\nPermission '%s' given to %s (%s)." % (rhs, obj.name, plystring) caller_result.append("\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) target_result.append("\n%s gives you (%s, %s) the permission '%s'."
caller.msg(cstring.strip()) % (caller.name, obj.name, plystring, rhs))
if tstring: caller.msg("".join(caller_result).strip())
obj.msg(tstring.strip()) if target_result:
obj.msg("".join(target_result).strip())
class CmdWall(COMMAND_DEFAULT_CLASS): class CmdWall(COMMAND_DEFAULT_CLASS):
@ -550,7 +551,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
help_category = "Admin" help_category = "Admin"
def func(self): def func(self):
"Implements command" """Implements command"""
if not self.args: if not self.args:
self.caller.msg("Usage: @wall <message>") self.caller.msg("Usage: @wall <message>")
return return

View file

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

View file

@ -591,9 +591,11 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
if not obj: if not obj:
return return
desc = self.args desc = self.args
if obj.access(caller, "edit"):
obj.db.desc = desc obj.db.desc = desc
caller.msg("The description was set on %s." % obj.get_display_name(caller)) 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): class CmdDestroy(COMMAND_DEFAULT_CLASS):
@ -1138,7 +1140,7 @@ class CmdName(ObjManipCommand):
caller.msg("No name defined!") caller.msg("No name defined!")
return return
if not (obj.access(caller, "control") or obj.access(caller, "edit")): 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 return
obj.username = newname obj.username = newname
obj.save() obj.save()

View file

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

View file

@ -60,16 +60,17 @@ class CmdLook(COMMAND_DEFAULT_CLASS):
""" """
Handle the looking. Handle the looking.
""" """
caller = self.caller
if not self.args: if not self.args:
target = self.caller.location target = caller.location
if not target: if not target:
self.caller.msg("You have no location to look at!") caller.msg("You have no location to look at!")
return return
else: else:
target = self.caller.search(self.args) target = caller.search(self.args, use_dbref=caller.check_permstring("Builders"))
if not target: if not target:
return return
self.msg(self.caller.at_look(target)) self.msg(caller.at_look(target))
class CmdNick(COMMAND_DEFAULT_CLASS): class CmdNick(COMMAND_DEFAULT_CLASS):
@ -175,10 +176,9 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
errstring += "Not a valid nick index." errstring += "Not a valid nick index."
else: else:
errstring += "Nick not found." errstring += "Nick not found."
if "delete" in switches or "del" in switches: if "delete" in switches or "del" in switches:
# clear the nick # 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) caller.nicks.remove(old_nickstring, category=nicktype)
string += "\nNick removed: '|w%s|n' -> |w%s|n." % (old_nickstring, old_replstring) string += "\nNick removed: '|w%s|n' -> |w%s|n." % (old_nickstring, old_replstring)
else: else:
@ -354,6 +354,8 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
caller.msg("You give %s to %s." % (to_give.key, target.key)) caller.msg("You give %s to %s." % (to_give.key, target.key))
to_give.move_to(target, quiet=True) to_give.move_to(target, quiet=True)
target.msg("%s gives you %s." % (caller.key, to_give.key)) 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): class CmdSetDesc(COMMAND_DEFAULT_CLASS):
@ -415,7 +417,50 @@ class CmdSay(COMMAND_DEFAULT_CLASS):
# Build the string to emit to neighbors. # Build the string to emit to neighbors.
emit_string = '%s says, "%s|n"' % (caller.name, speech) 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): class CmdPose(COMMAND_DEFAULT_CLASS):
@ -458,7 +503,8 @@ class CmdPose(COMMAND_DEFAULT_CLASS):
self.caller.msg(msg) self.caller.msg(msg)
else: else:
msg = "%s%s" % (self.caller.name, self.args) 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): class CmdAccess(COMMAND_DEFAULT_CLASS):

View file

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

View file

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

View file

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

View file

@ -38,7 +38,7 @@ class CommandTest(EvenniaTest):
Tests a command 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 Test a command by assigning all the needed
properties to cmdobj and running properties to cmdobj and running
@ -48,6 +48,10 @@ class CommandTest(EvenniaTest):
cmdobj.at_post_cmd() cmdobj.at_post_cmd()
The msgreturn value is compared to eventual The msgreturn value is compared to eventual
output sent to caller.msg in the game 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 caller = caller if caller else self.char1
receiver = receiver if receiver else caller receiver = receiver if receiver else caller
@ -60,9 +64,10 @@ class CommandTest(EvenniaTest):
cmdobj.session = SESSIONS.session_from_sessid(1) cmdobj.session = SESSIONS.session_from_sessid(1)
cmdobj.player = self.player cmdobj.player = self.player
cmdobj.raw_string = cmdobj.key + " " + args 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 # test
old_msg = receiver.msg old_msg = receiver.msg
returned_msg = ""
try: try:
receiver.msg = Mock() receiver.msg = Mock()
cmdobj.at_pre_cmd() cmdobj.at_pre_cmd()
@ -74,18 +79,23 @@ class CommandTest(EvenniaTest):
for name, args, kwargs in receiver.msg.mock_calls] 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 # 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] 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: 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()): if msg == "" and returned_msg or not returned_msg.startswith(msg.strip()):
sep1 = "\n" + "="*30 + "Wanted message" + "="*34 + "\n" sep1 = "\n" + "="*30 + "Wanted message" + "="*34 + "\n"
sep2 = "\n" + "="*30 + "Returned message" + "="*32 + "\n" sep2 = "\n" + "="*30 + "Returned message" + "="*32 + "\n"
sep3 = "\n" + "="*78 sep3 = "\n" + "="*78
retval = sep1 + msg.strip() + sep2 + returned_msg + sep3 retval = sep1 + msg.strip() + sep2 + returned_msg + sep3
raise AssertionError(retval) raise AssertionError(retval)
else:
returned_msg = "\n".join(stored_msg)
returned_msg = ansi.parse_ansi(returned_msg, strip_ansi=noansi).strip()
finally: finally:
receiver.msg = old_msg receiver.msg = old_msg
return returned_msg
# ------------------------------------------------------------ # ------------------------------------------------------------
# Individual module Tests # Individual module Tests
# ------------------------------------------------------------ # ------------------------------------------------------------
@ -119,6 +129,9 @@ class TestGeneral(CommandTest):
def test_say(self): def test_say(self):
self.call(general.CmdSay(), "Testing", "You say, \"Testing\"") 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): def test_access(self):
self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):") 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. # would also block dummyrunner, so it's not added as default.
_LATEST_FAILED_LOGINS = defaultdict(list) _LATEST_FAILED_LOGINS = defaultdict(list)
def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS): def _throttle(session, maxlim=None, timeout=None, storage=_LATEST_FAILED_LOGINS):
""" """
This will check the session's address against the This will check the session's address against the
@ -95,8 +97,8 @@ def create_guest_player(session):
bans = ServerConfig.objects.conf("server_bans") bans = ServerConfig.objects.conf("server_bans")
if bans and any(tup[2].match(session.address) for tup in bans if tup[2]): if bans and any(tup[2].match(session.address) for tup in bans if tup[2]):
# this is a banned IP! # this is a banned IP!
string = "{rYou have been banned and cannot continue from here." \ string = "|rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.{x" "\nIf you feel this ban is in error, please email an admin.|x"
session.msg(string) session.msg(string)
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return True, None return True, None
@ -118,14 +120,11 @@ def create_guest_player(session):
permissions = settings.PERMISSION_GUEST_DEFAULT permissions = settings.PERMISSION_GUEST_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS typeclass = settings.BASE_CHARACTER_TYPECLASS
ptypeclass = settings.BASE_GUEST_TYPECLASS ptypeclass = settings.BASE_GUEST_TYPECLASS
new_player = _create_player(session, playername, password, new_player = _create_player(session, playername, password, permissions, ptypeclass)
permissions, ptypeclass)
if new_player: if new_player:
_create_character(session, new_player, typeclass, _create_character(session, new_player, typeclass, home, permissions)
home, permissions)
return True, new_player return True, new_player
except Exception: except Exception:
# We are in the middle between logged in and -not, so we have # We are in the middle between logged in and -not, so we have
# to handle tracebacks ourselves at this point. If we don't, # 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. # check for too many login errors too quick.
if _throttle(session, maxlim=5, timeout=5*60): if _throttle(session, maxlim=5, timeout=5*60):
# timeout is 5 minutes. # 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 return None
# Match account name and check password # Match account name and check password
@ -167,15 +166,14 @@ def create_normal_player(session, name, password):
player.at_failed_login(session) player.at_failed_login(session)
return None return None
# Check IP and/or name bans # Check IP and/or name bans
bans = ServerConfig.objects.conf("server_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 or
any(tup[2].match(session.address) for tup in bans if tup[2])): any(tup[2].match(session.address) for tup in bans if tup[2])):
# this is a banned IP or name! # this is a banned IP or name!
string = "{rYou have been banned and cannot continue from here." \ string = "|rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.{x" "\nIf you feel this ban is in error, please email an admin.|x"
session.msg(string) session.msg(string)
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return None return None
@ -213,7 +211,7 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
# check for too many login errors too quick. # check for too many login errors too quick.
if _throttle(session, maxlim=5, timeout=5*60, storage=_LATEST_FAILED_LOGINS): if _throttle(session, maxlim=5, timeout=5*60, storage=_LATEST_FAILED_LOGINS):
# timeout is 5 minutes. # 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 return
args = self.args args = self.args
@ -237,12 +235,6 @@ class CmdUnconnectedConnect(COMMAND_DEFAULT_CLASS):
name, password = parts name, password = parts
player = create_normal_player(session, name, password) player = create_normal_player(session, name, password)
if player: 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) session.sessionhandler.login(session, player)
@ -264,7 +256,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
arg_regex = r"\s.*?|$" arg_regex = r"\s.*?|$"
def func(self): def func(self):
"Do checks and create account" """Do checks and create account"""
session = self.caller session = self.caller
args = self.args.strip() args = self.args.strip()
@ -309,12 +301,12 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
# Check IP and/or name bans # Check IP and/or name bans
bans = ServerConfig.objects.conf("server_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 or
any(tup[2].match(session.address) for tup in bans if tup[2])): any(tup[2].match(session.address) for tup in bans if tup[2])):
# this is a banned IP or name! # this is a banned IP or name!
string = "{rYou have been banned and cannot continue from here." \ string = "|rYou have been banned and cannot continue from here." \
"\nIf you feel this ban is in error, please email an admin.{x" "\nIf you feel this ban is in error, please email an admin.|x"
session.msg(string) session.msg(string)
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
return return
@ -327,8 +319,7 @@ class CmdUnconnectedCreate(COMMAND_DEFAULT_CLASS):
if new_player: if new_player:
if MULTISESSION_MODE < 2: if MULTISESSION_MODE < 2:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME) default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
_create_character(session, new_player, typeclass, _create_character(session, new_player, typeclass, default_home, permissions)
default_home, permissions)
# tell the caller everything went well. # tell the caller everything went well.
string = "A new account '%s' was created. Welcome!" string = "A new account '%s' was created. Welcome!"
if " " in playername: if " " in playername:
@ -361,7 +352,7 @@ class CmdUnconnectedQuit(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"Simply close the connection." """Simply close the connection."""
session = self.caller session = self.caller
session.sessionhandler.disconnect(session, "Good bye! Disconnecting.") session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
@ -383,7 +374,7 @@ class CmdUnconnectedLook(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"Show the connect screen." """Show the connect screen."""
connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE) connection_screen = utils.random_string_from_module(CONNECTION_SCREEN_MODULE)
if not connection_screen: if not connection_screen:
connection_screen = "No connection screen found. Please contact an admin." connection_screen = "No connection screen found. Please contact an admin."
@ -405,25 +396,25 @@ class CmdUnconnectedHelp(COMMAND_DEFAULT_CLASS):
locks = "cmd:all()" locks = "cmd:all()"
def func(self): def func(self):
"Shows help" """Shows help"""
string = \ string = \
""" """
You are not yet logged into the game. Commands available at this point: You are not yet logged into the game. Commands available at this point:
{wcreate{n - create a new account |wcreate|n - create a new account
{wconnect{n - connect with an existing account |wconnect|n - connect with an existing account
{wlook{n - re-show the connection screen |wlook|n - re-show the connection screen
{whelp{n - show this help |whelp|n - show this help
{wencoding{n - change the text encoding to match your client |wencoding|n - change the text encoding to match your client
{wscreenreader{n - make the server more suitable for use with screen readers |wscreenreader|n - make the server more suitable for use with screen readers
{wquit{n - abort the connection |wquit|n - abort the connection
First create an account e.g. with {wcreate 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 (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 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) self.caller.msg(string)
@ -479,10 +470,10 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
pencoding = self.session.protocol_flags.get("ENCODING", None) pencoding = self.session.protocol_flags.get("ENCODING", None)
string = "" string = ""
if pencoding: 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 encodings = settings.ENCODINGS
if 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: if not string:
string = "No encodings found." string = "No encodings found."
else: else:
@ -492,7 +483,8 @@ class CmdUnconnectedEncoding(COMMAND_DEFAULT_CLASS):
try: try:
utils.to_str(utils.to_unicode("test-string"), encoding=encoding) utils.to_str(utils.to_unicode("test-string"), encoding=encoding)
except LookupError: 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: else:
self.session.protocol_flags["ENCODING"] = encoding self.session.protocol_flags["ENCODING"] = encoding
string = "Your custom text encoding was changed from '|w%s|n' to '|w%s|n'." % (old_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.session.sessionhandler.session_portal_sync(self.session)
self.caller.msg(string.strip()) self.caller.msg(string.strip())
class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS): class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
""" """
Activate screenreader mode. Activate screenreader mode.
@ -515,21 +508,20 @@ class CmdUnconnectedScreenreader(COMMAND_DEFAULT_CLASS):
aliases = "@screenreader" aliases = "@screenreader"
def func(self): def func(self):
"Flips screenreader setting." """Flips screenreader setting."""
new_setting = not self.session.protocol_flags.get("SCREENREADER", False) new_setting = not self.session.protocol_flags.get("SCREENREADER", False)
self.session.protocol_flags["SCREENREADER"] = new_setting self.session.protocol_flags["SCREENREADER"] = new_setting
string = "Screenreader mode turned {w%s{n." % ("on" if new_setting else "off") string = "Screenreader mode turned |w%s|n." % ("on" if new_setting else "off")
self.caller.msg(string) self.caller.msg(string)
self.session.sessionhandler.session_portal_sync(self.session) 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. Helper function, creates a player of the specified typeclass.
""" """
try: try:
new_player = create.create_player(playername, None, password, new_player = create.create_player(playername, email, password, permissions=permissions, typeclass=typeclass)
permissions=permissions, typeclass=typeclass)
except Exception as e: except Exception as e:
session.msg("There was an error creating the Player:\n%s\n If this problem persists, contact an admin." % 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 # join the new player to the public channel
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"]) pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
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 string = "New player '%s' could not connect to public channel!" % new_player.key
logger.log_err(string) logger.log_err(string)
return new_player 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. This is meant for Guest and MULTISESSION_MODE < 2 situations.
""" """
try: try:
new_character = create.create_object(typeclass, key=new_player.key, new_character = create.create_object(typeclass, key=new_player.key, home=home, permissions=permissions)
home=home, permissions=permissions)
# set playable character list # set playable character list
new_player.db._playable_characters.append(new_character) 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] listening = [ob for ob in subs if ob.is_connected and ob not in self.mutelist]
if subs: if subs:
# display listening subscribers in bold # 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: else:
string = "<None>" string = "<None>"
return string return string
@ -126,7 +126,6 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
return True return True
return False return False
def connect(self, subscriber): def connect(self, subscriber):
""" """
Connect the user to this channel. This checks access. Connect the user to this channel. This checks access.
@ -254,7 +253,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
try: try:
# note our addition of the from_channel keyword here. This could be checked # note our addition of the from_channel keyword here. This could be checked
# by a custom player.msg() to treat channel-receives differently. # 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: except AttributeError as e:
logger.log_trace("%s\nCannot send msg to '%s'." % (e, entity)) 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) self.msg(message, senders=senders, header=header, keep_log=False)
# hooks # hooks
def channel_prefix(self, msg=None, emit=False): def channel_prefix(self, msg=None, emit=False):
@ -376,7 +374,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
message accordingly. message accordingly.
Args: 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. sender_string (str): The name of the sender/poser.
Returns: Returns:
@ -426,7 +424,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
Hook method. Formats a message body for display. Hook method. Formats a message body for display.
Args: 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. emit (bool, optional): The message is agnostic of senders.
Returns: Returns:
@ -475,7 +473,7 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
value, leaving the channel will be aborted. value, leaving the channel will be aborted.
Args: Args:
joiner (object): The joining object. leaver (object): The leaving object.
Returns: Returns:
should_leave (bool): If `False`, channel parting is aborted. 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. Hook method. Runs right after an object or player leaves a channel.
Args: Args:
joiner (object): The joining object. leaver (object): The leaving object.
""" """
pass pass

View file

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

View file

@ -2,32 +2,25 @@
Contribution - Griatch 2011 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 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 This is a simple character creation commandset for the Player level.
test this together with menu_login, which doesn't create a Character It shows some more info and gives the Player the option to create a
on its own. This shows some more info and gives the Player the option character without any more customizations than their name (further
to create a character without any more customizations than their name options are unique for each game anyway).
(further options are unique for each game anyway).
Since this extends the OOC cmdset, logging in from the menu will In MULTISESSION_MODEs 0 and 1, you will automatically log into an
automatically drop the Player into this cmdset unless they logged off existing Character. When using `@ooc` you will then end up in this
while puppeting a Character already before. cmdset.
Installation: Installation:
Read the instructions in contrib/examples/cmdset.py in order to create Import this module to `mygame/commands/default_cmdsets.py` and
a new default cmdset module for Evennia to use (copy the template up add `chargen.OOCCMdSetCharGen` to the `PlayerCmdSet` class
one level, and change the settings file's relevant variables to point (it says where to add it). Reload.
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)
""" """
@ -179,7 +172,7 @@ class CmdOOCCharacterCreate(Command):
else: else:
avail_chars = [new_character.id] avail_chars = [new_character.id]
self.caller.db._character_dbrefs = avail_chars 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): 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 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 helper functions in this module in your own code. The calendar can be
specified in your settings file by adding and setting custom values customized by adding the `TIME_UNITS` dictionary to your settings
for one or more of the variables `TIME_SECS_PER_MIN`, file. This maps unit names to their length, expressed in the smallest
`TIME_MINS_PER_HOUR`, `TIME_DAYS_PER_WEEK`, `TIME_WEEKS_PER_MONTH` and unit. Here's the default as an example:
`TIME_MONTHS_PER_YEAR`. These are all given in seconds and whereas
they are called "week", "month" etc these names could represent TIME_UNITS = {
whatever fits your game. You can also set `TIME_UNITS` to a dict "sec": 1,
mapping the name of a unit to its length in seconds (like `{"min": "min": 60,
60, ...}. If not given, sane defaults will be used. "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 django.conf import settings
from evennia import DefaultScript from evennia import DefaultScript
from evennia.utils.create import create_script 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 # The game time speedup / slowdown relative real time
TIMEFACTOR = settings.TIME_FACTOR TIMEFACTOR = settings.TIME_FACTOR
# Game-time units, in game time seconds. These are supplied as a # These are the unit names understood by the scheduler.
# convenient measure for determining the current in-game time, e.g. # Each unit must be consistent and expressed in seconds.
# 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.
UNITS = getattr(settings, "TIME_UNITS", { UNITS = getattr(settings, "TIME_UNITS", {
"sec": SEC, # default custom calendar
"min": MIN, "sec": 1,
"hr": HOUR, "min": 60,
"hour": HOUR, "hr": 60 * 60,
"day": DAY, "hour": 60 * 60,
"week": WEEK, "day": 60 * 60 * 24,
"month": MONTH, "week": 60 * 60 * 24 * 7,
"year": YEAR, "month": 60 * 60 * 24 * 7 * 4,
"yr": YEAR, "yr": 60 * 60 * 24 * 7 * 4 * 12,
}) "year": 60 * 60 * 24 * 7 * 4 * 12, })
def time_to_tuple(seconds, *divisors): def time_to_tuple(seconds, *divisors):
@ -92,7 +91,8 @@ def gametime_to_realtime(format=False, **kwargs):
Kwargs: Kwargs:
format (bool): Formatting the output. 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: Returns:
time (float or tuple): The realtime difference or the same time (float or tuple): The realtime difference or the same
@ -163,7 +163,7 @@ def custom_gametime(absolute=False):
week, day, hour, minute, second). week, day, hour, minute, second).
""" """
current = gametime(absolute=absolute) current = gametime.gametime(absolute=absolute)
units = sorted(set(UNITS.values()), reverse=True) units = sorted(set(UNITS.values()), reverse=True)
del units[-1] del units[-1]
return time_to_tuple(current, *units) 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. 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) units = sorted(set(UNITS.values()), reverse=True)
# Remove seconds from the tuple # Remove seconds from the tuple
del units[-1] del units[-1]
@ -232,25 +232,26 @@ def schedule(callback, repeat=False, **kwargs):
""" """
Call the callback when the game time is up. 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: Args:
callback (function): the callback function that will be called [1]. callback (function): The callback function that will be called. This
repeat (bool, optional): should the callback be called regularly? must be a top-level function since the script will be persistent.
times (str: int): the time to call the callback. repeat (bool, optional): Should the callback be called regularly?
day, month, etc (str: int): The time units to call the callback; should
[1] The callback must be a top-level function, since the script will match the keys of TIME_UNITS.
be persistent.
Returns: 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) 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.utils import create, logger, utils, ansi
from evennia.commands.default.muxcommand import MuxCommand from evennia.commands.default.muxcommand import MuxCommand
from evennia.commands.cmdhandler import CMD_LOGINSTART from evennia.commands.cmdhandler import CMD_LOGINSTART
from evennia.commands.default import unloggedin as default_unloggedin # Used in CmdUnconnectedCreate
# limit symbol import for API # limit symbol import for API
__all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate", __all__ = ("CmdUnconnectedConnect", "CmdUnconnectedCreate",
@ -161,99 +162,77 @@ class CmdUnconnectedCreate(MuxCommand):
"""Do checks and create account""" """Do checks and create account"""
session = self.caller session = self.caller
try: try:
playername, email, password = self.playerinfo playername, email, password = self.playerinfo
except ValueError: except ValueError:
string = "\n\r Usage (without <>): create \"<playername>\" <email> <password>" string = "\n\r Usage (without <>): create \"<playername>\" <email> <password>"
session.msg(string) session.msg(string)
return 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: if not email or not password:
session.msg("\n\r You have to supply an e-mail address followed by a password.") session.msg("\n\r You have to supply an e-mail address followed by a password.")
return return
if not utils.validate_email_address(email): if not utils.validate_email_address(email):
# check so the email at least looks ok. # check so the email at least looks ok.
session.msg("'%s' is not a valid e-mail address." % email) session.msg("'%s' is not a valid e-mail address." % email)
return return
# sanity checks
# Run sanity and security checks if not re.findall(r"^[\w. @+\-']+$", playername) or not (0 < len(playername) <= 30):
# this echoes the restrictions made by django's auth
if PlayerDB.objects.filter(username=playername): # module (except not allowing spaces, for convenience of
# player already exists # 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) session.msg("Sorry, there is already a player with the name '%s'." % playername)
return return
if PlayerDB.objects.get_player_from_email(email): if PlayerDB.objects.get_player_from_email(email):
# email already set on a player # email already set on a player
session.msg("Sorry, there is already a player with that email address.") session.msg("Sorry, there is already a player with that email address.")
return return
if len(password) < 3: # Reserve playernames found in GUEST_LIST
# too short password if settings.GUEST_LIST and playername.lower() in (guest.lower() for guest in settings.GUEST_LIST):
string = "Your password must be at least 3 characters or longer." string = "\n\r That name is reserved. Please choose another Playername."
string += "\n\rFor best security, make it at least 8 characters long, "
string += "avoid making it a real word and mix numbers into it."
session.msg(string) session.msg(string)
return 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. # everything's ok. Create the new player account.
try: try:
default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
typeclass = settings.BASE_CHARACTER_TYPECLASS
permissions = settings.PERMISSION_PLAYER_DEFAULT permissions = settings.PERMISSION_PLAYER_DEFAULT
typeclass = settings.BASE_CHARACTER_TYPECLASS
try: new_player = default_unloggedin._create_player(session, playername, password, permissions, email=email)
new_player = create.create_player(playername, email, password, permissions=permissions) if new_player:
if MULTISESSION_MODE < 2:
except Exception as e: default_home = ObjectDB.objects.get_id(settings.DEFAULT_HOME)
session.msg("There was an error creating the default Player/Character:\n%s\n" default_unloggedin._create_character(session, new_player, typeclass, default_home, permissions)
" If this problem persists, contact an admin." % e) # tell the caller everything went well.
logger.log_trace() string = "A new account '%s' was created. Welcome!"
return if " " in playername:
string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
# This needs to be set so the engine knows this player is else:
# logging in for the first time. (so it knows to call the right string += "\n\nYou can now log with the command 'connect %s <your password>'."
# hooks during login later) session.msg(string % (playername, email))
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))
except Exception: except Exception:
# We are in the middle between logged in and -not, so we have # 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. # we won't see any errors at all.
session.msg("An error occurred. Please e-mail an admin if the problem persists.") session.msg("An error occurred. Please e-mail an admin if the problem persists.")
logger.log_trace() logger.log_trace()
raise
class CmdUnconnectedQuit(MuxCommand): class CmdUnconnectedQuit(MuxCommand):
@ -276,8 +256,7 @@ class CmdUnconnectedQuit(MuxCommand):
def func(self): def func(self):
"""Simply close the connection.""" """Simply close the connection."""
session = self.caller session = self.caller
session.msg("Good bye! Disconnecting ...") session.sessionhandler.disconnect(session, "Good bye! Disconnecting.")
session.session_disconnect()
class CmdUnconnectedLook(MuxCommand): class CmdUnconnectedLook(MuxCommand):

View file

@ -68,6 +68,7 @@ Installation/testing:
""" """
from __future__ import division from __future__ import division
import datetime
import re import re
from django.conf import settings from django.conf import settings
from evennia import DefaultRoom from evennia import DefaultRoom
@ -129,12 +130,13 @@ class ExtendedRoom(DefaultRoom):
""" """
Calculate the current time and season ids. Calculate the current time and season ids.
""" """
# get the current time as parts of year and parts of day # get the current time as parts of year and parts of day.
# returns a tuple (years,months,weeks,days,hours,minutes,sec) # we assume a standard calendar here and use 24h format.
time = gametime.gametime(format=True) timestamp = gametime.gametime(absolute=True)
month, hour = time[1], time[4] # note that fromtimestamp includes the effects of server time zone!
season = float(month) / MONTHS_PER_YEAR datestamp = datetime.datetime.fromtimestamp(timestamp)
timeslot = float(hour) / HOURS_PER_DAY season = float(datestamp.month) / MONTHS_PER_YEAR
timeslot = float(datestamp.hour) / HOURS_PER_DAY
# figure out which slots these represent # figure out which slots these represent
if SEASONAL_BOUNDARIES[0] <= season < SEASONAL_BOUNDARIES[1]: 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 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 will result in "Tom falls on his|her|its|their face with a thud"
on the gender of the object being messaged. Default gender is depending on the gender of the object being messaged. Default gender
"neutral". is "ambiguous" (they).
To use, have DefaultCharacter inherit from this, or change To use, have DefaultCharacter inherit from this, or change
setting.DEFAULT_CHARACTER to point to this class. setting.DEFAULT_CHARACTER to point to this class.
The `@gender` command needs to be added to the default cmdset The `@gender` command needs to be added to the default cmdset before
before it becomes available. it becomes available.
""" """
@ -50,7 +50,7 @@ _GENDER_PRONOUN_MAP = {"male": {"s": "he",
"p": "their", "p": "their",
"a": "theirs"} "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 # in-game command for setting the gender
@ -59,7 +59,7 @@ class SetGender(Command):
Sets gender on yourself Sets gender on yourself
Usage: Usage:
@gender male|female|neutral|ambiguous @gender male||female||neutral||ambiguous
""" """
key = "@gender" key = "@gender"
@ -73,10 +73,10 @@ class SetGender(Command):
caller = self.caller caller = self.caller
arg = self.args.strip().lower() arg = self.args.strip().lower()
if not arg in ("male", "female", "neutral", "ambiguous"): 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 return
caller.db.gender = arg 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 # Gender-aware character class
@ -103,10 +103,10 @@ class GenderCharacter(DefaultCharacter):
regex_match (MatchObject): the regular expression match. regex_match (MatchObject): the regular expression match.
Notes: Notes:
- `{s`, `{S`: Subjective form: he, she, it, He, She, It, They - `|s`, `|S`: Subjective form: he, she, it, He, She, It, They
- `{o`, `{O`: Objective form: him, her, it, Him, Her, It, Them - `|o`, `|O`: Objective form: him, her, it, Him, Her, It, Them
- `{p`, `{P`: Possessive form: his, her, its, His, Her, Its, Their - `|p`, `|P`: Possessive form: his, her, its, His, Her, Its, Their
- `{a`, `{A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs - `|a`, `|A`: Absolute Possessive form: his, hers, its, His, Hers, Its, Theirs
""" """
typ = regex_match.group()[1] # "s", "O" etc typ = regex_match.group()[1] # "s", "O" etc
@ -134,5 +134,8 @@ class GenderCharacter(DefaultCharacter):
""" """
# pre-process the text before continuing # 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) 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. A simple Brandymail style @mail system that uses the Msg class from Evennia Core.
Installation: 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 import default_cmds
from evennia.utils import create, evtable from evennia.utils import create, evtable, make_iter
from evennia.comms.models import Msg from evennia.comms.models import Msg
@ -48,7 +52,7 @@ class CmdMail(default_cmds.MuxCommand):
@mail 2 @mail 2
@mail Griatch=New mail/Hey man, I am sending you a message! @mail Griatch=New mail/Hey man, I am sending you a message!
@mail/delete 6 @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! @mail/reply 9=Thanks for the info!
""" """
key = "@mail" key = "@mail"
@ -56,9 +60,67 @@ class CmdMail(default_cmds.MuxCommand):
lock = "cmd:all()" lock = "cmd:all()"
help_category = "General" 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): def func(self):
subject = "" subject = ""
body = "" body = ""
if self.switches or self.args: if self.switches or self.args:
if "delete" in self.switches: if "delete" in self.switches:
try: try:
@ -66,12 +128,15 @@ class CmdMail(default_cmds.MuxCommand):
self.caller.msg("No Message ID given. Unable to delete.") self.caller.msg("No Message ID given. Unable to delete.")
return return
else: else:
if self.get_all_mail()[int(self.lhs) - 1]: all_mail = self.get_all_mail()
self.get_all_mail()[int(self.lhs) - 1].delete() mind = int(self.lhs) - 1
if all_mail[mind]:
all_mail[mind].delete()
self.caller.msg("Message %s deleted" % self.lhs) self.caller.msg("Message %s deleted" % self.lhs)
else: else:
self.caller.msg("That message does not exist.") raise IndexError
return except IndexError:
self.caller.msg("That message does not exist.")
except ValueError: except ValueError:
self.caller.msg("Usage: @mail/delete <message ID>") self.caller.msg("Usage: @mail/delete <message ID>")
elif "forward" in self.switches: elif "forward" in self.switches:
@ -83,29 +148,33 @@ class CmdMail(default_cmds.MuxCommand):
self.caller.msg("You must define a message to forward.") self.caller.msg("You must define a message to forward.")
return return
else: else:
all_mail = self.get_all_mail()
if "/" in self.rhs: if "/" in self.rhs:
message_number, message = self.rhs.split("/") message_number, message = self.rhs.split("/")
if self.get_all_mail()[int(message_number) - 1]: mind = int(message_number) - 1
old_message = self.get_all_mail()[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, message + "\n---- Original Message ----\n" + old_message.message,
self.caller) self.caller)
self.caller.msg("Message forwarded.") self.caller.msg("Message forwarded.")
else: else:
self.caller.msg("Message does not exist.") raise IndexError
return
else: else:
if self.get_all_mail()[int(self.rhs) - 1]: mind = int(self.rhs) - 1
old_message = self.get_all_mail()[int(self.rhs) - 1] if all_mail[mind]:
self.send_mail(self.lhslist, "FWD: " + old_message.header, 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) "\n---- Original Message ----\n" + old_message.message, self.caller)
self.caller.msg("Message forwarded.") self.caller.msg("Message forwarded.")
old_message.tags.remove("u", category="mail") old_message.tags.remove("u", category="mail")
old_message.tags.add("f", category="mail") old_message.tags.add("f", category="mail")
else: else:
self.caller.msg("Message does not exist.") raise IndexError
return except IndexError:
self.caller.msg("Message does not exixt.")
except ValueError: except ValueError:
self.caller.msg("Usage: @mail/forward <player list>=<#>[/<Message>]") self.caller.msg("Usage: @mail/forward <player list>=<#>[/<Message>]")
elif "reply" in self.switches: elif "reply" in self.switches:
@ -117,29 +186,33 @@ class CmdMail(default_cmds.MuxCommand):
self.caller.msg("You must supply a reply message") self.caller.msg("You must supply a reply message")
return return
else: else:
if self.get_all_mail()[int(self.lhs) - 1]: all_mail = self.get_all_mail()
old_message = self.get_all_mail()[int(self.lhs) - 1] 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.send_mail(old_message.senders, "RE: " + old_message.header,
self.rhs + "\n---- Original Message ----\n" + old_message.message, self.caller) self.rhs + "\n---- Original Message ----\n" + old_message.message, self.caller)
old_message.tags.remove("u", category="mail") old_message.tags.remove("u", category="mail")
old_message.tags.add("r", category="mail") old_message.tags.add("r", category="mail")
return return
else: else:
self.caller.msg("Message does not exist.") raise IndexError
return except IndexError:
self.caller.msg("Message does not exist.")
except ValueError: except ValueError:
self.caller.msg("Usage: @mail/reply <#>=<message>") self.caller.msg("Usage: @mail/reply <#>=<message>")
else: else:
# normal send
if self.rhs: if self.rhs:
if "/" in self.rhs: if "/" in self.rhs:
subject, body = self.rhs.split("/", 1) subject, body = self.rhs.split("/", 1)
else: else:
body = self.rhs 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: else:
try: try:
message = self.get_all_mail()[int(self.lhs) - 1] 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) self.caller.msg("'%s' is not a valid mail id." % self.lhs)
return return
@ -176,47 +249,8 @@ class CmdMail(default_cmds.MuxCommand):
table.reformat_column(4, width=7) table.reformat_column(4, width=7)
self.caller.msg(_HEAD_CHAR * _WIDTH) self.caller.msg(_HEAD_CHAR * _WIDTH)
self.caller.msg(table) self.caller.msg(unicode(table))
self.caller.msg(_HEAD_CHAR * _WIDTH) self.caller.msg(_HEAD_CHAR * _WIDTH)
else: 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 from random import randint
import random import random
# A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an # A map with a temple (▲) amongst mountains (n,∩) in a forest (♣,♠) on an
# island surrounded by water (≈). By giving no instructions for the water # island surrounded by water (≈). By giving no instructions for the water
# characters we effectively skip it and create no rooms for those squares. # 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] 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): class CmdMapBuilder(COMMAND_DEFAULT_CLASS):
""" """
Build a map from a 2D ASCII map. 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. # Pass map and legend to the build function.
build_map(caller, game_map, legend, iterations, build_exits) 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 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 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: Installation:

View file

@ -135,9 +135,10 @@ class CmdStop(Command):
stored deferred from the exit traversal above. stored deferred from the exit traversal above.
""" """
currently_moving = self.caller.ndb.currently_moving currently_moving = self.caller.ndb.currently_moving
if currently_moving: if currently_moving and not currently_moving.called:
currently_moving.cancel() currently_moving.cancel()
self.caller.msg("You stop moving.") 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: else:
self.caller.msg("You are not moving.") self.caller.msg("You are not moving.")

View file

@ -1,12 +1,13 @@
# -*- coding: utf-8 -*-
""" """
Testing suite for contrib folder Testing suite for contrib folder
""" """
from django.conf import settings import datetime
from evennia.commands.default.tests import CommandTest from evennia.commands.default.tests import CommandTest
from evennia.utils.test_resources import EvenniaTest from evennia.utils.test_resources import EvenniaTest
from mock import Mock from mock import Mock, patch
# Testing of rplanguage module # Testing of rplanguage module
@ -168,44 +169,52 @@ class TestRPSystem(EvenniaTest):
# Testing of ExtendedRoom contrib # Testing of ExtendedRoom contrib
from django.conf import settings
from evennia.contrib import extended_room from evennia.contrib import extended_room
from evennia import gametime from evennia import gametime
from evennia.objects.objects import DefaultRoom from evennia.objects.objects import DefaultRoom
# mock gametime to return 7th month, 10 in morning class ForceUTCDatetime(datetime.datetime):
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
"""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): class TestExtendedRoom(CommandTest):
room_typeclass = extended_room.ExtendedRoom room_typeclass = extended_room.ExtendedRoom
DETAIL_DESC = "A test detail." DETAIL_DESC = "A test detail."
SUMMER_DESC = "A summer description." SPRING_DESC = "A spring description."
OLD_DESC = "Old description." OLD_DESC = "Old description."
settings.TIME_ZONE = "UTC"
def setUp(self): def setUp(self):
super(TestExtendedRoom, self).setUp() super(TestExtendedRoom, self).setUp()
self.room1.ndb.last_timeslot = "night" self.room1.ndb.last_timeslot = "afternoon"
self.room1.ndb.last_season = "winter" self.room1.ndb.last_season = "winter"
self.room1.db.details = {'testdetail': self.DETAIL_DESC} 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 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): def test_return_appearance(self):
# get the appearance of a non-extended room for contrast purposes # get the appearance of a non-extended room for contrast purposes
old_desc = DefaultRoom.return_appearance(self.room1, self.char1) old_desc = DefaultRoom.return_appearance(self.room1, self.char1)
# the new appearance should be the old one, but with the desc switched # 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(old_desc.replace(self.OLD_DESC, self.SPRING_DESC),
self.assertEqual("summer", self.room1.ndb.last_season) self.room1.return_appearance(self.char1))
self.assertEqual("morning", self.room1.ndb.last_timeslot) self.assertEqual("spring", self.room1.ndb.last_season)
self.assertEqual("evening", self.room1.ndb.last_timeslot)
def test_return_detail(self): def test_return_detail(self):
self.assertEqual(self.DETAIL_DESC, self.room1.return_detail("testdetail")) self.assertEqual(self.DETAIL_DESC, self.room1.return_detail("testdetail"))
def test_cmdextendedlook(self): 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(), "testdetail", self.DETAIL_DESC)
self.call(extended_room.CmdExtendedLook(), "nonexistent", "Could not find 'nonexistent'.") 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:") self.call(extended_room.CmdExtendedDesc(), "", "Descriptions on Room:")
def test_cmdgametime(self): 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 # 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.CmdTradeHelp(), "", "Trading commands\n", caller=self.char1)
self.call(barter.CmdFinish(), ": Ending.", "You say, \"Ending.\"\n [You aborted trade. No deal was made.]") 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.contrib import wilderness
from evennia import DefaultCharacter from evennia import DefaultCharacter
class TestWilderness(EvenniaTest): class TestWilderness(EvenniaTest):
def setUp(self): def setUp(self):
@ -426,3 +435,323 @@ class TestWilderness(EvenniaTest):
for direction, correct_loc in directions.iteritems(): # Not compatible with Python 3 for direction, correct_loc in directions.iteritems(): # Not compatible with Python 3
new_loc = wilderness.get_new_coordinates(loc, direction) new_loc = wilderness.get_new_coordinates(loc, direction)
self.assertEquals(new_loc, correct_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. character make observations and notices at irregular intervals.
To test, use 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 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! make sure to put it on yourself or you won't see any messages!

View file

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

View file

@ -279,7 +279,7 @@ class TutorialRoom(DefaultRoom):
# These are rainy weather strings # These are rainy weather strings
WEATHER_STRINGS = ( WEATHER_STRINGS = (
"The rain coming down from the iron-grey sky intensifies.", "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.", "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.", "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.", "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 # subscribe ourselves to a ticker to repeatedly call the hook
# "update_weather" on this object. The interval is randomized # "update_weather" on this object. The interval is randomized
# so as to not have all weather rooms update at the same time. # so as to not have all weather rooms update at the same time.
interval = random.randint(50, 70) self.db.interval = random.randint(50, 70)
TICKER_HANDLER.add(interval=interval, callback=self.update_weather, idstring="tutorial") TICKER_HANDLER.add(interval=self.db.interval, callback=self.update_weather, idstring="tutorial")
# this is parsed by the 'tutorial' command on TutorialRooms. # this is parsed by the 'tutorial' command on TutorialRooms.
self.db.tutorial_info = \ self.db.tutorial_info = \
"This room has a Script running that has it echo a weather-related message at irregular intervals." "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 = ( BRIDGE_WEATHER = (
"The rain intensifies, making the planks of the bridge even more slippery.", "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 rainfall eases a bit and the sky momentarily brightens.",
"The bridge shakes under the thunder of a closeby thunder strike.", "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.", "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 #: server/initial_setup.py:29
msgid "" msgid ""
"\n" "\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" "you need\n"
"help, want to contribute, report issues or just join the community.\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 " "As Player #1 you can create a demo/tutorial area with |w@batchcommand "
"tutorial_world.build{n.\n" "tutorial_world.build|n.\n"
" " " "
msgstr "" msgstr ""
"\n" "\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" "evennia.com si vous avez besoin\n"
"d'aide, si vous voulez contribuer, rapporter des problèmes ou faire partie " "d'aide, si vous voulez contribuer, rapporter des problèmes ou faire partie "
"de la communauté.\n" "de la communauté.\n"
"En tant que Joueur #1 vous pouvez créer une zone de démo/tutoriel avec " "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 #: server/initial_setup.py:102

View file

@ -221,16 +221,16 @@ msgstr "Aggiorna l'handler del canale."
#: .\server\initial_setup.py:29 #: .\server\initial_setup.py:29
msgid "" msgid ""
"\n" "\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" "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 "" msgstr ""
"\n" "\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" "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" "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 #: .\server\initial_setup.py:99

View file

@ -208,19 +208,19 @@ msgstr ""
#: server/initial_setup.py:30 #: server/initial_setup.py:30
msgid "" msgid ""
"\n" "\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" "you need\n"
"help, want to contribute, report issues or just join the community.\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 " "As Player #1 you can create a demo/tutorial area with |w@batchcommand "
"tutorial_world.build{n.\n" "tutorial_world.build|n.\n"
" " " "
msgstr "" msgstr ""
"\n" "\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" "se você precisar de ajuda, desejar contribuir, reportar bugs ou apenas\n"
"juntar-se à comunidade.\n" "juntar-se à comunidade.\n"
"Como Player #1 você pode criar uma área demonstrativa/tutorial digitando\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 #: server/initial_setup.py:103

View file

@ -214,11 +214,11 @@ msgstr "Detta är en generisk lagringskontainer."
#: server/initial_setup.py:29 #: server/initial_setup.py:29
msgid "" msgid ""
"\n" "\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" "you need\n"
"help, want to contribute, report issues or just join the community.\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 " "As Player #1 you can create a demo/tutorial area with |w@batchcommand "
"tutorial_world.build{n.\n" "tutorial_world.build|n.\n"
" " " "
msgstr "" msgstr ""

View file

@ -40,16 +40,21 @@ class ObjectCreateForm(forms.ModelForm):
fields = '__all__' fields = '__all__'
db_key = forms.CharField(label="Name/Key", db_key = forms.CharField(label="Name/Key",
widget=forms.TextInput(attrs={'size': '78'}), 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", db_typeclass_path = forms.CharField(label="Typeclass",
initial=settings.BASE_OBJECT_TYPECLASS, initial=settings.BASE_OBJECT_TYPECLASS,
widget=forms.TextInput(attrs={'size': '78'}), 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", db_cmdset_storage = forms.CharField(label="CmdSet",
initial="", initial="",
required=False, required=False,
widget=forms.TextInput(attrs={'size': '78'}), 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') raw_id_fields = ('db_destination', 'db_location', 'db_home')
@ -63,8 +68,10 @@ class ObjectEditForm(ObjectCreateForm):
fields = '__all__' fields = '__all__'
db_lock_storage = forms.CharField(label="Locks", db_lock_storage = forms.CharField(label="Locks",
required=False, required=False,
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}), 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);...") 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): class ObjectDBAdmin(admin.ModelAdmin):
@ -90,15 +97,15 @@ class ObjectDBAdmin(admin.ModelAdmin):
form = ObjectEditForm form = ObjectEditForm
fieldsets = ( fieldsets = (
(None, { (None, {
'fields': (('db_key','db_typeclass_path'), ('db_lock_storage', ), 'fields': (('db_key', 'db_typeclass_path'), ('db_lock_storage', ),
('db_location', 'db_home'), 'db_destination','db_cmdset_storage' ('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage'
)}), )}),
) )
add_form = ObjectCreateForm add_form = ObjectCreateForm
add_fieldsets = ( add_fieldsets = (
(None, { (None, {
'fields': (('db_key','db_typeclass_path'), 'fields': (('db_key', 'db_typeclass_path'),
('db_location', 'db_home'), 'db_destination', 'db_cmdset_storage' ('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. # Try to use a custom way to parse id-tagged multimatches.
class ObjectDBManager(TypedObjectManager): class ObjectDBManager(TypedObjectManager):
""" """
This ObjectManager implements methods for searching This ObjectManager implements methods for searching
@ -79,11 +80,13 @@ class ObjectDBManager(TypedObjectManager):
if dbref: if dbref:
return dbref return dbref
# not a dbref. Search by name. # 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: if exact:
return self.filter(cand_restriction & Q(db_player__username__iexact=ostring)) return self.filter(cand_restriction & Q(db_player__username__iexact=ostring))
else: # fuzzy matching else: # fuzzy matching
ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)).values_list("db_key", flat=True) ply_cands = self.filter(cand_restriction & Q(playerdb__username__istartswith=ostring)
).values_list("db_key", flat=True)
if candidates: if candidates:
index_matches = string_partial_matching(ply_cands, ostring, ret_index=True) 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] return [obj for ind, obj in enumerate(make_iter(candidates)) if ind in index_matches]
@ -103,7 +106,8 @@ class ObjectDBManager(TypedObjectManager):
Returns: Returns:
matches (list): The matching objects. 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)) return self.filter(cand_restriction & Q(db_key__iexact=oname, db_typeclass_path__exact=otypeclass_path))
# attr/property related # attr/property related
@ -121,7 +125,9 @@ class ObjectDBManager(TypedObjectManager):
matches (list): All objects having the given attribute_name defined at all. 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))) return list(self.filter(cand_restriction & Q(db_attributes__db_key=attribute_name)))
@returns_typeclass_list @returns_typeclass_list
@ -144,20 +150,23 @@ class ObjectDBManager(TypedObjectManager):
cannot be indexed, searching by Attribute key is to be preferred whenever possible. 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() 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)): 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: 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 global _ATTR
if not _ATTR: if not _ATTR:
from evennia.typeclasses.models import Attribute as _ATTR from evennia.typeclasses.models import Attribute as _ATTR
cands = list(self.filter(cand_restriction & type_restriction & Q(db_attributes__db_key=attribute_name))) 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) return chain(*results)
@returns_typeclass_list @returns_typeclass_list
@ -174,8 +183,9 @@ class ObjectDBManager(TypedObjectManager):
""" """
property_name = "db_%s" % property_name.lstrip('db_') 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() cand_restriction = candidates is not None and Q(pk__in=[_GA(obj, "id") for obj in make_iter(candidates)
querykwargs = {property_name:None} if obj]) or Q()
querykwargs = {property_name: None}
try: try:
return list(self.filter(cand_restriction).exclude(Q(**querykwargs))) return list(self.filter(cand_restriction).exclude(Q(**querykwargs)))
except exceptions.FieldError: except exceptions.FieldError:
@ -198,8 +208,9 @@ class ObjectDBManager(TypedObjectManager):
if isinstance(property_name, basestring): if isinstance(property_name, basestring):
if not property_name.startswith('db_'): if not property_name.startswith('db_'):
property_name = "db_%s" % property_name property_name = "db_%s" % property_name
querykwargs = {property_name:property_value} 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() 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() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
try: try:
return list(self.filter(cand_restriction & type_restriction & Q(**querykwargs))) return list(self.filter(cand_restriction & type_restriction & Q(**querykwargs)))
@ -207,7 +218,8 @@ class ObjectDBManager(TypedObjectManager):
return [] return []
except ValueError: except ValueError:
from evennia.utils import logger 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 [] return []
@returns_typeclass_list @returns_typeclass_list
@ -228,7 +240,7 @@ class ObjectDBManager(TypedObjectManager):
@returns_typeclass_list @returns_typeclass_list
def get_objs_with_key_or_alias(self, ostring, exact=True, def get_objs_with_key_or_alias(self, ostring, exact=True,
candidates=None, typeclasses=None): candidates=None, typeclasses=None):
""" """
Args: Args:
ostring (str): A search criterion. ostring (str): A search criterion.
@ -253,7 +265,7 @@ class ObjectDBManager(TypedObjectManager):
# build query objects # build query objects
candidates_id = [_GA(obj, "id") for obj in make_iter(candidates) if obj] 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() type_restriction = typeclasses and Q(db_typeclass_path__in=make_iter(typeclasses)) or Q()
if exact: if exact:
# exact match - do direct search # exact match - do direct search
@ -264,7 +276,8 @@ class ObjectDBManager(TypedObjectManager):
search_candidates = self.filter(cand_restriction & type_restriction) search_candidates = self.filter(cand_restriction & type_restriction)
else: else:
# fuzzy without supplied candidates - we select our own candidates # 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 # fuzzy matching
key_strings = search_candidates.values_list("db_key", flat=True).order_by("id") key_strings = search_candidates.values_list("db_key", flat=True).order_by("id")
@ -275,10 +288,10 @@ class ObjectDBManager(TypedObjectManager):
else: else:
# match by alias rather than by key # match by alias rather than by key
search_candidates = search_candidates.filter(db_tags__db_tagtype__iexact="alias", 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_strings = []
alias_candidates = [] 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 candidate in search_candidates:
for alias in candidate.aliases.all(): for alias in candidate.aliases.all():
alias_strings.append(alias) alias_strings.append(alias)
@ -343,13 +356,16 @@ class ObjectDBManager(TypedObjectManager):
""" """
if attribute_name: if attribute_name:
# attribute/property search (always exact). # 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: if matches:
return 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: else:
# normal key/alias search # 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: if not searchdata and searchdata != 0:
return [] return []
@ -372,7 +388,7 @@ class ObjectDBManager(TypedObjectManager):
candidates = [cand for cand in make_iter(candidates) if cand] candidates = [cand for cand in make_iter(candidates) if cand]
if typeclass: if typeclass:
candidates = [cand for cand in candidates 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) dbref = not attribute_name and exact and use_dbref and self.dbref(searchdata)
if dbref: if dbref:
@ -418,7 +434,6 @@ class ObjectDBManager(TypedObjectManager):
# #
# ObjectManager Copy method # ObjectManager Copy method
#
def copy_object(self, original_object, new_key=None, def copy_object(self, original_object, new_key=None,
new_location=None, new_home=None, new_location=None, new_home=None,

View file

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

View file

@ -21,7 +21,7 @@ from evennia.commands.cmdsethandler import CmdSetHandler
from evennia.commands import cmdhandler from evennia.commands import cmdhandler
from evennia.utils import logger from evennia.utils import logger
from evennia.utils.utils import (variable_from_module, lazy_property, 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 _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 _ from django.utils.translation import ugettext as _
class ObjectSessionHandler(object): class ObjectSessionHandler(object):
""" """
Handles the get/setting of the sessid Handles the get/setting of the sessid
@ -133,7 +134,7 @@ class ObjectSessionHandler(object):
Remove session from handler. Remove session from handler.
Args: Args:
sessid (Session or int): Session or session id to remove. session (Session or int): Session or session id to remove.
""" """
try: try:
@ -144,7 +145,7 @@ class ObjectSessionHandler(object):
sessid_cache = self._sessid_cache sessid_cache = self._sessid_cache
if sessid in sessid_cache: if sessid in sessid_cache:
sessid_cache.remove(sessid) 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"]) self.obj.save(update_fields=["db_sessid"])
def clear(self): def clear(self):
@ -167,10 +168,9 @@ class ObjectSessionHandler(object):
return len(self._sessid_cache) return len(self._sessid_cache)
# #
# Base class to inherit from. # Base class to inherit from.
#
class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): 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 \ 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): def contents_get(self, exclude=None):
""" """
@ -241,7 +241,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
""" """
con = self.contents_cache.get(exclude=exclude) 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 return con
contents = property(contents_get) contents = property(contents_get)
@ -370,8 +370,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# do nick-replacement on search # do nick-replacement on search
searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True) searchdata = self.nicks.nickreplace(searchdata, categories=("object", "player"), include_player=True)
if(global_search or (is_string and searchdata.startswith("#") and if (global_search or (is_string and searchdata.startswith("#") and
len(searchdata) > 1 and searchdata[1:].isdigit())): len(searchdata) > 1 and searchdata[1:].isdigit())):
# only allow exact matching if searching the entire database # only allow exact matching if searching the entire database
# or unique #dbrefs # or unique #dbrefs
exact = True exact = True
@ -403,8 +403,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
use_dbref=use_dbref) use_dbref=use_dbref)
if quiet: if quiet:
return results return results
return _AT_SEARCH_RESULT(results, self, query=searchdata, return _AT_SEARCH_RESULT(results, self, query=searchdata,
nofound_string=nofound_string, multimatch_string=multimatch_string) nofound_string=nofound_string, multimatch_string=multimatch_string)
def search_player(self, searchdata, quiet=False): def search_player(self, searchdata, quiet=False):
""" """
@ -474,11 +474,9 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
# nick replacement - we require full-word matching. # nick replacement - we require full-word matching.
# do text encoding conversion # do text encoding conversion
raw_string = to_unicode(raw_string) raw_string = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string, raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=True)
categories=("inputline", "channel"), include_player=True)
return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs) return cmdhandler.cmdhandler(self, raw_string, callertype="object", session=session, **kwargs)
def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs): def msg(self, text=None, from_obj=None, session=None, options=None, **kwargs):
""" """
Emits something to a session attached to the object. Emits something to a session attached to the object.
@ -549,21 +547,28 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
for obj in contents: for obj in contents:
func(obj, **kwargs) 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. Emits a message to all objects inside this object.
Args: 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. exclude (list, optional): A list of objects not to send to.
from_obj (Object, optional): An object designated as the from_obj (Object, optional): An object designated as the
"sender" of the message. See `DefaultObject.msg()` for "sender" of the message. See `DefaultObject.msg()` for
more info. more info.
mapping (dict, optional): A mapping of formatting keys mapping (dict, optional): A mapping of formatting keys
`{"key":<object>, "key2":<object2>,...}. The 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)` 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: Kwargs:
Keyword arguments will be passed on to `obj.msg()` for all Keyword arguments will be passed on to `obj.msg()` for all
messaged objects. messaged objects.
@ -577,14 +582,23 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
not have `get_display_name()`, its string value will be used. not have `get_display_name()`, its string value will be used.
Example: 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( char.location.msg_contents(
"{attacker} {action} {defender}", "{attacker} kicks {defender}",
mapping=dict(attacker=char, defender=npc, action=action), mapping=dict(attacker=char, defender=npc), exclude=(char, 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 contents = self.contents
if exclude: if exclude:
exclude = make_iter(exclude) exclude = make_iter(exclude)
@ -592,12 +606,12 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
for obj in contents: for obj in contents:
if mapping: if mapping:
substitutions = {t: sub.get_display_name(obj) substitutions = {t: sub.get_display_name(obj)
if hasattr(sub, 'get_display_name') if hasattr(sub, 'get_display_name')
else str(sub) else str(sub) for t, sub in mapping.items()}
for t, sub in mapping.items()} outmessage = inmessage.format(**substitutions)
obj.msg(message.format(**substitutions), from_obj=from_obj, **kwargs)
else: 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, def move_to(self, destination, quiet=False,
emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True): 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): def logerr(string="", err=None):
"Simple log helper method" """Simple log helper method"""
logger.log_trace() logger.log_trace()
self.msg("%s%s" % (string, "" if err is None else " (%s)" % err)) self.msg("%s%s" % (string, "" if err is None else " (%s)" % err))
return return
@ -658,7 +672,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.location = None self.location = None
return True return True
emit_to_obj.msg(_("The destination doesn't exist.")) emit_to_obj.msg(_("The destination doesn't exist."))
return return False
if destination.destination and use_destination: if destination.destination and use_destination:
# traverse exits # traverse exits
destination = destination.destination destination = destination.destination
@ -667,7 +681,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
if move_hooks: if move_hooks:
try: try:
if not self.at_before_move(destination): if not self.at_before_move(destination):
return return False
except Exception as err: except Exception as err:
logerr(errtxt % "at_before_move()", err) logerr(errtxt % "at_before_move()", err)
return False return False
@ -684,7 +698,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return False return False
if not quiet: if not quiet:
#tell the old room we are leaving # tell the old room we are leaving
try: try:
self.announce_move_from(destination) self.announce_move_from(destination)
except Exception as err: except Exception as err:
@ -704,7 +718,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.announce_move_to(source_location) self.announce_move_to(source_location)
except Exception as err: except Exception as err:
logerr(errtxt % "announce_move_to()", err) logerr(errtxt % "announce_move_to()", err)
return False return False
if move_hooks: if move_hooks:
# Perform eventual extra commands on the receiving location # Perform eventual extra commands on the receiving location
@ -779,7 +793,6 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
obj.msg(_(string)) obj.msg(_(string))
obj.move_to(home) obj.move_to(home)
def copy(self, new_key=None): def copy(self, new_key=None):
""" """
Makes an identical copy of this object, identical except for a Makes an identical copy of this object, identical except for a
@ -803,15 +816,15 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
""" """
key = self.key key = self.key
num = 1 num = 1
for obj in (obj for obj in self.location.contents for _ in (obj for obj in self.location.contents
if obj.key.startswith(key) and if obj.key.startswith(key) and obj.key.lstrip(key).isdigit()):
obj.key.lstrip(key).isdigit()):
num += 1 num += 1
return "%s%03i" % (key, num) return "%s%03i" % (key, num)
new_key = new_key or find_clone_key() new_key = new_key or find_clone_key()
return ObjectDB.objects.copy_object(self, new_key=new_key) return ObjectDB.objects.copy_object(self, new_key=new_key)
delete_iter = 0 delete_iter = 0
def delete(self): def delete(self):
""" """
Deletes this object. Before deletion, this method makes sure Deletes this object. Before deletion, this method makes sure
@ -862,7 +875,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.attributes.clear() self.attributes.clear()
self.nicks.clear() self.nicks.clear()
self.aliases.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 # Perform the deletion of the object
super(DefaultObject, self).delete() super(DefaultObject, self).delete()
@ -955,8 +968,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
self.basetype_posthook_setup() self.basetype_posthook_setup()
# hooks called by the game engine #
## hooks called by the game engine
def basetype_setup(self): def basetype_setup(self):
""" """
@ -1032,8 +1044,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
have no cmdsets. have no cmdsets.
Kwargs: Kwargs:
Usually not set but could be used e.g. to force rebuilding caller (Session, Object or Player): The caller requesting
of a dynamically created cmdset or similar. this cmdset.
""" """
pass pass
@ -1118,9 +1130,8 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args: Args:
result (bool): The outcome of the access call. result (bool): The outcome of the access call.
accessing_obj (Object or Player): The entity trying to accessing_obj (Object or Player): The entity trying to gain access.
gain access. access_type (str): The type of access that access_type (str): The type of access that was requested.
was requested.
Kwargs: Kwargs:
Not used by default, added for possible expandability in a 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. before it is even started.
""" """
#return has_perm(self, destination, "can_move") # return has_perm(self, destination, "can_move")
return True 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 if the move is to be announced. This is
called while we are still standing in the old called while we are still standing in the old
@ -1158,25 +1169,56 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
Args: Args:
destination (Object): The place we are going to. 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: if not self.location:
return return
string = "%s is leaving %s, heading for %s." if msg:
location = self.location string = msg
for obj in self.location.contents: else:
if obj != self: string = "{object} is leaving {origin}, heading for {destination}."
obj.msg(string % (self.get_display_name(obj),
location.get_display_name(obj) if location else "nowhere",
destination.get_display_name(obj)))
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 Called after the move if the move was not quiet. At this point
we are standing in the new location. we are standing in the new location.
Args: Args:
source_location (Object): The place we came from 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) self.location.msg(string)
return return
string = "%s arrives to %s%s." if source_location:
location = self.location if msg:
for obj in self.location.contents: string = msg
if obj != self: else:
obj.msg(string % (self.get_display_name(obj), string = "{object} arrives to {destination} from {origin}."
location.get_display_name(obj) if location else "nowhere", else:
" from %s" % source_location.get_display_name(obj) if source_location 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): def at_after_move(self, source_location):
""" """
@ -1288,7 +1348,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
check for this. . check for this. .
Consider this a pre-processing method before msg is passed on 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. will not be passed on.
Args: Args:
@ -1341,7 +1401,7 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
return "" return ""
# get and identify all objects # get and identify all objects
visible = (con for con in self.contents if con != looker and visible = (con for con in self.contents if con != looker and
con.access(looker, "view")) con.access(looker, "view"))
exits, users, things = [], [], [] exits, users, things = [], [], []
for con in visible: for con in visible:
key = con.get_display_name(looker) key = con.get_display_name(looker)
@ -1416,6 +1476,22 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)):
""" """
pass 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): def at_drop(self, dropper):
""" """
Called by the default `drop` command when this object has been 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. dropper (Object): The object which just dropped this object.
Notes: Notes:
This hook cannot stop the pickup from happening. Use This hook cannot stop the drop from happening. Use
permissions from that. permissions from that.
""" """
@ -1473,7 +1549,7 @@ class DefaultCharacter(DefaultObject):
""" """
super(DefaultCharacter, self).basetype_setup() super(DefaultCharacter, self).basetype_setup()
self.locks.add(";".join(["get:false()", # noone can pick up the character 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 # add the default cmdset
self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True) self.cmdset.add_default(settings.CMDSET_CHARACTER, permanent=True)
@ -1565,7 +1641,7 @@ class DefaultCharacter(DefaultObject):
# #
# Base Room object # Base Room object
#
class DefaultRoom(DefaultObject): class DefaultRoom(DefaultObject):
""" """
@ -1581,7 +1657,7 @@ class DefaultRoom(DefaultObject):
super(DefaultRoom, self).basetype_setup() super(DefaultRoom, self).basetype_setup()
self.locks.add(";".join(["get:false()", 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 self.location = None
@ -1631,7 +1707,7 @@ class ExitCommand(command.Command):
# #
# Base Exit object # Base Exit object
#
class DefaultExit(DefaultObject): class DefaultExit(DefaultObject):
""" """
@ -1683,8 +1759,8 @@ class DefaultExit(DefaultObject):
exit_cmdset.add(cmd) exit_cmdset.add(cmd)
return exit_cmdset return exit_cmdset
# Command hooks # Command hooks
def basetype_setup(self): def basetype_setup(self):
""" """
Setup exit-security Setup exit-security
@ -1696,8 +1772,8 @@ class DefaultExit(DefaultObject):
super(DefaultExit, self).basetype_setup() super(DefaultExit, self).basetype_setup()
# setting default locks (overload these in at_object_creation() # setting default locks (overload these in at_object_creation()
self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ... self.locks.add(";".join(["puppet:false()", # would be weird to puppet an exit ...
"traverse:all()", # who can pass through exit by default "traverse:all()", # who can pass through exit by default
"get:false()"])) # noone can pick up the exit "get:false()"])) # noone can pick up the exit
# an exit should have a destination (this is replaced at creation time) # 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 _IDLE_TIMEOUT = settings.IDLE_TIMEOUT
_IRC_ENABLED = settings.IRC_ENABLED
_RSS_ENABLED = settings.RSS_ENABLED
_SESSIONS = None _SESSIONS = None
@ -78,8 +81,10 @@ class BotStarter(DefaultScript):
""" """
self.db.started = False self.db.started = False
#
# Bot base class # Bot base class
class Bot(DefaultPlayer): class Bot(DefaultPlayer):
""" """
A Bot will start itself when the server starts (it will generally 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. 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 global _SESSIONS
if not _SESSIONS: if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _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 # instruct the server and portal to create a new session with
# the stored configuration # the stored configuration
configdict = {"uid":self.dbid, configdict = {"uid": self.dbid,
"botname": self.db.irc_botname, "botname": self.db.irc_botname,
"channel": self.db.irc_channel , "channel": self.db.irc_channel,
"network": self.db.irc_network, "network": self.db.irc_network,
"port": self.db.irc_port, "port": self.db.irc_port,
"ssl": self.db.irc_ssl} "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), whos.append("%s (%s/%s)" % (utils.crop("|w%s|n" % player.name, width=25),
utils.time_format(delta_conn, 0), utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1))) 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"): elif txt.lower().startswith("about"):
# some bot info # some bot info
text = "This is an Evennia IRC bot connecting from '%s'." % settings.SERVERNAME text = "This is an Evennia IRC bot connecting from '%s'." % settings.SERVERNAME
else: else:
text = "I understand 'who' and 'about'." text = "I understand 'who' and 'about'."
super(IRCBot, self).msg(privmsg=((text,), {"user":user})) super(IRCBot, self).msg(privmsg=((text,), {"user": user}))
else: else:
# something to send to the main channel # something to send to the main channel
if kwargs["type"] == "action": if kwargs["type"] == "action":
# An action (irc pose) # An action (irc pose)
text = "%s@%s %s" % (kwargs["user"], kwargs["channel"], txt) text = "%s@%s %s" % (kwargs["user"], kwargs["channel"], txt)
else: else:
# msg - A normal channel message # msg - A normal channel message
text = "%s@%s: %s" % (kwargs["user"], kwargs["channel"], txt) text = "%s@%s: %s" % (kwargs["user"], kwargs["channel"], txt)
if not self.ndb.ev_channel and self.db.ev_channel: if not self.ndb.ev_channel and self.db.ev_channel:
# cache channel lookup # cache channel lookup
self.ndb.ev_channel = self.db.ev_channel self.ndb.ev_channel = self.db.ev_channel
if self.ndb.ev_channel: if self.ndb.ev_channel:
self.ndb.ev_channel.msg(text, senders=self.id) self.ndb.ev_channel.msg(text, senders=self.id)
#
# RSS # RSS
class RSSBot(Bot): class RSSBot(Bot):
""" """
An RSS relayer. The RSS protocol itself runs a ticker to update An RSS relayer. The RSS protocol itself runs a ticker to update
@ -354,12 +366,17 @@ class RSSBot(Bot):
Args: Args:
ev_channel (str): Key of the Evennia channel to connect to. ev_channel (str): Key of the Evennia channel to connect to.
rss_url (str): Full URL to the RSS feed to subscribe 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: Raises:
RuntimeError: If `ev_channel` does not exist. 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 global _SESSIONS
if not _SESSIONS: if not _SESSIONS:
from evennia.server.sessionhandler import SESSIONS as _SESSIONS from evennia.server.sessionhandler import SESSIONS as _SESSIONS
@ -390,7 +407,7 @@ class RSSBot(Bot):
Args: Args:
session (Session, optional): Session responsible for this session (Session, optional): Session responsible for this
command. command.
text (str, optional): Command string. txt (str, optional): Command string.
kwargs (dict, optional): Additional Information passed from bot. kwargs (dict, optional): Additional Information passed from bot.
Not used by the RSSbot by default. Not used by the RSSbot by default.

View file

@ -104,17 +104,6 @@ class PlayerDB(TypedObject, AbstractUser):
class Meta(object): class Meta(object):
verbose_name = 'Player' 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 # cmdset_storage property
# This seems very sensitive to caching, so leaving it be for now /Griatch # This seems very sensitive to caching, so leaving it be for now /Griatch
#@property #@property

View file

@ -41,6 +41,7 @@ _MAX_NR_CHARACTERS = settings.MAX_NR_CHARACTERS
_CMDSET_PLAYER = settings.CMDSET_PLAYER _CMDSET_PLAYER = settings.CMDSET_PLAYER
_CONNECT_CHANNEL = None _CONNECT_CHANNEL = None
class PlayerSessionHandler(object): class PlayerSessionHandler(object):
""" """
Manages the session(s) attached to a player. Manages the session(s) attached to a player.
@ -99,7 +100,6 @@ class PlayerSessionHandler(object):
return len(self.get()) return len(self.get())
class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)): class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
""" """
This is the base Typeclass for all Players. Players represent This is the base Typeclass for all Players. Players represent
@ -188,7 +188,6 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
def sessions(self): def sessions(self):
return PlayerSessionHandler(self) return PlayerSessionHandler(self)
# session-related methods # session-related methods
def disconnect_session_from_player(self, session): def disconnect_session_from_player(self, session):
@ -337,8 +336,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
by this Player. by this Player.
""" """
return list(set(session.puppet for session in self.sessions.all() return list(set(session.puppet for session in self.sessions.all() if session.puppet))
if session.puppet))
def __get_single_puppet(self): def __get_single_puppet(self):
""" """
@ -383,7 +381,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
self.nicks.clear() self.nicks.clear()
self.aliases.clear() self.aliases.clear()
super(DefaultPlayer, self).delete(*args, **kwargs) 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): 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 = to_unicode(raw_string)
raw_string = self.nicks.nickreplace(raw_string, raw_string = self.nicks.nickreplace(raw_string, categories=("inputline", "channel"), include_player=False)
categories=("inputline", "channel"), include_player=False)
if not session and _MULTISESSION_MODE in (0, 1): if not session and _MULTISESSION_MODE in (0, 1):
# for these modes we use the first/only session # for these modes we use the first/only session
sessions = self.sessions.get() sessions = self.sessions.get()
@ -557,7 +554,7 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
return time.time() - float(min(conn)) return time.time() - float(min(conn))
return None return None
## player hooks # player hooks
def basetype_setup(self): def basetype_setup(self):
""" """
@ -600,7 +597,6 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
""" """
pass pass
# Note that the hooks below also exist in the character object's # Note that the hooks below also exist in the character object's
# typeclass. You can often ignore these and rely on the character # typeclass. You can often ignore these and rely on the character
# ones instead, unless you are implementing a multi-character game # 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 is_su = self.is_superuser
# text shown when looking in the ooc area # 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) 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): for isess, sess in enumerate(sessions):
csessid = sess.sessid csessid = sess.sessid
addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple) and str(sess.address[0]) or str(sess.address)) addr = "%s (%s)" % (sess.protocol_key, isinstance(sess.address, tuple)
string += "\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1) or " %s" % (isess + 1), addr) and str(sess.address[0]) or str(sess.address))
string += "\n\n |whelp|n - more commands" result.append("\n %s %s" % (session.sessid == csessid and "|w* %s|n" % (isess + 1)
string += "\n |wooc <Text>|n - talk on public channel" 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 charmax = _MAX_NR_CHARACTERS if _MULTISESSION_MODE > 1 else 1
if is_su or len(characters) < charmax: if is_su or len(characters) < charmax:
if not characters: 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: else:
string += "\n |w@charcreate <name> [=description]|n - create new character" result.append("\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@chardelete <name>|n - delete a character (cannot be undone!)")
if characters: if characters:
string_s_ending = len(characters) > 1 and "s" or "" 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: 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: else:
string += "\n\nAvailable character%s%s:" % (string_s_ending, result.append("\n\nAvailable character%s%s:"
charmax > 1 and " (%i/%i)" % (len(characters), charmax) or "") % (string_s_ending, charmax > 1 and " (%i/%i)" % (len(characters), charmax) or ""))
for char in characters: for char in characters:
csessions = char.sessions.all() csessions = char.sessions.all()
@ -883,14 +881,16 @@ class DefaultPlayer(with_metaclass(TypeclassBase, PlayerDB)):
# character is already puppeted # character is already puppeted
sid = sess in sessions and sessions.index(sess) + 1 sid = sess in sessions and sessions.index(sess) + 1
if sess and sid: 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: 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: else:
# character is "free to puppet" # character is "free to puppet"
string += "\n - %s [%s]" % (char.key, ", ".join(char.permissions.all())) result.append("\n - %s [%s]" % (char.key, ", ".join(char.permissions.all())))
string = ("-" * 68) + "\n" + string + "\n" + ("-" * 68) look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68)
return string return look_string
class DefaultGuest(DefaultPlayer): class DefaultGuest(DefaultPlayer):
@ -910,7 +910,6 @@ class DefaultGuest(DefaultPlayer):
self._send_to_connect_channel("|G%s connected|n" % self.key) self._send_to_connect_channel("|G%s connected|n" % self.key)
self.puppet_object(session, self.db._last_puppet) self.puppet_object(session, self.db._last_puppet)
def at_server_shutdown(self): def at_server_shutdown(self):
""" """
We repeat the functionality of `at_disconnect()` here just to 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 Portal - (AMP client) handles protocols. It contains a list of connected
sessions in a dictionary for identifying the respective player 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. try to reconnect.
Server - (AMP server) Handles all mud operations. The server holds its own list 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 twisted.internet.defer import Deferred
from evennia.utils import logger from evennia.utils import logger
from evennia.utils.utils import to_str, variable_from_module from evennia.utils.utils import to_str, variable_from_module
import zlib # Used in Compressed class
DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0) DUMMYSESSION = namedtuple('DummySession', ['sessid'])(0)
# communication bits # communication bits
# (chr(9) and chr(10) are \t and \n, so skipping them) # (chr(9) and chr(10) are \t and \n, so skipping them)
PCONN = chr(1) # portal session connect PCONN = chr(1) # portal session connect
PDISCONN = chr(2) # portal session disconnect PDISCONN = chr(2) # portal session disconnect
PSYNC = chr(3) # portal session sync PSYNC = chr(3) # portal session sync
SLOGIN = chr(4) # server session login SLOGIN = chr(4) # server session login
SDISCONN = chr(5) # server session disconnect SDISCONN = chr(5) # server session disconnect
SDISCONNALL = chr(6) # server session disconnect all SDISCONNALL = chr(6) # server session disconnect all
SSHUTD = chr(7) # server shutdown SSHUTD = chr(7) # server shutdown
SSYNC = chr(8) # server session sync SSYNC = chr(8) # server session sync
SCONN = chr(11) # server creating new connection (for irc bots and etc) SCONN = chr(11) # server creating new connection (for irc bots and etc)
PCONNSYNC = chr(12) # portal post-syncing a session PCONNSYNC = chr(12) # portal post-syncing a session
PDISCONNALL = chr(13) # portal session disconnect all PDISCONNALL = chr(13) # portal session disconnect all
AMP_MAXLEN = amp.MAX_VALUE_LENGTH # max allowed data length in AMP protocol (cannot be changed) 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_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_TIMEOUT = 0.5 # how often to poll to empty batch queue, in seconds
# buffers # buffers
_SENDBATCH = defaultdict(list) _SENDBATCH = defaultdict(list)
_MSGBUFFER = defaultdict(list) _MSGBUFFER = defaultdict(list)
import zlib
def get_restart_mode(restart_file): 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)) loads = lambda data: pickle.loads(to_str(data))
#------------------------------------------------------------ # -------------------------------------------------------------
# Core AMP protocol for communication Server <-> Portal # Core AMP protocol for communication Server <-> Portal
#------------------------------------------------------------ # -------------------------------------------------------------
class AMPProtocol(amp.AMP): class AMPProtocol(amp.AMP):
""" """
@ -385,7 +385,6 @@ class AMPProtocol(amp.AMP):
""" """
pass pass
# Error handling # Error handling
def errback(self, e, info): def errback(self, e, info):
@ -447,7 +446,7 @@ class AMPProtocol(amp.AMP):
Access method called by the Portal and executed on the Portal. Access method called by the Portal and executed on the Portal.
Args: Args:
sessid (int): Unique Session id. session (session): Session
kwargs (any, optional): Optional data. kwargs (any, optional): Optional data.
Returns: Returns:
@ -473,7 +472,6 @@ class AMPProtocol(amp.AMP):
self.factory.portal.sessions.data_out(session, **kwargs) self.factory.portal.sessions.data_out(session, **kwargs)
return {} return {}
def send_MsgServer2Portal(self, session, **kwargs): def send_MsgServer2Portal(self, session, **kwargs):
""" """
Access method - executed on the Server for sending data Access method - executed on the Server for sending data
@ -506,7 +504,7 @@ class AMPProtocol(amp.AMP):
# create a new session and sync it # create a new session and sync it
server_sessionhandler.portal_connect(kwargs.get("sessiondata")) 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")) server_sessionhandler.portal_session_sync(kwargs.get("sessiondata"))
elif operation == PDISCONN: # portal_session_disconnect elif operation == PDISCONN: # portal_session_disconnect
@ -515,7 +513,7 @@ class AMPProtocol(amp.AMP):
if session: if session:
server_sessionhandler.portal_disconnect(session) server_sessionhandler.portal_disconnect(session)
elif operation == PDISCONNALL: # portal_disconnect_all elif operation == PDISCONNALL: # portal_disconnect_all
# portal orders all sessions to close # portal orders all sessions to close
server_sessionhandler.portal_disconnect_all() server_sessionhandler.portal_disconnect_all()
@ -545,7 +543,7 @@ class AMPProtocol(amp.AMP):
""" """
return self.send_data(AdminPortal2Server, session.sessid, operation=operation, **kwargs) return self.send_data(AdminPortal2Server, session.sessid, operation=operation, **kwargs)
# Portal administraton from the Server side # Portal administration from the Server side
@AdminServer2Portal.responder @AdminServer2Portal.responder
def portal_receive_adminserver2portal(self, packed_data): def portal_receive_adminserver2portal(self, packed_data):
@ -562,7 +560,6 @@ class AMPProtocol(amp.AMP):
operation = kwargs.pop("operation") operation = kwargs.pop("operation")
portal_sessionhandler = self.factory.portal.sessions portal_sessionhandler = self.factory.portal.sessions
if operation == SLOGIN: # server_session_login if operation == SLOGIN: # server_session_login
# a session has authenticated; sync it. # a session has authenticated; sync it.
session = portal_sessionhandler.get(sessid) 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 # set a flag in case we are about to shut down soon
self.factory.server_restart_mode = True 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) portal_sessionhandler.server_connect(**kwargs)
else: else:
@ -665,4 +662,5 @@ class AMPProtocol(amp.AMP):
module=modulepath, module=modulepath,
function=functionname, function=functionname,
args=dumps(args), 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 from evennia.utils import create, logger
ERROR_NO_SUPERUSER = \ ERROR_NO_SUPERUSER = """
"""
No superuser exists yet. The superuser is the 'owner' account on No superuser exists yet. The superuser is the 'owner' account on
the Evennia server. Create a new superuser using the command the Evennia server. Create a new superuser using the command
@ -26,16 +25,14 @@ ERROR_NO_SUPERUSER = \
""" """
LIMBO_DESC = \ LIMBO_DESC = _("""
_(""" Welcome to your new |wEvennia|n-based game! Visit http://www.evennia.com if you need
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. 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: PostgreSQL-psycopg2 compatibility fix:
The in-game channels {chan1}, {chan2} and {chan3} were created, The in-game channels {chan1}, {chan2} and {chan3} were created,
but the superuser was not yet connected to them. Please use in 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_DGREY = "14"
IRC_GRAY = "15" IRC_GRAY = "15"
# test: # obsolete test:
# {rred {ggreen {yyellow {bblue {mmagenta {ccyan {wwhite {xdgrey # {rred {ggreen {yyellow {bblue {mmagenta {ccyan {wwhite {xdgrey
# {Rdred {Gdgreen {Ydyellow {Bdblue {Mdmagenta {Cdcyan {Wlgrey {Xblack # {Rdred {Gdgreen {Ydyellow {Bdblue {Mdmagenta {Cdcyan {Wlgrey {Xblack
# {[rredbg {[ggreenbg {[yyellowbg {[bbluebg {[mmagentabg {[ccyanbg {[wlgreybg {[xblackbg # {[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([ IRC_COLOR_MAP = dict([
# obs - {-type colors are deprecated but still used in many places. # 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'{/', ""), # line break
(r'{-', " "), # tab (r'{-', " "), # tab
(r'{_', " "), # space (r'{_', " "), # space
(r'{*', ""), # invert (r'{*', ""), # invert
(r'{^', ""), # blinking text (r'{^', ""), # blinking text
(r'{r', IRC_COLOR + IRC_RED), (r'{r', IRC_COLOR + IRC_RED),
@ -69,7 +74,7 @@ IRC_COLOR_MAP = dict([
(r'{B', IRC_COLOR + IRC_DBLUE), (r'{B', IRC_COLOR + IRC_DBLUE),
(r'{M', IRC_COLOR + IRC_DMAGENTA), (r'{M', IRC_COLOR + IRC_DMAGENTA),
(r'{C', IRC_COLOR + IRC_DCYAN), (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'{X', IRC_COLOR + IRC_BLACK), # pure black
(r'{[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED), (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'{[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
(r'{[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN), (r'{[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
(r'{[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background (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. # |-type formatting is the thing to use.
(r'|n', IRC_RESET), # reset (r'|n', IRC_RESET), # reset
(r'|/', ""), # line break (r'|/', ""), # line break
(r'|-', " "), # tab (r'|-', " "), # tab
(r'|_', " "), # space (r'|_', " "), # space
(r'|*', ""), # invert (r'|*', ""), # invert
(r'|^', ""), # blinking text (r'|^', ""), # blinking text
(r'|r', IRC_COLOR + IRC_RED), (r'|r', IRC_COLOR + IRC_RED),
@ -104,7 +109,7 @@ IRC_COLOR_MAP = dict([
(r'|B', IRC_COLOR + IRC_DBLUE), (r'|B', IRC_COLOR + IRC_DBLUE),
(r'|M', IRC_COLOR + IRC_DMAGENTA), (r'|M', IRC_COLOR + IRC_DMAGENTA),
(r'|C', IRC_COLOR + IRC_DCYAN), (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'|X', IRC_COLOR + IRC_BLACK), # pure black
(r'|[r', IRC_COLOR + IRC_NORMAL + "," + IRC_DRED), (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'|[m', IRC_COLOR + IRC_NORMAL + "," + IRC_DMAGENTA),
(r'|[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN), (r'|[c', IRC_COLOR + IRC_NORMAL + "," + IRC_DCYAN),
(r'|[w', IRC_COLOR + IRC_NORMAL + "," + IRC_GRAY), # light grey background (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_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) RE_ANSI_ESCAPES = re.compile(r"(%s)" % "|".join(("{{", "%%", "\\\\")), re.DOTALL)
def sub_irc(ircmatch): def sub_irc(ircmatch):
""" """
Substitute irc color info. Used by re.sub. Substitute irc color info. Used by re.sub.
@ -133,6 +139,7 @@ def sub_irc(ircmatch):
""" """
return IRC_COLOR_MAP.get(ircmatch.group(), "") return IRC_COLOR_MAP.get(ircmatch.group(), "")
def parse_irc_colors(string): def parse_irc_colors(string):
""" """
Parse {-type syntax and replace with IRC color markers Parse {-type syntax and replace with IRC color markers
@ -156,9 +163,10 @@ def parse_irc_colors(string):
# IRC bot # IRC bot
class IRCBot(irc.IRCClient, Session): 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 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, logger.log_info("IRC bot '%s' connected to %s at %s:%s." % (self.nickname, self.channel,
self.network, self.port)) self.network, self.port))
def disconnect(self, reason=None): def disconnect(self, reason=""):
""" """
Called by sessionhandler to disconnect this protocol. Called by sessionhandler to disconnect this protocol.
@ -198,7 +206,7 @@ class IRCBot(irc.IRCClient, Session):
reason (str): Motivation for the disconnect. reason (str): Motivation for the disconnect.
""" """
self.sessionhandler.disconnect(self) self.sessionhandler.disconnect(self, reason=reason)
self.stopping = True self.stopping = True
self.transport.loseConnection() self.transport.loseConnection()
@ -246,14 +254,14 @@ class IRCBot(irc.IRCClient, Session):
self.sendLine("NAMES %s" % self.channel) self.sendLine("NAMES %s" % self.channel)
def irc_RPL_NAMREPLY(self, prefix, params): def irc_RPL_NAMREPLY(self, prefix, params):
"Handles IRC NAME request returns (nicklist)" """"Handles IRC NAME request returns (nicklist)"""
channel = params[2].lower() channel = params[2].lower()
if channel != self.channel.lower(): if channel != self.channel.lower():
return return
self.nicklist += params[3].split(' ') self.nicklist += params[3].split(' ')
def irc_RPL_ENDOFNAMES(self, prefix, params): 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() channel = params[1].lower()
if channel != self.channel.lower(): if channel != self.channel.lower():
return return
@ -271,7 +279,6 @@ class IRCBot(irc.IRCClient, Session):
""" """
self.data_in(text="", type="ping", user="server", channel=self.channel, timing=time) self.data_in(text="", type="ping", user="server", channel=self.channel, timing=time)
def data_in(self, text=None, **kwargs): def data_in(self, text=None, **kwargs):
""" """
Data IRC -> Server. Data IRC -> Server.

View file

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

View file

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

View file

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

View file

@ -54,6 +54,7 @@ class SSLProtocol(TelnetProtocol):
super(SSLProtocol, self).__init__(*args, **kwargs) super(SSLProtocol, self).__init__(*args, **kwargs)
self.protocol_name = "ssl" self.protocol_name = "ssl"
def verify_SSL_key_and_cert(keyfile, certfile): def verify_SSL_key_and_cert(keyfile, certfile):
""" """
This function looks for RSA key and certificate in the current 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 # try to create the certificate
CERT_EXPIRE = 365 * 20 # twenty years validity CERT_EXPIRE = 365 * 20 # twenty years validity
# default: # 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) exestring = "openssl req -new -x509 -key %s -out %s -days %s" % (keyfile, certfile, CERT_EXPIRE)
try: try:
subprocess.call(exestring) 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 import ansi
from evennia.utils.utils import to_str 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_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_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) _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
_IDLE_COMMAND = settings.IDLE_COMMAND + "\n" _IDLE_COMMAND = settings.IDLE_COMMAND + "\n"
class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session): class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
""" """
Each player connecting over telnet (ie using most traditional mud 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 client_address = client_address[0] if client_address else None
# this number is counted down for every handshake that completes. # this number is counted down for every handshake that completes.
# when it reaches 0 the portal/server syncs their data # 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) self.init_session(self.protocol_name, client_address, self.factory.sessionhandler)
# negotiate client size # negotiate client size
@ -79,7 +80,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.toggle_nop_keepalive() self.toggle_nop_keepalive()
def _send_nop_keepalive(self): 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"): if self.protocol_flags.get("NOPKEEPALIVE"):
self._write(IAC + NOP) self._write(IAC + NOP)
@ -140,7 +141,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
enable (bool): If this option should be enabled. enable (bool): If this option should be enabled.
""" """
return (option == MCCP or option==ECHO) return option == MCCP or option == ECHO
def disableLocal(self, option): def disableLocal(self, option):
""" """
@ -178,7 +179,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
directly. directly.
Args: Args:
string (str): Incoming data. data (str): Incoming data.
""" """
if not data: if not data:
@ -188,7 +189,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# legacy clients. There should never be a reason to send a # legacy clients. There should never be a reason to send a
# lone NULL character so this seems to be a safe thing to # lone NULL character so this seems to be a safe thing to
# support for backwards compatibility. It also stops the # 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] data = [_IDLE_COMMAND]
else: else:
data = _RE_LINEBREAK.split(data) data = _RE_LINEBREAK.split(data)
@ -205,7 +206,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
self.data_in(text=dat + "\n") self.data_in(text=dat + "\n")
def _write(self, data): 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') data = data.replace('\n', '\r\n').replace('\r\r\n', '\r\n')
super(TelnetProtocol, self)._write(mccp_compress(self, data)) super(TelnetProtocol, self)._write(mccp_compress(self, data))
@ -217,24 +218,23 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
line (str): Line to send. 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 += self.delimiter
line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n') line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n')
return self.transport.write(mccp_compress(self, line)) return self.transport.write(mccp_compress(self, line))
# Session hooks # Session hooks
def disconnect(self, reason=None): def disconnect(self, reason=""):
""" """
generic hook for the engine to call in order to generic hook for the engine to call in order to
disconnect this protocol. disconnect this protocol.
Args: 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) self.connectionLost(reason)
def data_in(self, **kwargs): def data_in(self, **kwargs):
@ -245,8 +245,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
kwargs (any): Options from the protocol. kwargs (any): Options from the protocol.
""" """
#from evennia.server.profiling.timetrace import timetrace # from evennia.server.profiling.timetrace import timetrace # DEBUG
#text = timetrace(text, "telnet.data_in") # text = timetrace(text, "telnet.data_in") # DEBUG
self.sessionhandler.data_in(self, **kwargs) 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)) nocolor = options.get("nocolor", flags.get("NOCOLOR") or not (xterm256 or useansi))
echo = options.get("echo", None) echo = options.get("echo", None)
mxp = options.get("mxp", flags.get("MXP", False)) 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: if screenreader:
# screenreader mode cleans up output # screenreader mode cleans up output
@ -306,9 +306,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
if options.get("send_prompt"): if options.get("send_prompt"):
# send a prompt instead. # send a prompt instead.
prompt = text
if not raw: if not raw:
# processing # 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: if mxp:
prompt = mxp_parse(prompt) prompt = mxp_parse(prompt)
prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n') prompt = prompt.replace(IAC, IAC + IAC).replace('\n', '\r\n')
@ -335,7 +337,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
else: else:
# we need to make sure to kill the color at the end in order # we need to make sure to kill the color at the end in order
# to match the webclient output. # 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: if mxp:
linetosend = mxp_parse(linetosend) linetosend = mxp_parse(linetosend)
self.sendLine(linetosend) self.sendLine(linetosend)
@ -348,7 +351,6 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
kwargs["options"].update({"send_prompt": True}) kwargs["options"].update({"send_prompt": True})
self.send_text(*args, **kwargs) self.send_text(*args, **kwargs)
def send_default(self, cmdname, *args, **kwargs): def send_default(self, cmdname, *args, **kwargs):
""" """
Send other oob data Send other oob data

View file

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

View file

@ -27,6 +27,7 @@ MTTS = [(128, 'PROXY'),
(2, 'VT100'), (2, 'VT100'),
(1, 'ANSI')] (1, 'ANSI')]
class Ttype(object): class Ttype(object):
""" """
Handles ttype negotiations. Called and initiated by the 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 # use name to identify support for xterm256. Many of these
# only support after a certain version, but all support # only support after a certain version, but all support
# it since at least 4 years. We assume recent client here for now. # it since at least 4 years. We assume recent client here for now.
xterm256 = False
cupper = clientname.upper() cupper = clientname.upper()
if cupper.startswith("MUDLET"): if cupper.startswith("MUDLET"):
# supports xterm256 stably since 1.1 (2010?) # 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: else:
xterm256 = (cupper.startswith("XTERM") or xterm256 = (cupper.startswith("XTERM") or
cupper.endswith("-256COLOR") or cupper.endswith("-256COLOR") or
cupper in ("ATLANTIS", # > 0.9.9.0 (aug 2009) cupper in ("ATLANTIS", # > 0.9.9.0 (aug 2009)
"CMUD", # > 3.04 (mar 2009) "CMUD", # > 3.04 (mar 2009)
"KILDCLIENT", # > 2.2.0 (sep 2005) "KILDCLIENT", # > 2.2.0 (sep 2005)
"MUDLET", # > beta 15 (sep 2009) "MUDLET", # > beta 15 (sep 2009)
"MUSHCLIENT", # > 4.02 (apr 2007) "MUSHCLIENT", # > 4.02 (apr 2007)
"PUTTY", # > 0.58 (apr 2005) "PUTTY", # > 0.58 (apr 2005)
"BEIP", # > 2.00.206 (late 2009) (BeipMu) "BEIP", # > 2.00.206 (late 2009) (BeipMu)
"POTATO")) # > 2.00 (maybe earlier) "POTATO")) # > 2.00 (maybe earlier)
# all clients supporting TTYPE at all seem to support ANSI # all clients supporting TTYPE at all seem to support ANSI
self.protocol.protocol_flags['ANSI'] = True 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 # Handlers for Session.db/ndb operation
class NDbHolder(object): 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'): def __init__(self, obj, name, manager_name='attributes'):
_SA(self, name, _GA(obj, manager_name)) _SA(self, name, _GA(obj, manager_name))
_SA(self, 'name', name) _SA(self, 'name', name)
@ -145,9 +146,9 @@ class NAttributeHandler(object):
return [key for key in self._store if not key.startswith("_")] return [key for key in self._store if not key.startswith("_")]
#------------------------------------------------------------ # -------------------------------------------------------------
# Server Session # Server Session
#------------------------------------------------------------ # -------------------------------------------------------------
class ServerSession(Session): class ServerSession(Session):
""" """
@ -160,7 +161,7 @@ class ServerSession(Session):
""" """
def __init__(self): def __init__(self):
"Initiate to avoid AttributeErrors down the line" """Initiate to avoid AttributeErrors down the line"""
self.puppet = None self.puppet = None
self.player = None self.player = None
self.cmdset_storage_string = "" self.cmdset_storage_string = ""
@ -203,7 +204,7 @@ class ServerSession(Session):
obj.player = self.player obj.player = self.player
self.puid = obj.id self.puid = obj.id
self.puppet = obj self.puppet = obj
#obj.scripts.validate() # obj.scripts.validate()
obj.locks.cache_lock_bypass(obj) obj.locks.cache_lock_bypass(obj)
def at_login(self, player): def at_login(self, player):
@ -264,7 +265,6 @@ class ServerSession(Session):
MONITOR_HANDLER.remove(player, "_saved_webclient_options", MONITOR_HANDLER.remove(player, "_saved_webclient_options",
self.sessid) self.sessid)
def get_player(self): def get_player(self):
""" """
Get the player associated with this session Get the player associated with this session
@ -364,7 +364,6 @@ class ServerSession(Session):
self.protocol_flags.update(kwargs) self.protocol_flags.update(kwargs)
self.sessionhandler.session_portal_sync(self) self.sessionhandler.session_portal_sync(self)
def data_out(self, **kwargs): def data_out(self, **kwargs):
""" """
Sending data from Evennia->Client Sending data from Evennia->Client
@ -437,7 +436,7 @@ class ServerSession(Session):
self.sessionhandler.data_in(self, **kwargs) self.sessionhandler.data_in(self, **kwargs)
def __eq__(self, other): def __eq__(self, other):
"Handle session comparisons" """Handle session comparisons"""
try: try:
return self.address == other.address return self.address == other.address
except AttributeError: except AttributeError:
@ -462,11 +461,9 @@ class ServerSession(Session):
return "%s%s@%s" % (self.uname, symbol, address) return "%s%s@%s" % (self.uname, symbol, address)
def __unicode__(self): def __unicode__(self):
"Unicode representation" """Unicode representation"""
return u"%s" % str(self) return u"%s" % str(self)
# Dummy API hooks for use during non-loggedin operation # Dummy API hooks for use during non-loggedin operation
def at_cmdset_get(self, **kwargs): def at_cmdset_get(self, **kwargs):
@ -488,7 +485,7 @@ class ServerSession(Session):
def attributes(self): def attributes(self):
return self.nattributes return self.nattributes
#@property # @property
def ndb_get(self): def ndb_get(self):
""" """
A non-persistent store (ndb: NonDataBase). Everything stored A non-persistent store (ndb: NonDataBase). Everything stored
@ -503,7 +500,7 @@ class ServerSession(Session):
self._ndb_holder = NDbHolder(self, "nattrhandler", manager_name="nattributes") self._ndb_holder = NDbHolder(self, "nattrhandler", manager_name="nattributes")
return self._ndb_holder return self._ndb_holder
#@ndb.setter # @ndb.setter
def ndb_set(self, value): def ndb_set(self, value):
""" """
Stop accidentally replacing the db object Stop accidentally replacing the db object
@ -516,9 +513,9 @@ class ServerSession(Session):
string += "Use ndb.attr=value instead." string += "Use ndb.attr=value instead."
raise Exception(string) raise Exception(string)
#@ndb.deleter # @ndb.deleter
def ndb_del(self): def ndb_del(self):
"Stop accidental deletion." """Stop accidental deletion."""
raise Exception("Cannot delete the ndb object!") raise Exception("Cannot delete the ndb object!")
ndb = property(ndb_get, ndb_set, ndb_del) ndb = property(ndb_get, ndb_set, ndb_del)
db = 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 # Mock access method for the session (there is no lock info
# at this stage, so we just present a uniform API) # at this stage, so we just present a uniform API)
def access(self, *args, **kwargs): def access(self, *args, **kwargs):
"Dummy method to mimic the logged-in API." """Dummy method to mimic the logged-in API."""
return True return True

View file

@ -23,6 +23,8 @@ from twisted.web.wsgi import WSGIResource
from django.conf import settings from django.conf import settings
from django.core.handlers.wsgi import WSGIHandler from django.core.handlers.wsgi import WSGIHandler
from evennia.utils import logger
_UPSTREAM_IPS = settings.UPSTREAM_IPS _UPSTREAM_IPS = settings.UPSTREAM_IPS
_DEBUG = settings.DEBUG _DEBUG = settings.DEBUG
@ -70,6 +72,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
resource (EvenniaReverseProxyResource): A proxy resource. resource (EvenniaReverseProxyResource): A proxy resource.
""" """
request.notifyFinish().addErrback(lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f))
return EvenniaReverseProxyResource( return EvenniaReverseProxyResource(
self.host, self.port, self.path + '/' + urlquote(path, safe=""), self.host, self.port, self.path + '/' + urlquote(path, safe=""),
self.reactor) self.reactor)
@ -98,6 +101,8 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
request.getAllHeaders(), request.content.read(), request) request.getAllHeaders(), request.content.read(), request)
clientFactory.noisy = False clientFactory.noisy = False
self.reactor.connectTCP(self.host, self.port, clientFactory) 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 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 _TYPECLASS_AGGRESSIVE_CACHE = settings.TYPECLASS_AGGRESSIVE_CACHE
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# Attributes # Attributes
# #
#------------------------------------------------------------ # -------------------------------------------------------------
class Attribute(SharedMemoryModel): class Attribute(SharedMemoryModel):
""" """
@ -90,7 +91,7 @@ class Attribute(SharedMemoryModel):
'date_created', editable=False, auto_now_add=True) 'date_created', editable=False, auto_now_add=True)
# Database manager # Database manager
#objects = managers.AttributeManager() # objects = managers.AttributeManager()
@lazy_property @lazy_property
def locks(self): def locks(self):
@ -110,12 +111,15 @@ class Attribute(SharedMemoryModel):
def __lock_storage_get(self): def __lock_storage_get(self):
return self.db_lock_storage return self.db_lock_storage
def __lock_storage_set(self, value): def __lock_storage_set(self, value):
self.db_lock_storage = value self.db_lock_storage = value
self.save(update_fields=["db_lock_storage"]) self.save(update_fields=["db_lock_storage"])
def __lock_storage_del(self): def __lock_storage_del(self):
self.db_lock_storage = "" self.db_lock_storage = ""
self.save(update_fields=["db_lock_storage"]) self.save(update_fields=["db_lock_storage"])
lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del) lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del)
# Wrapper properties to easily set database fields. These are # Wrapper properties to easily set database fields. These are
@ -127,7 +131,7 @@ class Attribute(SharedMemoryModel):
# is the object in question). # is the object in question).
# value property (wraps db_value) # value property (wraps db_value)
#@property # @property
def __value_get(self): def __value_get(self):
""" """
Getter. Allows for `value = self.value`. Getter. Allows for `value = self.value`.
@ -137,19 +141,19 @@ class Attribute(SharedMemoryModel):
""" """
return from_pickle(self.db_value, db_obj=self) return from_pickle(self.db_value, db_obj=self)
#@value.setter # @value.setter
def __value_set(self, new_value): def __value_set(self, new_value):
""" """
Setter. Allows for self.value = value. We cannot cache here, Setter. Allows for self.value = value. We cannot cache here,
see self.__value_get. see self.__value_get.
""" """
self.db_value = to_pickle(new_value) 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"]) self.save(update_fields=["db_value"])
#@value.deleter # @value.deleter
def __value_del(self): 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() self.delete()
value = property(__value_get, __value_set, __value_del) 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)) return smart_str("%s(%s)" % (self.db_key, self.id))
def __unicode__(self): 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): def access(self, accessing_obj, access_type='read', default=False, **kwargs):
""" """
@ -202,7 +206,7 @@ class AttributeHandler(object):
_attrtype = None _attrtype = None
def __init__(self, obj): def __init__(self, obj):
"Initialize handler." """Initialize handler."""
self.obj = obj self.obj = obj
self._objid = obj.id self._objid = obj.id
self._model = to_str(obj.__dbclass__.__name__.lower()) self._model = to_str(obj.__dbclass__.__name__.lower())
@ -213,16 +217,16 @@ class AttributeHandler(object):
self._cache_complete = False self._cache_complete = False
def _fullcache(self): def _fullcache(self):
"Cache all attributes of this object" """Cache all attributes of this object"""
query = {"%s__id" % self._model : self._objid, query = {"%s__id" % self._model: self._objid,
"attribute__db_model" : self._model, "attribute__db_model": self._model,
"attribute__db_attrtype" : self._attrtype} "attribute__db_attrtype": self._attrtype}
attrs = [conn.attribute for conn in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)] 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(), self._cache = dict(("%s-%s" % (to_str(attr.db_key).lower(),
attr.db_category.lower() if attr.db_category else None), attr.db_category.lower() if attr.db_category else None),
attr) for attr in attrs) attr) for attr in attrs)
self._cache_complete = True self._cache_complete = True
def _getcache(self, key=None, category=None): def _getcache(self, key=None, category=None):
""" """
Retrieve from cache or database (always caches) Retrieve from cache or database (always caches)
@ -264,15 +268,15 @@ class AttributeHandler(object):
del self._cache[cachekey] del self._cache[cachekey]
if cachefound: if cachefound:
if attr: if attr:
return [attr] # return cached entity return [attr] # return cached entity
else: else:
return [] # no such attribute: return an empty list return [] # no such attribute: return an empty list
else: else:
query = {"%s__id" % self._model : self._objid, query = {"%s__id" % self._model: self._objid,
"attribute__db_model" : self._model, "attribute__db_model": self._model,
"attribute__db_attrtype" : self._attrtype, "attribute__db_attrtype": self._attrtype,
"attribute__db_key__iexact" : key.lower(), "attribute__db_key__iexact": key.lower(),
"attribute__db_category__iexact" : category.lower() if category else None} "attribute__db_category__iexact": category.lower() if category else None}
conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query) conn = getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)
if conn: if conn:
attr = conn[0].attribute attr = conn[0].attribute
@ -290,15 +294,15 @@ class AttributeHandler(object):
# for this category before # for this category before
catkey = "-%s" % category catkey = "-%s" % category
if _TYPECLASS_AGGRESSIVE_CACHE and catkey in self._catcache: 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: else:
# we have to query to make this category up-date in the cache # we have to query to make this category up-date in the cache
query = {"%s__id" % self._model : self._objid, query = {"%s__id" % self._model: self._objid,
"attribute__db_model" : self._model, "attribute__db_model": self._model,
"attribute__db_attrtype" : self._attrtype, "attribute__db_attrtype": self._attrtype,
"attribute__db_category__iexact" : category.lower() if category else None} "attribute__db_category__iexact": category.lower() if category else None}
attrs = [conn.attribute for conn in getattr(self.obj, attrs = [conn.attribute for conn
self._m2m_fieldname).through.objects.filter(**query)] in getattr(self.obj, self._m2m_fieldname).through.objects.filter(**query)]
for attr in attrs: for attr in attrs:
if attr.pk: if attr.pk:
cachekey = "%s-%s" % (attr.db_key, category) cachekey = "%s-%s" % (attr.db_key, category)
@ -306,7 +310,6 @@ class AttributeHandler(object):
# mark category cache as up-to-date # mark category cache as up-to-date
self._catcache[catkey] = True self._catcache[catkey] = True
return attrs return attrs
return []
def _setcache(self, key, category, attr_obj): def _setcache(self, key, category, attr_obj):
""" """
@ -318,7 +321,7 @@ class AttributeHandler(object):
attr_obj (Attribute): The newly saved attribute 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 return
cachekey = "%s-%s" % (key, category) cachekey = "%s-%s" % (key, category)
catkey = "-%s" % category catkey = "-%s" % category
@ -342,7 +345,7 @@ class AttributeHandler(object):
self._cache.pop(cachekey, None) self._cache.pop(cachekey, None)
else: else:
self._cache = {key: attrobj for key, attrobj in 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 # mark that the category cache is no longer up-to-date
self._catcache.pop(catkey, None) self._catcache.pop(catkey, None)
self._cache_complete = False self._cache_complete = False
@ -402,6 +405,7 @@ class AttributeHandler(object):
accessing_obj (object, optional): If set, an `attrread` accessing_obj (object, optional): If set, an `attrread`
permission lock will be checked before returning each permission lock will be checked before returning each
looked-after Attribute. looked-after Attribute.
default_access (bool, optional):
Returns: Returns:
result (any, Attribute or list): This will be the value of the found result (any, Attribute or list): This will be the value of the found
@ -416,7 +420,7 @@ class AttributeHandler(object):
""" """
class RetDefault(object): class RetDefault(object):
"Holds default values" """Holds default values"""
def __init__(self): def __init__(self):
self.key = None self.key = None
self.value = default self.value = default
@ -444,8 +448,7 @@ class AttributeHandler(object):
ret = ret if return_obj else [attr.value for attr in ret if attr] ret = ret if return_obj else [attr.value for attr in ret if attr]
if not ret: if not ret:
return ret if len(key) > 1 else default 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="", def add(self, key, value, category=None, lockstring="",
strattr=False, accessing_obj=None, default_access=True): strattr=False, accessing_obj=None, default_access=True):
@ -470,8 +473,7 @@ class AttributeHandler(object):
`attrcreate` is defined on the Attribute in question. `attrcreate` is defined on the Attribute in question.
""" """
if accessing_obj and not self.obj.access(accessing_obj, if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access):
self._attrcreate, default=default_access):
# check create access # check create access
return return
@ -494,21 +496,20 @@ class AttributeHandler(object):
attr_obj.value = value attr_obj.value = value
else: else:
# create a new Attribute (no OOB handlers can be notified) # create a new Attribute (no OOB handlers can be notified)
kwargs = {"db_key" : keystr, kwargs = {"db_key": keystr,
"db_category" : category, "db_category": category,
"db_model" : self._model, "db_model": self._model,
"db_attrtype" : self._attrtype, "db_attrtype": self._attrtype,
"db_value" : None if strattr else to_pickle(value), "db_value": None if strattr else to_pickle(value),
"db_strvalue" : value if strattr else None} "db_strvalue": value if strattr else None}
new_attr = Attribute(**kwargs) new_attr = Attribute(**kwargs)
new_attr.save() new_attr.save()
getattr(self.obj, self._m2m_fieldname).add(new_attr) getattr(self.obj, self._m2m_fieldname).add(new_attr)
# update cache # update cache
self._setcache(keystr, category, new_attr) self._setcache(keystr, category, new_attr)
def batch_add(self, key, value, category=None, lockstring="", 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 Batch-version of `add()`. This is more efficient than
repeat-calling add when having many Attributes to add. 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 RuntimeError: If `key` and `value` lists are not of the
same lengths. same lengths.
""" """
if accessing_obj and not self.obj.access(accessing_obj, if accessing_obj and not self.obj.access(accessing_obj, self._attrcreate, default=default_access):
self._attrcreate, default=default_access):
# check create access # check create access
return return
@ -564,12 +564,12 @@ class AttributeHandler(object):
attr_obj.value = new_value attr_obj.value = new_value
else: else:
# create a new Attribute (no OOB handlers can be notified) # create a new Attribute (no OOB handlers can be notified)
kwargs = {"db_key" : keystr, kwargs = {"db_key": keystr,
"db_category" : category, "db_category": category,
"db_model": self._model, "db_model": self._model,
"db_attrtype" : self._attrtype, "db_attrtype": self._attrtype,
"db_value" : None if strattr else to_pickle(new_value), "db_value": None if strattr else to_pickle(new_value),
"db_strvalue" : value if strattr else None} "db_strvalue": value if strattr else None}
new_attr = Attribute(**kwargs) new_attr = Attribute(**kwargs)
new_attr.save() new_attr.save()
new_attrobjs.append(new_attr) new_attrobjs.append(new_attr)
@ -578,7 +578,6 @@ class AttributeHandler(object):
# Add new objects to m2m field all at once # Add new objects to m2m field all at once
getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs) getattr(self.obj, self._m2m_fieldname).add(*new_attrobjs)
def remove(self, key, raise_exception=False, category=None, def remove(self, key, raise_exception=False, category=None,
accessing_obj=None, default_access=True): accessing_obj=None, default_access=True):
""" """
@ -664,7 +663,7 @@ class AttributeHandler(object):
key=lambda o: o.id) key=lambda o: o.id)
if accessing_obj: if accessing_obj:
return [attr for attr in attrs 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: else:
return attrs return attrs
@ -729,7 +728,6 @@ def initialize_nick_templates(in_template, out_template):
""" """
# create the regex for in_template # create the regex for in_template
regex_string = fnmatch.translate(in_template) regex_string = fnmatch.translate(in_template)
# we must account for a possible line break coming over the wire # we must account for a possible line break coming over the wire
@ -876,12 +874,12 @@ class NickHandler(AttributeHandler):
nicks = {} nicks = {}
for category in make_iter(categories): for category in make_iter(categories):
nicks.update({nick.key: nick 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: if include_player and self.obj.has_player:
for category in make_iter(categories): for category in make_iter(categories):
nicks.update({nick.key: nick nicks.update({nick.key: nick
for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True))
if nick and nick.key}) if nick and nick.key})
for key, nick in nicks.iteritems(): for key, nick in nicks.iteritems():
nick_regex, template, _, _ = nick.value nick_regex, template, _, _ = nick.value
regex = self._regex_cache.get(nick_regex) 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. to apply colour to text according to the ANSI standard.
Examples: 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; Mostly you should not need to call parse_ansi() explicitly;
it is run by Evennia just before returning data to/from the 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 from builtins import object, range
@ -133,7 +136,7 @@ class ANSIParser(object):
rgbtag = rgbmatch.group()[1:] rgbtag = rgbmatch.group()[1:]
background = rgbtag[0] == '[' background = rgbtag[0] == '['
grayscale = rgbtag[0 + int(background)] == '=' grayscale = rgbtag[0 + int(background)] == '='
if not grayscale: if not grayscale:
# 6x6x6 color-cube (xterm indexes 16-231) # 6x6x6 color-cube (xterm indexes 16-231)
if background: if background:
@ -143,9 +146,9 @@ class ANSIParser(object):
else: else:
# grayscale values (xterm indexes 0, 232-255, 15) for full spectrum # grayscale values (xterm indexes 0, 232-255, 15) for full spectrum
letter = rgbtag[int(background) + 1] letter = rgbtag[int(background) + 1]
if (letter == 'a'): if letter == 'a':
colval = 16 # pure black @ index 16 (first color cube entry) 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) colval = 231 # pure white @ index 231 (last color cube entry)
else: else:
# letter in range [b..y] (exactly 24 values!) # letter in range [b..y] (exactly 24 values!)
@ -161,8 +164,8 @@ class ANSIParser(object):
colval = 16 + (red * 36) + (green * 6) + blue colval = 16 + (red * 36) + (green * 6) + blue
return "\033[%s8;5;%sm" % (3 + int(background), colval) 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. # 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) # return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10)
else: else:
# xterm256 not supported, convert the rgb value to ansi instead # xterm256 not supported, convert the rgb value to ansi instead
@ -289,7 +292,7 @@ class ANSIParser(object):
in_string = utils.to_str(string) in_string = utils.to_str(string)
# do string replacement # do string replacement
parsed_string = "" parsed_string = ""
parts = self.ansi_escapes.split(in_string) + [" "] parts = self.ansi_escapes.split(in_string) + [" "]
for part, sep in zip(parts[::2], parts[1::2]): for part, sep in zip(parts[::2], parts[1::2]):
pstring = self.xterm256_sub.sub(do_xterm256, part) pstring = self.xterm256_sub.sub(do_xterm256, part)
@ -307,7 +310,7 @@ class ANSIParser(object):
# cache and crop old cache # cache and crop old cache
_PARSE_CACHE[cachekey] = parsed_string _PARSE_CACHE[cachekey] = parsed_string
if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE: if len(_PARSE_CACHE) > _PARSE_CACHE_SIZE:
_PARSE_CACHE.popitem(last=False) _PARSE_CACHE.popitem(last=False)
return parsed_string return parsed_string
@ -321,8 +324,8 @@ class ANSIParser(object):
(r'{/', ANSI_RETURN), # line break (r'{/', ANSI_RETURN), # line break
(r'{-', ANSI_TAB), # tab (r'{-', ANSI_TAB), # tab
(r'{_', ANSI_SPACE), # space (r'{_', ANSI_SPACE), # space
(r'{*', ANSI_INVERSE), # invert (r'{*', ANSI_INVERSE), # invert
(r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients) (r'{^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'{u', ANSI_UNDERLINE), # underline (r'{u', ANSI_UNDERLINE), # underline
(r'{r', hilite + ANSI_RED), (r'{r', hilite + ANSI_RED),
@ -366,14 +369,14 @@ class ANSIParser(object):
(r'{[W', ANSI_BACK_WHITE), # light grey background (r'{[W', ANSI_BACK_WHITE), # light grey background
(r'{[X', ANSI_BACK_BLACK), # pure black background (r'{[X', ANSI_BACK_BLACK), # pure black background
## alternative |-format # alternative |-format
(r'|n', ANSI_NORMAL), # reset (r'|n', ANSI_NORMAL), # reset
(r'|/', ANSI_RETURN), # line break (r'|/', ANSI_RETURN), # line break
(r'|-', ANSI_TAB), # tab (r'|-', ANSI_TAB), # tab
(r'|_', ANSI_SPACE), # space (r'|_', ANSI_SPACE), # space
(r'|*', ANSI_INVERSE), # invert (r'|*', ANSI_INVERSE), # invert
(r'|^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients) (r'|^', ANSI_BLINK), # blinking text (very annoying and not supported by all clients)
(r'|u', ANSI_UNDERLINE), # underline (r'|u', ANSI_UNDERLINE), # underline
(r'|r', hilite + ANSI_RED), (r'|r', hilite + ANSI_RED),
@ -433,8 +436,7 @@ class ANSIParser(object):
(r'{[w', r'{[555'), # white background (r'{[w', r'{[555'), # white background
(r'{[x', r'{[222'), # dark grey background (r'{[x', r'{[222'), # dark grey background
## |-style variations # |-style variations
(r'|[r', r'|[500'), (r'|[r', r'|[500'),
(r'|[g', r'|[050'), (r'|[g', r'|[050'),
(r'|[y', r'|[550'), (r'|[y', r'|[550'),
@ -448,13 +450,13 @@ class ANSIParser(object):
# the sub_xterm256 method # the sub_xterm256 method
xterm256_map = [ xterm256_map = [
(r'\{[0-5]{3}', ""), # {123 - foreground colour (r'\{[0-5]{3}', ""), # {123 - foreground colour
(r'\{\[[0-5]{3}', ""), # {[123 - background colour (r'\{\[[0-5]{3}', ""), # {[123 - background colour
## |-style # |-style
(r'\|[0-5]{3}', ""), # |123 - foreground colour (r'\|[0-5]{3}', ""), # |123 - foreground colour
(r'\|\[[0-5]{3}', ""), # |[123 - background 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]', ""), (r'\{\[=[a-z]', ""),
(r'\|=[a-z]', ""), (r'\|=[a-z]', ""),
@ -512,6 +514,7 @@ def strip_ansi(string, parser=ANSI_PARSER):
markup. markup.
Args: Args:
string (str): The string to strip.
parser (ansi.AnsiParser, optional): The parser to use. parser (ansi.AnsiParser, optional): The parser to use.
Returns: Returns:
@ -520,6 +523,7 @@ def strip_ansi(string, parser=ANSI_PARSER):
""" """
return parser.parse_ansi(string, strip_ansi=True) return parser.parse_ansi(string, strip_ansi=True)
def strip_raw_ansi(string, parser=ANSI_PARSER): def strip_raw_ansi(string, parser=ANSI_PARSER):
""" """
Remove raw ansi codes from string. This assumes pure Remove raw ansi codes from string. This assumes pure
@ -544,7 +548,7 @@ def raw(string):
string (str): The raw, escaped string. string (str): The raw, escaped string.
""" """
return string.replace('{', '{{') return string.replace('{', '{{').replace('|', '||')
def group(lst, n): def group(lst, n):
@ -1056,7 +1060,7 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
res.append(self[start:len(self)]) res.append(self[start:len(self)])
if drop_spaces: if drop_spaces:
return [part for part in res if part != ""] return [part for part in res if part != ""]
return res return res
def rsplit(self, by=None, maxsplit=-1): def rsplit(self, by=None, maxsplit=-1):
@ -1129,7 +1133,6 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
rstripped = rstripped[::-1] rstripped = rstripped[::-1]
return ANSIString(lstripped + raw[ir1:ir2+1] + rstripped) return ANSIString(lstripped + raw[ir1:ir2+1] + rstripped)
def lstrip(self, chars=None): def lstrip(self, chars=None):
""" """
Strip from the left, taking ANSI markers into account. 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_INSERT = re.compile(r"^\#INSERT (.*)", re.MULTILINE)
_RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE) _RE_CLEANBLOCK = re.compile(r"^\#.*?$|^\s*$", re.MULTILINE)
_RE_CMD_SPLIT = re.compile(r"^\#.*?$", 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 # Helper function
#------------------------------------------------------------ # -------------------------------------------------------------
def read_batchfile(pythonpath, file_ending='.py'): def read_batchfile(pythonpath, file_ending='.py'):
""" """
@ -212,8 +213,7 @@ def read_batchfile(pythonpath, file_ending='.py'):
""" """
# find all possible absolute paths # find all possible absolute paths
abspaths = utils.pypath_to_realpath(pythonpath, abspaths = utils.pypath_to_realpath(pythonpath, file_ending, settings.BASE_BATCHPROCESS_PATHS)
file_ending, settings.BASE_BATCHPROCESS_PATHS)
if not abspaths: if not abspaths:
raise IOError raise IOError
text = None text = None
@ -237,11 +237,11 @@ def read_batchfile(pythonpath, file_ending='.py'):
return text return text
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# Batch-command processor # Batch-command processor
# #
#------------------------------------------------------------ # -------------------------------------------------------------
class BatchCommandProcessor(object): class BatchCommandProcessor(object):
""" """
@ -271,35 +271,35 @@ class BatchCommandProcessor(object):
text = "".join(read_batchfile(pythonpath, file_ending='.ev')) text = "".join(read_batchfile(pythonpath, file_ending='.ev'))
def replace_insert(match): def replace_insert(match):
"Map replace entries" """Map replace entries"""
return "\n#".join(self.parse_file(match.group(1))) return "\n#".join(self.parse_file(match.group(1)))
# insert commands from inserted files # insert commands from inserted files
text = _RE_INSERT.sub(replace_insert, text) 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 # get all commands
commands = _RE_CMD_SPLIT.split(text) commands = _RE_CMD_SPLIT.split(text)
#commands = re.split(r"^\#.*?$", text, flags=re.MULTILINE) # re.split(r"^\#.*?$", text, flags=re.MULTILINE)
#remove eventual newline at the end of commands # remove eventual newline at the end of commands
commands = [c.strip('\r\n') for c in commands] commands = [c.strip('\r\n') for c in commands]
commands = [c for c in commands if c] commands = [c for c in commands if c]
return commands return commands
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# Batch-code processor # Batch-code processor
# #
#------------------------------------------------------------ # -------------------------------------------------------------
def tb_filename(tb): def tb_filename(tb):
"Helper to get filename from traceback" """Helper to get filename from traceback"""
return tb.tb_frame.f_code.co_filename return tb.tb_frame.f_code.co_filename
def tb_iter(tb): def tb_iter(tb):
"Traceback iterator." """Traceback iterator."""
while tb is not None: while tb is not None:
yield tb yield tb
tb = tb.tb_next tb = tb.tb_next
@ -341,7 +341,7 @@ class BatchCodeProcessor(object):
text = "".join(read_batchfile(pythonpath, file_ending='.py')) text = "".join(read_batchfile(pythonpath, file_ending='.py'))
def replace_insert(match): 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) path = match.group(1)
return "# batchcode insert (%s):" % path + "\n".join(self.parse_file(path)) return "# batchcode insert (%s):" % path + "\n".join(self.parse_file(path))
@ -356,7 +356,7 @@ class BatchCodeProcessor(object):
code = text[istart:iend] code = text[istart:iend]
if mtype == "#HEADER": if mtype == "#HEADER":
headers.append(code) headers.append(code)
else: # either #CODE or matching from start of file else: # either #CODE or matching from start of file
codes.append(code) codes.append(code)
# join all headers together to one # 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] codes = ["%s# batchcode code:\n%s" % (header, code) for code in codes]
return codes return codes
def code_exec(self, code, extra_environ=None, debug=False): def code_exec(self, code, extra_environ=None, debug=False):
""" """
Execute a single code block, including imports and appending Execute a single code block, including imports and appending
@ -406,7 +405,7 @@ class BatchCodeProcessor(object):
err = "" err = ""
for iline, line in enumerate(code.split("\n")): for iline, line in enumerate(code.split("\n")):
if iline == lineno: 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: elif lineno - 5 < iline < lineno + 5:
err += "\n%02i: %s" % (iline + 1, line) err += "\n%02i: %s" % (iline + 1, line)

View file

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

View file

@ -33,10 +33,11 @@ from evennia.utils.utils import to_str, uses_database
from evennia.utils import logger from evennia.utils import logger
__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle", __all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle",
"dbserialize", "dbunserialize") "dbserialize", "dbunserialize")
PICKLE_PROTOCOL = 2 PICKLE_PROTOCOL = 2
def _get_mysql_db_version(): def _get_mysql_db_version():
""" """
This is a helper method for specifically getting the version This is a helper method for specifically getting the version
@ -93,7 +94,7 @@ def _TO_DATESTRING(obj):
def _init_globals(): 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 global _FROM_MODEL_MAP, _TO_MODEL_MAP, _SESSION_HANDLER
if not _FROM_MODEL_MAP: if not _FROM_MODEL_MAP:
_FROM_MODEL_MAP = defaultdict(str) _FROM_MODEL_MAP = defaultdict(str)
@ -110,7 +111,7 @@ def _init_globals():
def _save(method): def _save(method):
"method decorator that saves data to Attribute" """method decorator that saves data to Attribute"""
def save_wrapper(self, *args, **kwargs): def save_wrapper(self, *args, **kwargs):
self.__doc__ = method.__doc__ self.__doc__ = method.__doc__
ret = method(self, *args, **kwargs) ret = method(self, *args, **kwargs)
@ -127,17 +128,17 @@ class _SaverMutable(object):
will not save the updated value to the database. will not save the updated value to the database.
""" """
def __init__(self, *args, **kwargs): 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._parent = kwargs.pop("_parent", None)
self._db_obj = kwargs.pop("_db_obj", None) self._db_obj = kwargs.pop("_db_obj", None)
self._data = None self._data = None
def __nonzero__(self): def __nonzero__(self):
"Make sure to evaluate as False if empty" """Make sure to evaluate as False if empty"""
return bool(self._data) return bool(self._data)
def _save_tree(self): 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: if self._parent:
self._parent._save_tree() self._parent._save_tree()
elif self._db_obj: elif self._db_obj:
@ -146,9 +147,9 @@ class _SaverMutable(object):
logger.log_err("_SaverMutable %s has no root Attribute to save to." % self) logger.log_err("_SaverMutable %s has no root Attribute to save to." % self)
def _convert_mutables(self, data): 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): def process_tree(item, parent):
"recursively populate the tree, storing parents" """recursively populate the tree, storing parents"""
dtype = type(item) dtype = type(item)
if dtype in (basestring, int, float, bool, tuple): if dtype in (basestring, int, float, bool, tuple):
return item return item
@ -218,7 +219,6 @@ class _SaverList(_SaverMutable, MutableSequence):
return self._data.index(value, *args) return self._data.index(value, *args)
class _SaverDict(_SaverMutable, MutableMapping): class _SaverDict(_SaverMutable, MutableMapping):
""" """
A dict that stores changes to an Attribute when updated A dict that stores changes to an Attribute when updated
@ -290,8 +290,10 @@ class _SaverDeque(_SaverMutable):
# maxlen property # maxlen property
def _getmaxlen(self): def _getmaxlen(self):
return self._data.maxlen return self._data.maxlen
def _setmaxlen(self, value): def _setmaxlen(self, value):
self._data.maxlen = value self._data.maxlen = value
def _delmaxlen(self): def _delmaxlen(self):
del self._data.maxlen del self._data.maxlen
maxlen = property(_getmaxlen, _setmaxlen, _delmaxlen) maxlen = property(_getmaxlen, _setmaxlen, _delmaxlen)
@ -314,7 +316,7 @@ class _SaverDeque(_SaverMutable):
# #
# serialization helpers # serialization helpers
#
def pack_dbobj(item): def pack_dbobj(item):
""" """
@ -335,7 +337,7 @@ def pack_dbobj(item):
# build the internal representation as a tuple # build the internal representation as a tuple
# ("__packed_dbobj__", key, creation_time, id) # ("__packed_dbobj__", key, creation_time, id)
return natural_key and ('__packed_dbobj__', natural_key, 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): def unpack_dbobj(item):
@ -391,10 +393,11 @@ def pack_session(item):
# to be accepted as actually being a session (sessids gets # to be accepted as actually being a session (sessids gets
# reused all the time). # reused all the time).
return item.conn_time and item.sessid and ('__packed_session__', return item.conn_time and item.sessid and ('__packed_session__',
_GA(item, "sessid"), _GA(item, "sessid"),
_GA(item, "conn_time")) _GA(item, "conn_time"))
return None return None
def unpack_session(item): def unpack_session(item):
""" """
Check and convert internal representations back to Sessions. Check and convert internal representations back to Sessions.
@ -419,7 +422,7 @@ def unpack_session(item):
# #
# Access methods # Access methods
#
def to_pickle(data): def to_pickle(data):
""" """
@ -437,7 +440,7 @@ def to_pickle(data):
""" """
def process_item(item): def process_item(item):
"Recursive processor and identification of data" """Recursive processor and identification of data"""
dtype = type(item) dtype = type(item)
if dtype in (basestring, int, float, bool): if dtype in (basestring, int, float, bool):
return item return item
@ -466,7 +469,7 @@ def to_pickle(data):
return process_item(data) return process_item(data)
#@transaction.autocommit # @transaction.autocommit
def from_pickle(data, db_obj=None): def from_pickle(data, db_obj=None):
""" """
This should be fed a just de-pickled data object. It will be converted back 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): def process_item(item):
"Recursive processor and identification of data" """Recursive processor and identification of data"""
dtype = type(item) dtype = type(item)
if dtype in (basestring, int, float, bool): if dtype in (basestring, int, float, bool):
return item return item
@ -518,7 +521,7 @@ def from_pickle(data, db_obj=None):
return item return item
def process_tree(item, parent): 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) dtype = type(item)
if dtype in (basestring, int, float, bool): if dtype in (basestring, int, float, bool):
return item return item
@ -534,7 +537,7 @@ def from_pickle(data, db_obj=None):
elif dtype == dict: elif dtype == dict:
dat = _SaverDict(_parent=parent) dat = _SaverDict(_parent=parent)
dat._data.update((process_item(key), process_tree(val, dat)) dat._data.update((process_item(key), process_tree(val, dat))
for key, val in item.items()) for key, val in item.items())
return dat return dat
elif dtype == set: elif dtype == set:
dat = _SaverSet(_parent=parent) dat = _SaverSet(_parent=parent)
@ -543,7 +546,7 @@ def from_pickle(data, db_obj=None):
elif dtype == OrderedDict: elif dtype == OrderedDict:
dat = _SaverOrderedDict(_parent=parent) dat = _SaverOrderedDict(_parent=parent)
dat._data.update((process_item(key), process_tree(val, dat)) dat._data.update((process_item(key), process_tree(val, dat))
for key, val in item.items()) for key, val in item.items())
return dat return dat
elif dtype == deque: elif dtype == deque:
dat = _SaverDeque(_parent=parent) dat = _SaverDeque(_parent=parent)
@ -571,7 +574,7 @@ def from_pickle(data, db_obj=None):
elif dtype == dict: elif dtype == dict:
dat = _SaverDict(_db_obj=db_obj) dat = _SaverDict(_db_obj=db_obj)
dat._data.update((process_item(key), process_tree(val, dat)) dat._data.update((process_item(key), process_tree(val, dat))
for key, val in data.items()) for key, val in data.items())
return dat return dat
elif dtype == set: elif dtype == set:
dat = _SaverSet(_db_obj=db_obj) dat = _SaverSet(_db_obj=db_obj)
@ -580,7 +583,7 @@ def from_pickle(data, db_obj=None):
elif dtype == OrderedDict: elif dtype == OrderedDict:
dat = _SaverOrderedDict(_db_obj=db_obj) dat = _SaverOrderedDict(_db_obj=db_obj)
dat._data.update((process_item(key), process_tree(val, dat)) dat._data.update((process_item(key), process_tree(val, dat))
for key, val in data.items()) for key, val in data.items())
return dat return dat
elif dtype == deque: elif dtype == deque:
dat = _SaverDeque(_db_obj=db_obj) dat = _SaverDeque(_db_obj=db_obj)
@ -590,20 +593,20 @@ def from_pickle(data, db_obj=None):
def do_pickle(data): def do_pickle(data):
"Perform pickle to string" """Perform pickle to string"""
return to_str(dumps(data, protocol=PICKLE_PROTOCOL)) return to_str(dumps(data, protocol=PICKLE_PROTOCOL))
def do_unpickle(data): def do_unpickle(data):
"Retrieve pickle from pickled string" """Retrieve pickle from pickled string"""
return loads(to_str(data)) return loads(to_str(data))
def dbserialize(data): def dbserialize(data):
"Serialize to pickled form in one step" """Serialize to pickled form in one step"""
return do_pickle(to_pickle(data)) return do_pickle(to_pickle(data))
def dbunserialize(data, db_obj=None): 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) 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? # use NAWS in the future?
_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# texts # texts
# #
#------------------------------------------------------------ # -------------------------------------------------------------
_HELP_TEXT = \ _HELP_TEXT = """
"""
<txt> - any non-command is appended to the end of the buffer. <txt> - any non-command is appended to the end of the buffer.
: <l> - view buffer or only line(s) <l> : <l> - view buffer or only line(s) <l>
:: <l> - raw-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) :echo - turn echoing of the input on/off (helpful for some clients)
""" """
_HELP_LEGEND = \ _HELP_LEGEND = """
"""
Legend: Legend:
<l> - line number, like '5' or range, like '3:7'. <l> - line number, like '5' or range, like '3:7'.
<w> - a single word, or multiple words with quotes around them. <w> - a single word, or multiple words with quotes around them.
<txt> - longer string, usually not needing quotes. <txt> - longer string, usually not needing quotes.
""" """
_HELP_CODE = \ _HELP_CODE = """
"""
:! - Execute code buffer without saving :! - Execute code buffer without saving
:< - Decrease the level of automatic indentation for the next lines :< - Decrease the level of automatic indentation for the next lines
:> - Increase the level of automatic indentation for the next lines :> - Increase the level of automatic indentation for the next lines
:= - Switch automatic indentation on/off := - Switch automatic indentation on/off
""".lstrip("\n") """.lstrip("\n")
_ERROR_LOADFUNC = \ _ERROR_LOADFUNC = """
"""
{error} {error}
|rBuffer load function error. Could not load initial data.|n |rBuffer load function error. Could not load initial data.|n
""" """
_ERROR_SAVEFUNC = \ _ERROR_SAVEFUNC = """
"""
{error} {error}
|rSave function returned an error. Buffer not saved.|n |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" _MSG_SAVE_NO_CHANGE = "No changes need saving"
_DEFAULT_NO_QUITFUNC = "Exited editor." _DEFAULT_NO_QUITFUNC = "Exited editor."
_ERROR_QUITFUNC = \ _ERROR_QUITFUNC = """
"""
{error} {error}
|rQuit function gave an error. Skipping.|n |rQuit function gave an error. Skipping.|n
""" """
_ERROR_PERSISTENT_SAVING = \ _ERROR_PERSISTENT_SAVING = """
"""
{error} {error}
|rThe editor state could not be saved for persistent mode. Switching |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 = \ _TRACE_PERSISTENT_SAVING = \
"EvEditor persistent-mode error. Commonly, this is because one or " \ "EvEditor persistent-mode error. Commonly, this is because one or " \
"more of the EvEditor callbacks could not be pickled, for example " \ "more of the EvEditor callbacks could not be pickled, for example " \
"because it's a class method or is defined inside another function." "because it's a class method or is defined inside another function."
_MSG_NO_UNDO = "Nothing to undo." _MSG_NO_UNDO = "Nothing to undo."
@ -167,11 +160,12 @@ _MSG_NO_REDO = "Nothing to redo."
_MSG_UNDO = "Undid one step." _MSG_UNDO = "Undid one step."
_MSG_REDO = "Redid one step." _MSG_REDO = "Redid one step."
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# Handle yes/no quit question # Handle yes/no quit question
# #
#------------------------------------------------------------ # -------------------------------------------------------------
class CmdSaveYesNo(Command): class CmdSaveYesNo(Command):
""" """
@ -185,7 +179,7 @@ class CmdSaveYesNo(Command):
help_cateogory = "LineEditor" help_cateogory = "LineEditor"
def func(self): def func(self):
"Implement the yes/no choice." """Implement the yes/no choice."""
# this is only called from inside the lineeditor # this is only called from inside the lineeditor
# so caller.ndb._lineditor must be set. # so caller.ndb._lineditor must be set.
@ -200,21 +194,21 @@ class CmdSaveYesNo(Command):
class SaveYesNoCmdSet(CmdSet): class SaveYesNoCmdSet(CmdSet):
"Stores the yesno question" """Stores the yesno question"""
key = "quitsave_yesno" key = "quitsave_yesno"
priority = 1 priority = 1
mergetype = "Replace" mergetype = "Replace"
def at_cmdset_creation(self): def at_cmdset_creation(self):
"at cmdset creation" """at cmdset creation"""
self.add(CmdSaveYesNo()) self.add(CmdSaveYesNo())
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# Editor commands # Editor commands
# #
#------------------------------------------------------------ # -------------------------------------------------------------
class CmdEditorBase(Command): class CmdEditorBase(Command):
""" """
@ -239,7 +233,6 @@ class CmdEditorBase(Command):
txt - extra text (string), could be encased in quotes. txt - extra text (string), could be encased in quotes.
""" """
linebuffer = []
editor = self.caller.ndb._eveditor editor = self.caller.ndb._eveditor
if not editor: if not editor:
# this will completely replace the editor # this will completely replace the editor
@ -297,11 +290,7 @@ class CmdEditorBase(Command):
arglist = arglist[1:] arglist = arglist[1:]
# nicer output formatting of the line range. # nicer output formatting of the line range.
lstr = "" lstr = "line %i" % (lstart + 1) if not linerange or lstart + 1 == lend else "lines %i-%i" % (lstart + 1, lend)
if not linerange or lstart + 1 == lend:
lstr = "line %i" % (lstart + 1)
else:
lstr = "lines %i-%i" % (lstart + 1, lend)
# arg1 and arg2 is whatever arguments. Line numbers or -ranges are # arg1 and arg2 is whatever arguments. Line numbers or -ranges are
# never included here. # never included here.
@ -369,25 +358,14 @@ class CmdLineInput(CmdEditorBase):
""" """
caller = self.caller caller = self.caller
editor = caller.ndb._eveditor editor = caller.ndb._eveditor
buf = editor.get_buffer() buf = editor.get_buffer()
# add a line of text to buffer # add a line of text to buffer
line = self.raw_string.strip("\r\n") line = self.raw_string.strip("\r\n")
if not editor._codefunc: if editor._codefunc and editor._indent >= 0:
if not buf:
buf = line
else:
buf = buf + "\n%s" % line
else:
# if automatic indentation is active, add spaces # if automatic indentation is active, add spaces
if editor._indent >= 0: line = editor.deduce_indent(line, buf)
line = editor.deduce_indent(line, buf) buf = line if not buf else buf + "\n%s" % line
if not buf:
buf = line
else:
buf = buf + "\n%s" % line
self.editor.update_buffer(buf) self.editor.update_buffer(buf)
if self.editor._echo_mode: if self.editor._echo_mode:
# need to do it here or we will be off one line # need to do it here or we will be off one line
@ -398,11 +376,10 @@ class CmdLineInput(CmdEditorBase):
if indent < 0: if indent < 0:
indent = "off" 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)) cline, indent, line))
else: else:
self.caller.msg("{b%02i|{n %s" % (cline, self.args)) self.caller.msg("|b%02i|||n %s" % (cline, self.args))
class CmdEditorGroup(CmdEditorBase): class CmdEditorGroup(CmdEditorBase):
@ -410,7 +387,7 @@ class CmdEditorGroup(CmdEditorBase):
Commands for the editor Commands for the editor
""" """
key = ":editor_command_group" 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", ":dd", ":dw", ":DD", ":y", ":x", ":p", ":i", ":j",
":r", ":I", ":A", ":s", ":S", ":f", ":fi", ":fd", ":echo", ":r", ":I", ":A", ":s", ":S", ":f", ":fi", ":fd", ":echo",
":!", ":<", ":>", ":="] ":!", ":<", ":>", ":="]
@ -443,9 +420,9 @@ class CmdEditorGroup(CmdEditorBase):
buf = linebuffer[lstart:lend] buf = linebuffer[lstart:lend]
editor.display_buffer(buf=buf, editor.display_buffer(buf=buf,
offset=lstart, offset=lstart,
linenums=False, options={"raw":True}) linenums=False, options={"raw": True})
else: else:
editor.display_buffer(linenums=False, options={"raw":True}) editor.display_buffer(linenums=False, options={"raw": True})
elif cmd == ":::": elif cmd == ":::":
# Insert single colon alone on a line # Insert single colon alone on a line
editor.update_buffer(editor.buffer + "\n:") editor.update_buffer(editor.buffer + "\n:")
@ -485,7 +462,7 @@ class CmdEditorGroup(CmdEditorBase):
# :dd <l> - delete line <l> # :dd <l> - delete line <l>
buf = linebuffer[:lstart] + linebuffer[lend:] buf = linebuffer[:lstart] + linebuffer[lend:]
editor.update_buffer(buf) editor.update_buffer(buf)
caller.msg("Deleted %s." % (self.lstr)) caller.msg("Deleted %s." % self.lstr)
elif cmd == ":dw": elif cmd == ":dw":
# :dw <w> - delete word in entire buffer # :dw <w> - delete word in entire buffer
# :dw <l> <w> delete word only on line(s) <l> # :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: if not self.raw_string and not editor._codefunc:
caller.msg("You need to enter text to insert.") caller.msg("You need to enter text to insert.")
else: 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) editor.update_buffer(buf)
caller.msg("Inserted text at beginning of %s." % self.lstr) caller.msg("Inserted text at beginning of %s." % self.lstr)
elif cmd == ":A": elif cmd == ":A":
@ -564,7 +542,8 @@ class CmdEditorGroup(CmdEditorBase):
if not self.args: if not self.args:
caller.msg("You need to enter text to append.") caller.msg("You need to enter text to append.")
else: 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) editor.update_buffer(buf)
caller.msg("Appended text to end of %s." % self.lstr) caller.msg("Appended text to end of %s." % self.lstr)
elif cmd == ":s": elif cmd == ":s":
@ -576,7 +555,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange: if not self.linerange:
lstart = 0 lstart = 0
lend = self.cline + 1 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: else:
caller.msg("Search-replaced %s -> %s for %s." % (self.arg1, self.arg2, self.lstr)) caller.msg("Search-replaced %s -> %s for %s." % (self.arg1, self.arg2, self.lstr))
sarea = "\n".join(linebuffer[lstart:lend]) sarea = "\n".join(linebuffer[lstart:lend])
@ -585,7 +564,8 @@ class CmdEditorGroup(CmdEditorBase):
regarg = self.arg1.strip("\'").strip('\"') regarg = self.arg1.strip("\'").strip('\"')
if " " in regarg: if " " in regarg:
regarg = regarg.replace(" ", " +") 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:] buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:]
editor.update_buffer(buf) editor.update_buffer(buf)
elif cmd == ":f": elif cmd == ":f":
@ -594,7 +574,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange: if not self.linerange:
lstart = 0 lstart = 0
lend = self.cline + 1 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: else:
caller.msg("Flood filled %s." % self.lstr) caller.msg("Flood filled %s." % self.lstr)
fbuf = "\n".join(linebuffer[lstart:lend]) 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 <l> <w> justify buffer of <l> with <w> as align (one of
# f(ull), c(enter), r(ight) or l(left). Default is full. # f(ull), c(enter), r(ight) or l(left). Default is full.
align_map = {"full": "f", "f": "f", "center": "c", "c": "c", 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"} align_name = {"f": "Full", "c": "Center", "l": "Left", "r": "Right"}
width = _DEFAULT_WIDTH width = _DEFAULT_WIDTH
if self.arg1 and self.arg1.lower() not in align_map: if self.arg1 and self.arg1.lower() not in align_map:
@ -628,7 +608,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange: if not self.linerange:
lstart = 0 lstart = 0
lend = self.cline + 1 lend = self.cline + 1
caller.msg("Indented lines %i-%i." % (lstart + 1 , lend)) caller.msg("Indented lines %i-%i." % (lstart + 1, lend))
else: else:
caller.msg("Indented %s." % self.lstr) caller.msg("Indented %s." % self.lstr)
fbuf = [indent + line for line in linebuffer[lstart:lend]] fbuf = [indent + line for line in linebuffer[lstart:lend]]
@ -639,7 +619,7 @@ class CmdEditorGroup(CmdEditorBase):
if not self.linerange: if not self.linerange:
lstart = 0 lstart = 0
lend = self.cline + 1 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: else:
caller.msg("Removed left margin (dedented) %s." % self.lstr) caller.msg("Removed left margin (dedented) %s." % self.lstr)
fbuf = "\n".join(linebuffer[lstart:lend]) fbuf = "\n".join(linebuffer[lstart:lend])
@ -693,18 +673,20 @@ class CmdEditorGroup(CmdEditorBase):
class EvEditorCmdSet(CmdSet): class EvEditorCmdSet(CmdSet):
"CmdSet for the editor commands" """CmdSet for the editor commands"""
key = "editorcmdset" key = "editorcmdset"
mergetype = "Replace" mergetype = "Replace"
def at_cmdset_creation(self): def at_cmdset_creation(self):
self.add(CmdLineInput()) self.add(CmdLineInput())
self.add(CmdEditorGroup()) self.add(CmdEditorGroup())
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# Main Editor object # Main Editor object
# #
#------------------------------------------------------------ # -------------------------------------------------------------
class EvEditor(object): class EvEditor(object):
""" """
@ -790,12 +772,10 @@ class EvEditor(object):
if persistent: if persistent:
# save in tuple {kwargs, other options} # save in tuple {kwargs, other options}
try: try:
caller.attributes.add("_eveditor_saved",( caller.attributes.add("_eveditor_saved", (
{"loadfunc":loadfunc, "savefunc": savefunc, dict(loadfunc=loadfunc, savefunc=savefunc, quitfunc=quitfunc,
"quitfunc": quitfunc, "codefunc": codefunc, codefunc=codefunc, key=key, persistent=persistent),
"key": key, "persistent": persistent}, dict(_pristine_buffer=self._pristine_buffer, _sep=self._sep)))
{"_pristine_buffer": self._pristine_buffer,
"_sep": self._sep}))
caller.attributes.add("_eveditor_buffer_temp", (self._buffer, self._undo_buffer)) caller.attributes.add("_eveditor_buffer_temp", (self._buffer, self._undo_buffer))
caller.attributes.add("_eveditor_unsaved", False) caller.attributes.add("_eveditor_unsaved", False)
caller.attributes.add("_eveditor_indent", 0) 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_buffer = self._undo_buffer[:self._undo_pos + 1] + [self._buffer]
self._undo_pos = len(self._undo_buffer) - 1 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. 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 `offset` should define the actual starting line number, to
get the linenum display right. get the linenum display right.
linenums (bool, optional): Show line numbers in buffer. 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. formatting information.
""" """
@ -949,10 +929,10 @@ class EvEditor(object):
sep = self._sep sep = self._sep
header = "|n" + sep * 10 + "Line Editor [%s]" % self._key + sep * (_DEFAULT_WIDTH-20-len(self._key)) 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) \ footer = "|n" + sep * 10 +\
+ sep * 12 + "(:h for help)" + sep * 28 "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars) + sep * 12 + "(:h for help)" + sep * 28
if linenums: 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: else:
main = "\n".join(lines) main = "\n".join(lines)
string = "%s\n%s\n%s" % (header, main, footer) string = "%s\n%s\n%s" % (header, main, footer)
@ -1018,6 +998,7 @@ class EvEditor(object):
self._indent += 1 self._indent += 1
if self._persistent: if self._persistent:
self._caller.attributes.add("_eveditor_indent", self._indent) self._caller.attributes.add("_eveditor_indent", self._indent)
def swap_autoindent(self): def swap_autoindent(self):
"""Swap automatic indentation on or off.""" """Swap automatic indentation on or off."""
if self._codefunc: 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 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 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 keyword options of the Menu() initialization call you can start the
menu at different places in the menu definition file, adjust if the menu at different places in the menu definition file, adjust if the
menu command should overload the normal commands or not, etc. 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 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 node and callback in the menu must be possible to be *pickled*, this
excludes e.g. callables that are class methods or functions defined 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. no such restrictions exist.
The menu is defined in a module (this can be the same module as the 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 ```python
@ -181,8 +181,7 @@ _HELP_NO_OPTIONS = _("Commands: help, quit")
_HELP_NO_OPTIONS_NO_QUIT = _("Commands: help") _HELP_NO_OPTIONS_NO_QUIT = _("Commands: help")
_HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.") _HELP_NO_OPTION_MATCH = _("Choose an option or try 'help'.")
_ERROR_PERSISTENT_SAVING = \ _ERROR_PERSISTENT_SAVING = """
"""
{error} {error}
|rThe menu state could not be saved for persistent mode. Switching |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 an eventual server reload).|n
""" """
_TRACE_PERSISTENT_SAVING = \ _TRACE_PERSISTENT_SAVING = "EvMenu persistent-mode error. Commonly, this is because one or " \
"EvMenu persistent-mode error. Commonly, this is because one or " \ "more of the EvEditor callbacks could not be pickled, for example " \
"more of the EvEditor callbacks could not be pickled, for example " \ "because it's a class method or is defined inside another function."
"because it's a class method or is defined inside another function."
class EvMenuError(RuntimeError): class EvMenuError(RuntimeError):
@ -203,11 +201,12 @@ class EvMenuError(RuntimeError):
""" """
pass pass
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# Menu command and command set # Menu command and command set
# #
#------------------------------------------------------------ # -------------------------------------------------------------
class CmdEvMenuNode(Command): class CmdEvMenuNode(Command):
""" """
@ -227,7 +226,11 @@ class CmdEvMenuNode(Command):
# this will re-start a completely new evmenu call. # this will re-start a completely new evmenu call.
saved_options = caller.attributes.get("_menutree_saved") saved_options = caller.attributes.get("_menutree_saved")
if saved_options: 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: if startnode:
saved_options[2]["startnode"] = startnode saved_options[2]["startnode"] = startnode
saved_options[2]["startnode_input"] = startnode_input saved_options[2]["startnode_input"] = startnode_input
@ -254,7 +257,7 @@ class CmdEvMenuNode(Command):
menu = caller.ndb._menutree menu = caller.ndb._menutree
if not menu: if not menu:
# can't restore from a session # 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 orig_caller.msg(err) # don't give the session as a kwarg here, direct to original
raise EvMenuError(err) raise EvMenuError(err)
# we must do this after the caller with the menui has been correctly identified since it # 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 # Menu main class
# #
#------------------------------------------------------------ # -------------------------------------------------------------
class EvMenu(object): class EvMenu(object):
""" """
@ -436,7 +440,17 @@ class EvMenu(object):
"persistent": persistent} "persistent": persistent}
calldict.update(kwargs) calldict.update(kwargs)
try: 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)) caller.attributes.add("_menutree_saved_startnode", (startnode, startnode_input))
except Exception as err: except Exception as err:
caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err), session=self._session) caller.msg(_ERROR_PERSISTENT_SAVING.format(error=err), session=self._session)
@ -505,7 +519,6 @@ class EvMenu(object):
# format the entire node # format the entire node
return self.node_formatter(nodetext, optionstext) return self.node_formatter(nodetext, optionstext)
def _execute_node(self, nodename, raw_string): def _execute_node(self, nodename, raw_string):
""" """
Execute a node. Execute a node.
@ -542,15 +555,12 @@ class EvMenu(object):
raise raise
return nodetext, options return nodetext, options
def display_nodetext(self): def display_nodetext(self):
self.caller.msg(self.nodetext, session=self._session) self.caller.msg(self.nodetext, session=self._session)
def display_helptext(self): def display_helptext(self):
self.caller.msg(self.helptext, session=self._session) self.caller.msg(self.helptext, session=self._session)
def callback_goto(self, callback, goto, raw_string): def callback_goto(self, callback, goto, raw_string):
""" """
Call callback and goto in sequence. Call callback and goto in sequence.
@ -860,25 +870,29 @@ class CmdGetInput(Command):
aliases = _CMD_NOINPUT aliases = _CMD_NOINPUT
def func(self): def func(self):
"This is called when user enters anything." """This is called when user enters anything."""
caller = self.caller caller = self.caller
callback = caller.ndb._getinput._callback try:
if not callback: getinput = caller.ndb._getinput
# this can be happen if called from a player-command when IC if not getinput and hasattr(caller, "player"):
caller = self.player getinput = caller.player.ndb._getinput
callback = caller.ndb._getinput._callback caller = caller.player
if not callback: callback = getinput._callback
raise RuntimeError("No input callback found.")
caller.ndb._getinput._session = self.session caller.ndb._getinput._session = self.session
prompt = caller.ndb._getinput._prompt prompt = caller.ndb._getinput._prompt
result = self.raw_string.strip() # we strip the ending line break caused by sending result = self.raw_string.strip() # we strip the ending line break caused by sending
ok = not callback(caller, prompt, result) ok = not callback(caller, prompt, result)
if ok: if ok:
# only clear the state if the callback does not return # only clear the state if the callback does not return
# anything # anything
del caller.ndb._getinput 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) caller.cmdset.remove(InputCmdSet)
@ -894,12 +908,12 @@ class InputCmdSet(CmdSet):
no_channels = False no_channels = False
def at_cmdset_creation(self): def at_cmdset_creation(self):
"called once at creation" """called once at creation"""
self.add(CmdGetInput()) self.add(CmdGetInput())
class _Prompt(object): class _Prompt(object):
"Dummy holder" """Dummy holder"""
pass pass
@ -958,11 +972,11 @@ def get_input(caller, prompt, callback, session=None):
caller.msg(prompt, session=session) caller.msg(prompt, session=session)
#------------------------------------------------------------ # -------------------------------------------------------------
# #
# test menu strucure and testing command # test menu strucure and testing command
# #
#------------------------------------------------------------ # -------------------------------------------------------------
def test_start_node(caller): def test_start_node(caller):
menu = caller.ndb._menutree menu = caller.ndb._menutree
@ -978,17 +992,17 @@ def test_start_node(caller):
The menu was initialized with two variables: %s and %s. The menu was initialized with two variables: %s and %s.
""" % (menu.testval, menu.testval2) """ % (menu.testval, menu.testval2)
options = ({"key": ("{yS{net", "s"), options = ({"key": ("|yS|net", "s"),
"desc": "Set an attribute on yourself.", "desc": "Set an attribute on yourself.",
"exec": lambda caller: caller.attributes.add("menuattrtest", "Test value"), "exec": lambda caller: caller.attributes.add("menuattrtest", "Test value"),
"goto": "test_set_node"}, "goto": "test_set_node"},
{"key": ("{yL{nook", "l"), {"key": ("|yL|nook", "l"),
"desc": "Look and see a custom message.", "desc": "Look and see a custom message.",
"goto": "test_look_node"}, "goto": "test_look_node"},
{"key": ("{yV{niew", "v"), {"key": ("|yV|niew", "v"),
"desc": "View your own name", "desc": "View your own name",
"goto": "test_view_node"}, "goto": "test_view_node"},
{"key": ("{yQ{nuit", "quit", "q", "Q"), {"key": ("|yQ|nuit", "quit", "q", "Q"),
"desc": "Quit this menu example.", "desc": "Quit this menu example.",
"goto": "test_end_node"}, "goto": "test_end_node"},
{"key": "_default", {"key": "_default",
@ -998,16 +1012,17 @@ def test_start_node(caller):
def test_look_node(caller): def test_look_node(caller):
text = "" text = ""
options = {"key": ("{yL{nook", "l"), options = {"key": ("|yL|nook", "l"),
"desc": "Go back to the previous menu.", "desc": "Go back to the previous menu.",
"goto": "test_start_node"} "goto": "test_start_node"}
return text, options return text, options
def test_set_node(caller): def test_set_node(caller):
text = (""" text = ("""
The attribute 'menuattrtest' was set to The attribute 'menuattrtest' was set to
{w%s{n |w%s|n
(check it with examine after quitting the menu). (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 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 to return to the main menu. So you can e.g. press <return> to go
back now. back now.
""" % caller.db.menuattrtest, """ % caller.db.menuattrtest, # optional help text for this node
# optional help text for this node """
"""
This is the help entry for this node. It is created by returning 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 the node text as a tuple - the second string in that tuple will be
used as the help text. used as the help text.
@ -1031,7 +1045,7 @@ def test_set_node(caller):
def test_view_node(caller): def test_view_node(caller):
text = """ text = """
Your name is {g%s{n! Your name is |g%s|n!
click |lclook|lthere|le to trigger a look command under MXP. click |lclook|lthere|le to trigger a look command under MXP.
This node's option has no explicit key (nor the "_default" key This node's option has no explicit key (nor the "_default" key
@ -1044,11 +1058,11 @@ def test_view_node(caller):
return text, options return text, options
def test_displayinput_node(caller, raw_string): def test_displayinput_node(caller, raw_string):
text = """ text = """
You entered the text: You entered the text:
"{w%s{n" "|w%s|n"
... which could now be handled or stored here in some way if this ... which could now be handled or stored here in some way if this
was not just an example. was not just an example.
@ -1059,7 +1073,7 @@ def test_displayinput_node(caller, raw_string):
to the start node. to the start node.
""" % raw_string """ % raw_string
options = {"key": "_default", options = {"key": "_default",
"goto": "test_start_node"} "goto": "test_start_node"}
return text, options return text, options
@ -1089,5 +1103,5 @@ class CmdTestMenu(Command):
self.caller.msg("Usage: testmenu menumodule") self.caller.msg("Usage: testmenu menumodule")
return return
# start menu # start menu
EvMenu(self.caller, self.args.strip(), startnode="test_start_node", persistent=True, cmdset_mergetype="Replace", EvMenu(self.caller, self.args.strip(), startnode="test_start_node", persistent=True,
testval="val", testval2="val2") cmdset_mergetype="Replace", testval="val", testval2="val2")

View file

@ -181,7 +181,7 @@ class EvMore(object):
lines.append(line) lines.append(line)
# always limit number of chars to 10 000 per page # 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._pages = ["\n".join(lines[i:i+height]) for i in range(0, len(lines), height)]
self._npages = len(self._pages) self._npages = len(self._pages)

View file

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

View file

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

View file

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

View file

@ -82,9 +82,9 @@ many traits with a normal *goblin*.
from __future__ import print_function from __future__ import print_function
import copy import copy
#TODO # TODO
#sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) # sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
#os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings' # os.environ['DJANGO_SETTINGS_MODULE'] = 'game.settings'
from django.conf import settings from django.conf import settings
from random import randint from random import randint
@ -131,9 +131,10 @@ def _get_prototype(dic, prot, protparents):
new_prot = _get_prototype(protparents.get(prototype, {}), prot, protparents) new_prot = _get_prototype(protparents.get(prototype, {}), prot, protparents)
prot.update(new_prot) prot.update(new_prot)
prot.update(dic) 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 return prot
def _batch_create_object(*objparams): def _batch_create_object(*objparams):
""" """
This is a cut-down version of the create_object() function, 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! so make sure the spawned Typeclass works before using this!
Args: 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 for the respective creation/add handlers in the following
order: (create, permissions, locks, aliases, nattributes, order: (create, permissions, locks, aliases, nattributes,
attributes) attributes)
@ -153,8 +154,9 @@ def _batch_create_object(*objparams):
# bulk create all objects in one go # bulk create all objects in one go
# unfortunately this doesn't work since bulk_create doesn't creates pks; # unfortunately this doesn't work since bulk_create doesn't creates pks;
# the result are double objects at the next stage # the result would be duplicate objects at the next stage, so we comment
#dbobjs = _ObjectDB.objects.bulk_create(dbobjs) # it out for now:
# dbobjs = _ObjectDB.objects.bulk_create(dbobjs)
dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams] dbobjs = [ObjectDB(**objparam[0]) for objparam in objparams]
objs = [] objs = []
@ -167,7 +169,7 @@ def _batch_create_object(*objparams):
"aliases": objparam[3], "aliases": objparam[3],
"nattributes": objparam[4], "nattributes": objparam[4],
"attributes": objparam[5], "attributes": objparam[5],
"tags":objparam[6]} "tags": objparam[6]}
# this triggers all hooks # this triggers all hooks
obj.save() obj.save()
# run eventual extra code # run eventual extra code
@ -201,9 +203,9 @@ def spawn(*prototypes, **kwargs):
if not protmodules and hasattr(settings, "PROTOTYPE_MODULES"): if not protmodules and hasattr(settings, "PROTOTYPE_MODULES"):
protmodules = make_iter(settings.PROTOTYPE_MODULES) protmodules = make_iter(settings.PROTOTYPE_MODULES)
for prototype_module in protmodules: for prototype_module in protmodules:
protparents.update(dict((key, val) protparents.update(dict((key, val) for key, val in
for key, val in all_from_module(prototype_module).items() if isinstance(val, dict))) all_from_module(prototype_module).items() if isinstance(val, dict)))
#overload module's protparents with specifically given protparents # overload module's protparents with specifically given protparents
protparents.update(kwargs.get("prototype_parents", {})) protparents.update(kwargs.get("prototype_parents", {}))
for key, prototype in protparents.items(): for key, prototype in protparents.items():
_validate_prototype(key, prototype, protparents, []) _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, # 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) # call that to get the value (don't catch errors)
create_kwargs = {} 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 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) create_kwargs["db_location"] = locval() if callable(locval) else _handle_dbref(locval)
homval = prot.pop("home", settings.DEFAULT_HOME) homval = prot.pop("home", settings.DEFAULT_HOME)
@ -244,7 +246,7 @@ def spawn(*prototypes, **kwargs):
lockval = prot.pop("locks", "") lockval = prot.pop("locks", "")
lock_string = lockval() if callable(lockval) else lockval lock_string = lockval() if callable(lockval) else lockval
aliasval = prot.pop("aliases", "") aliasval = prot.pop("aliases", "")
alias_string = aliasval() if callable(aliasval) else aliasval alias_string = aliasval() if callable(aliasval) else aliasval
tagval = prot.pop("tags", "") tagval = prot.pop("tags", "")
tags = tagval() if callable(tagval) else tagval tags = tagval() if callable(tagval) else tagval
exval = prot.pop("exec", "") exval = prot.pop("exec", "")
@ -252,16 +254,16 @@ def spawn(*prototypes, **kwargs):
# extract ndb assignments # extract ndb assignments
nattributes = dict((key.split("_", 1)[1], value() if callable(value) else value) 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 # the rest are attributes
attributes = dict((key, value() if callable(value) else value) attributes = dict((key, value() if callable(value) else value)
for key, value in prot.items() for key, value in prot.items()
if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_"))) if not (key in _CREATE_OBJECT_KWARGS or key.startswith("ndb_")))
# pack for call into _batch_create_object # pack for call into _batch_create_object
objsparams.append( (create_kwargs, permission_string, lock_string, objsparams.append((create_kwargs, permission_string, lock_string,
alias_string, nattributes, attributes, tags, execs) ) alias_string, nattributes, attributes, tags, execs))
return _batch_create_object(*objsparams) return _batch_create_object(*objsparams)
@ -271,33 +273,35 @@ if __name__ == "__main__":
protparents = { protparents = {
"NOBODY": {}, "NOBODY": {},
#"INFINITE" : { # "INFINITE" : {
# "prototype":"INFINITE" # "prototype":"INFINITE"
#}, # },
"GOBLIN" : { "GOBLIN": {
"key": "goblin grunt", "key": "goblin grunt",
"health": lambda: randint(20,30), "health": lambda: randint(20, 30),
"resists": ["cold", "poison"], "resists": ["cold", "poison"],
"attacks": ["fists"], "attacks": ["fists"],
"weaknesses": ["fire", "light"] "weaknesses": ["fire", "light"]
}, },
"GOBLIN_WIZARD" : { "GOBLIN_WIZARD": {
"prototype": "GOBLIN", "prototype": "GOBLIN",
"key": "goblin wizard", "key": "goblin wizard",
"spells": ["fire ball", "lighting bolt"] "spells": ["fire ball", "lighting bolt"]
}, },
"GOBLIN_ARCHER" : { "GOBLIN_ARCHER": {
"prototype": "GOBLIN", "prototype": "GOBLIN",
"key": "goblin archer", "key": "goblin archer",
"attacks": ["short bow"] "attacks": ["short bow"]
}, },
"ARCHWIZARD" : { "ARCHWIZARD": {
"attacks": ["archwizard staff"], "attacks": ["archwizard staff"],
}, },
"GOBLIN_ARCHWIZARD" : { "GOBLIN_ARCHWIZARD": {
"key": "goblin archwizard", "key": "goblin archwizard",
"prototype" : ("GOBLIN_WIZARD", "ARCHWIZARD") "prototype": ("GOBLIN_WIZARD", "ARCHWIZARD")
} }
} }
# test # 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. Make sure the ANSIString is always constructed correctly.
""" """
clean = u'This isA{r testTest' clean = u'This isA|r testTest'
encoded = u'\x1b[1m\x1b[32mThis is\x1b[1m\x1b[31mA{r test\x1b[0mTest\x1b[0m' 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') 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, char_table = [9, 10, 11, 12, 13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 37, 38, 39, 40]
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]
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.checker(target, encoded, clean)
self.table_check(target, char_table, code_table) self.table_check(target, char_table, code_table)
self.checker(ANSIString(target), encoded, clean) self.checker(ANSIString(target), encoded, clean)
@ -54,7 +52,7 @@ class ANSIStringTestCase(TestCase):
Verifies that slicing an ANSIString results in expected color code Verifies that slicing an ANSIString results in expected color code
distribution. distribution.
""" """
target = ANSIString(r'{gTest{rTest{n') target = ANSIString(r'|gTest|rTest|n')
result = target[:3] result = target[:3]
self.checker(result, u'\x1b[1m\x1b[32mTes', u'Tes') self.checker(result, u'\x1b[1m\x1b[32mTes', u'Tes')
result = target[:4] result = target[:4]
@ -80,7 +78,7 @@ class ANSIStringTestCase(TestCase):
Verifies that re.split and .split behave similarly and that color Verifies that re.split and .split behave similarly and that color
codes end up where they should. 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 ') 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', second = (u'\x1b[1m\x1b[32m\x1b[0m split string\x1b[1m\x1b[32m',
u' split string') u' split string')
@ -96,9 +94,9 @@ class ANSIStringTestCase(TestCase):
Verify that joining a set of ANSIStrings works. Verify that joining a set of ANSIStrings works.
""" """
# This isn't the desired behavior, but the expected one. Python # 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. # 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. # Force the generator to be evaluated.
result = "".join(l) result = "".join(l)
self.assertEqual(unicode(result), u'TestTestTest') self.assertEqual(unicode(result), u'TestTestTest')
@ -112,14 +110,14 @@ class ANSIStringTestCase(TestCase):
Make sure that length reporting on ANSIStrings does not include Make sure that length reporting on ANSIStrings does not include
ANSI codes. ANSI codes.
""" """
self.assertEqual(len(ANSIString('{gTest{n')), 4) self.assertEqual(len(ANSIString('|gTest|n')), 4)
def test_capitalize(self): def test_capitalize(self):
""" """
Make sure that capitalization works. This is the simplest of the Make sure that capitalization works. This is the simplest of the
_transform functions. _transform functions.
""" """
target = ANSIString('{gtest{n') target = ANSIString('|gtest|n')
result = u'\x1b[1m\x1b[32mTest\x1b[0m' result = u'\x1b[1m\x1b[32mTest\x1b[0m'
self.checker(target.capitalize(), result, u'Test') 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. Make sure MXP tags are not treated like ANSI codes, but normal text.
""" """
mxp1 = "{lclook{ltat{le" mxp1 = "|lclook|ltat|le"
mxp2 = "Start to {lclook here{ltclick somewhere here{le first" mxp2 = "Start to |lclook here|ltclick somewhere here|le first"
self.assertEqual(15, len(ANSIString(mxp1))) self.assertEqual(15, len(ANSIString(mxp1)))
self.assertEqual(53, len(ANSIString(mxp2))) self.assertEqual(53, len(ANSIString(mxp2)))
# These would indicate an issue with the tables. # These would indicate an issue with the tables.
@ -139,17 +137,15 @@ class ANSIStringTestCase(TestCase):
def test_add(self): def test_add(self):
""" """
Verify concatination works correctly. Verify concatenation works correctly.
""" """
a = ANSIString("{gTest") a = ANSIString("|gTest")
b = ANSIString("{cString{n") b = ANSIString("|cString|n")
c = a + b c = a + b
result = u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m' result = u'\x1b[1m\x1b[32mTest\x1b[1m\x1b[36mString\x1b[0m'
self.checker(c, result, u'TestString') self.checker(c, result, u'TestString')
char_table = [9, 10, 11, 12, 22, 23, 24, 25, 26, 27] char_table = [9, 10, 11, 12, 22, 23, 24, 25, 26, 27]
code_table = [ code_table = [0, 1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 21, 28, 29, 30, 31]
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) self.table_check(c, char_table, code_table)
def test_strip(self): def test_strip(self):
@ -166,7 +162,7 @@ class ANSIStringTestCase(TestCase):
class TestIsIter(TestCase): class TestIsIter(TestCase):
def test_is_iter(self): 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")) 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"' [1,2,3] -> '"1", "2" and "3"'
""" """
def test_list_to_string(self): 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=""))
self.assertEqual('"1", "2", "3"', utils.list_to_string([1,2,3], endsep="", addquote=True)) 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]))
self.assertEqual('"1", "2" and "3"', utils.list_to_string([1,2,3], endsep="and", addquote=True)) self.assertEqual('"1", "2" and "3"', utils.list_to_string([1, 2, 3], endsep="and", addquote=True))
class TestMLen(TestCase): class TestMLen(TestCase):
@ -231,10 +227,10 @@ class TestMLen(TestCase):
self.assertEqual(utils.m_len('|lclook|ltat|le'), 2) self.assertEqual(utils.m_len('|lclook|ltat|le'), 2)
def test_mxp_ansi_string(self): 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): 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) self.assertEqual(utils.m_len(ANSIString('|gHello|n')), 5)
def test_list(self): def test_list(self):
@ -246,6 +242,7 @@ class TestMLen(TestCase):
from .text2html import TextToHTMLparser from .text2html import TextToHTMLparser
class TestTextToHTMLparser(TestCase): class TestTextToHTMLparser(TestCase):
def setUp(self): def setUp(self):
self.parser = TextToHTMLparser() self.parser = TextToHTMLparser()
@ -255,71 +252,78 @@ class TestTextToHTMLparser(TestCase):
def test_url_scheme_ftp(self): def test_url_scheme_ftp(self):
self.assertEqual(self.parser.convert_urls('ftp.example.com'), 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): def test_url_scheme_www(self):
self.assertEqual(self.parser.convert_urls('www.example.com'), 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): def test_url_scheme_ftpproto(self):
self.assertEqual(self.parser.convert_urls('ftp://ftp.example.com'), 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): def test_url_scheme_http(self):
self.assertEqual(self.parser.convert_urls('http://example.com'), 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): def test_url_scheme_https(self):
self.assertEqual(self.parser.convert_urls('https://example.com'), 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): def test_url_chars_slash(self):
self.assertEqual(self.parser.convert_urls('www.example.com/homedir'), 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): def test_url_chars_colon(self):
self.assertEqual(self.parser.convert_urls('https://example.com:8000/login/'), 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): def test_url_chars_querystring(self):
self.assertEqual(self.parser.convert_urls('https://example.com/submitform?field1=val1+val3&field2=val2'), 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): def test_url_chars_anchor(self):
self.assertEqual(self.parser.convert_urls('http://www.example.com/menu#section_1'), 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): def test_url_chars_exclam(self):
self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve'), self.assertEqual(self.parser.convert_urls('https://groups.google.com/forum/'
'<a href="https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve" target="_blank">https://groups.google.com/forum/?fromgroups#!categories/evennia/ainneve</a>') '?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): def test_url_edge_leadingw(self):
self.assertEqual(self.parser.convert_urls('wwww.example.com'), 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): def test_url_edge_following_period_eol(self):
self.assertEqual(self.parser.convert_urls('www.example.com.'), 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): def test_url_edge_following_period(self):
self.assertEqual(self.parser.convert_urls('see www.example.com. '), 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): def test_url_edge_brackets(self):
self.assertEqual(self.parser.convert_urls('[http://example.com/]'), 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): def test_url_edge_multiline(self):
self.assertEqual(self.parser.convert_urls(' * http://example.com/info\n * bullet'), 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): def test_url_edge_following_htmlentity(self):
self.assertEqual(self.parser.convert_urls('http://example.com/info&lt;span&gt;'), 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): def test_url_edge_surrounded_spans(self):
self.assertEqual(self.parser.convert_urls('</span>http://example.com/<span class="red">'), 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 evennia.utils import evmenu
from mock import Mock from mock import Mock
@ -338,8 +342,9 @@ class TestEvMenu(TestCase):
from evennia.utils import inlinefuncs from evennia.utils import inlinefuncs
class TestInlineFuncs(TestCase): class TestInlineFuncs(TestCase):
"Test the nested inlinefunc module" """Test the nested inlinefunc module"""
def test_nofunc(self): def test_nofunc(self):
self.assertEqual(inlinefuncs.parse_inlinefunc( self.assertEqual(inlinefuncs.parse_inlinefunc(
"as$382ewrw w we w werw,|44943}"), "as$382ewrw w we w werw,|44943}"),
@ -372,12 +377,146 @@ class TestInlineFuncs(TestCase):
from evennia.utils import evform from evennia.utils import evform
class TestEvForm(TestCase): class TestEvForm(TestCase):
def test_form(self): def test_form(self):
self.maxDiff = None self.maxDiff = None
self.assertEqual(evform._test(), 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): def test_ansi_escape(self):
# note that in a msg() call, the result would be the correct |-----, # note that in a msg() call, the result would be the correct |-----,
# in a print, ansi only gets called once, so ||----- is the result # in a print, ansi only gets called once, so ||----- is the result
self.assertEqual(unicode(evform.EvForm(form={"FORM":"\n||-----"})), "||-----") 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 _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
def is_iter(iterable): def is_iter(iterable):
""" """
Checks if an object behaves iterably. Checks if an object behaves iterably.
@ -62,6 +63,7 @@ def is_iter(iterable):
""" """
return hasattr(iterable, '__iter__') return hasattr(iterable, '__iter__')
def make_iter(obj): def make_iter(obj):
""" """
Makes sure that the object is always iterable. Makes sure that the object is always iterable.
@ -201,7 +203,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
distribute odd spaces randomly to one of the gaps. distribute odd spaces randomly to one of the gaps.
""" """
line_rest = width - (wlen + ngaps) line_rest = width - (wlen + ngaps)
gap = " " # minimum gap between words gap = " " # minimum gap between words
if line_rest > 0: if line_rest > 0:
if align == 'l': if align == 'l':
line[-1] += " " * line_rest line[-1] += " " * line_rest
@ -211,7 +213,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
pad = " " * (line_rest // 2) pad = " " * (line_rest // 2)
line[0] = pad + line[0] line[0] = pad + line[0]
line[-1] = line[-1] + pad + " " * (line_rest % 2) line[-1] = line[-1] + pad + " " * (line_rest % 2)
else: # align 'f' else: # align 'f'
gap += " " * (line_rest // max(1, ngaps)) gap += " " * (line_rest // max(1, ngaps))
rest_gap = line_rest % max(1, ngaps) rest_gap = line_rest % max(1, ngaps)
for i in range(rest_gap): for i in range(rest_gap):
@ -250,8 +252,7 @@ def justify(text, width=_DEFAULT_WIDTH, align="f", indent=0):
wlen += word[1] wlen += word[1]
ngaps += 1 ngaps += 1
if line: # catch any line left behind
if line: # catch any line left behind
lines.append(_process_line(line)) lines.append(_process_line(line))
indentstring = " " * indent indentstring = " " * indent
return "\n".join([indentstring + line for line in lines]) return "\n".join([indentstring + line for line in lines])
@ -344,6 +345,7 @@ def time_format(seconds, style=0):
1. "1d" 1. "1d"
2. "1 day, 8 hours, 30 minutes" 2. "1 day, 8 hours, 30 minutes"
3. "1 day, 8 hours, 30 minutes, 10 seconds" 3. "1 day, 8 hours, 30 minutes, 10 seconds"
4. highest unit (like "3 years" or "8 months" or "1 second")
Returns: Returns:
timeformatted (str): A pretty time string. timeformatted (str): A pretty time string.
""" """
@ -360,7 +362,7 @@ def time_format(seconds, style=0):
minutes = seconds // 60 minutes = seconds // 60
seconds -= minutes * 60 seconds -= minutes * 60
retval = "" retval = ""
if style == 0: if style == 0:
""" """
Standard colon-style output. Standard colon-style output.
@ -432,6 +434,38 @@ def time_format(seconds, style=0):
else: else:
seconds_str = '%i seconds ' % seconds seconds_str = '%i seconds ' % seconds
retval = '%s%s%s%s' % (days_str, hours_str, minutes_str, seconds_str) 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() return retval.strip()
@ -522,13 +556,13 @@ def pypath_to_realpath(python_path, file_ending='.py', pypath_prefixes=None):
""" """
path = python_path.strip().split('.') path = python_path.strip().split('.')
plong = osjoin(*path) + file_ending 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('.')) prefixlong = [osjoin(*ppath.strip().split('.'))
for ppath in make_iter(pypath_prefixes)] \ for ppath in make_iter(pypath_prefixes)] \
if pypath_prefixes else [] if pypath_prefixes else []
prefixshort = [osjoin(*ppath.strip().split('.')[1:]) prefixshort = [osjoin(*ppath.strip().split('.')[1:])
for ppath in make_iter(pypath_prefixes) if len(ppath.strip().split('.')) > 1] \ for ppath in make_iter(pypath_prefixes) if len(ppath.strip().split('.')) > 1]\
if pypath_prefixes else [] if pypath_prefixes else []
paths = [plong] + \ paths = [plong] + \
[osjoin(_EVENNIA_DIR, prefix, plong) for prefix in prefixlong] + \ [osjoin(_EVENNIA_DIR, prefix, plong) for prefix in prefixlong] + \
[osjoin(_GAME_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": "-", _UNICODE_MAP = {"EM DASH": "-", "FIGURE DASH": "-", "EN DASH": "-", "HORIZONTAL BAR": "-",
"HORIZONTAL ELLIPSIS": "...", "RIGHT SINGLE QUOTATION MARK": "'"} "HORIZONTAL ELLIPSIS": "...", "RIGHT SINGLE QUOTATION MARK": "'"}
def latinify(unicode_string, default='?', pure_ascii=False): def latinify(unicode_string, default='?', pure_ascii=False):
""" """
Convert a unicode string to "safe" ascii/latin-1 characters. 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 # point name; e.g., since `name(u'á') == 'LATIN SMALL
# LETTER A WITH ACUTE'` translate `á` to `a`. However, in # LETTER A WITH ACUTE'` translate `á` to `a`. However, in
# some cases the unicode name is still "LATIN LETTER" # 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 # exists (e.g., Þ, "LATIN CAPITAL LETTER THORN") -- we can
# avoid these cases by checking that the letter name is # avoid these cases by checking that the letter name is
# composed of one letter only. # composed of one letter only.
@ -910,6 +945,8 @@ def delay(timedelay, callback, *args, **kwargs):
_TYPECLASSMODELS = None _TYPECLASSMODELS = None
_OBJECTMODELS = None _OBJECTMODELS = None
def clean_object_caches(obj): def clean_object_caches(obj):
""" """
Clean all object caches on the given object. Clean all object caches on the given object.
@ -1146,8 +1183,6 @@ def all_from_module(module):
# module if available (try to avoid not imports) # module if available (try to avoid not imports)
members = getmembers(mod, predicate=lambda obj: getmodule(obj) in (mod, None)) 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 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): def callables_from_module(module):
@ -1209,7 +1244,7 @@ def variable_from_module(module, variable=None, default=None):
else: else:
# get all # get all
result = [val for key, val in mod.__dict__.items() 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: if len(result) == 1:
return result[0] return result[0]
@ -1348,6 +1383,7 @@ def class_from_module(path, defaultpaths=None):
# alias # alias
object_from_module = class_from_module object_from_module = class_from_module
def init_new_player(player): def init_new_player(player):
""" """
Deprecated. Deprecated.
@ -1442,7 +1478,7 @@ def string_partial_matching(alternatives, inp, ret_index=True):
# (this will invalidate input in the wrong word order) # (this will invalidate input in the wrong word order)
submatch = [last_index + alt_num for alt_num, alt_word submatch = [last_index + alt_num for alt_num, alt_word
in enumerate(alt_words[last_index:]) in enumerate(alt_words[last_index:])
if alt_word.startswith(inp_word)] if alt_word.startswith(inp_word)]
if submatch: if submatch:
last_index = min(submatch) + 1 last_index = min(submatch) + 1
score += 1 score += 1
@ -1488,7 +1524,7 @@ def format_table(table, extra_space=1):
for ir, row in enumarate(ftable): for ir, row in enumarate(ftable):
if ir == 0: if ir == 0:
# make first row white # make first row white
string += "\n{w" + ""join(row) + "{n" string += "\n|w" + ""join(row) + "|n"
else: else:
string += "\n" + "".join(row) string += "\n" + "".join(row)
print string print string
@ -1539,6 +1575,8 @@ def get_evennia_pids():
from gc import get_referents from gc import get_referents
from sys import getsizeof from sys import getsizeof
def deepsize(obj, max_depth=4): def deepsize(obj, max_depth=4):
""" """
Get not only size of the given object, but also the size of 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): def _recurse(o, dct, depth):
if max_depth >= 0 and depth > max_depth: if 0 <= max_depth < depth:
return return
for ref in get_referents(o): for ref in get_referents(o):
idr = id(ref) idr = id(ref)
if not idr in dct: if idr not in dct:
dct[idr] = (ref, getsizeof(ref, default=0)) dct[idr] = (ref, getsizeof(ref, default=0))
_recurse(ref, dct, depth+1) _recurse(ref, dct, depth+1)
sizedict = {} sizedict = {}
_recurse(obj, sizedict, 0) _recurse(obj, sizedict, 0)
#count = len(sizedict) + 1
size = getsizeof(obj) + sum([p[1] for p in sizedict.values()]) size = getsizeof(obj) + sum([p[1] for p in sizedict.values()])
return size return size
# lazy load handler # lazy load handler
_missing = object() _missing = object()
class lazy_property(object): class lazy_property(object):
""" """
Delays loading of property until first access. Credit goes to the 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): 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.__name__ = name or func.__name__
self.__module__ = func.__module__ self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__ self.__doc__ = doc or func.__doc__
self.func = func self.func = func
def __get__(self, obj, type=None): def __get__(self, obj, type=None):
"Triggers initialization" """Triggers initialization"""
if obj is None: if obj is None:
return self return self
value = obj.__dict__.get(self.__name__, _missing) value = obj.__dict__.get(self.__name__, _missing)
@ -1614,7 +1653,9 @@ class lazy_property(object):
return value return value
_STRIP_ANSI = None _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): def strip_control_sequences(string):
""" """
Remove non-print text sequences. Remove non-print text sequences.
@ -1675,12 +1716,12 @@ def m_len(target):
return len(ANSI_PARSER.strip_mxp(target)) return len(ANSI_PARSER.strip_mxp(target))
return len(target) return len(target)
#------------------------------------------------------------------ # -------------------------------------------------------------------
# Search handler function # Search handler function
#------------------------------------------------------------------ # -------------------------------------------------------------------
# #
# Replace this hook function by changing settings.SEARCH_AT_RESULT. # Replace this hook function by changing settings.SEARCH_AT_RESULT.
#
def at_search_result(matches, caller, query="", quiet=False, **kwargs): 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. should the result pass through.
caller (Object): The object performing the search and/or which should caller (Object): The object performing the search and/or which should
receive error messages. 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 quiet (bool, optional): If `True`, no messages will be echoed to caller
on errors. on errors.
@ -1774,6 +1815,7 @@ class LimitedSizeOrderedDict(OrderedDict):
super(LimitedSizeOrderedDict, self).update(*args, **kwargs) super(LimitedSizeOrderedDict, self).update(*args, **kwargs)
self._check_size() self._check_size()
def get_game_dir_path(): def get_game_dir_path():
""" """
This is called by settings_default in order to determine the 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. # current working directory, assumed to be somewhere inside gamedir.
for i in range(10): for _ in range(10):
gpath = os.getcwd() gpath = os.getcwd()
if "server" in os.listdir(gpath): if "server" in os.listdir(gpath):
if os.path.isfile(os.path.join("server", "conf", "settings.py")): if os.path.isfile(os.path.join("server", "conf", "settings.py")):