Fixed conflicts against master.
This commit is contained in:
commit
b952a290b5
18 changed files with 307 additions and 223 deletions
|
|
@ -95,7 +95,7 @@ _ERROR_NOCMDSETS = "No command sets found! This is a sign of a critical bug." \
|
||||||
"\nsome other means for assistance."
|
"\nsome other means for assistance."
|
||||||
|
|
||||||
_ERROR_CMDHANDLER = "{traceback}\n"\
|
_ERROR_CMDHANDLER = "{traceback}\n"\
|
||||||
"Above traceback is from a Command handler bug." \
|
"Above traceback is from a Command handler bug. " \
|
||||||
"Please file a bug report with the Evennia project."
|
"Please file a bug report with the Evennia project."
|
||||||
|
|
||||||
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
|
_ERROR_RECURSION_LIMIT = "Command recursion limit ({recursion_limit}) " \
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#
|
#
|
||||||
# This is Evennia's default connection screen. It is imported
|
# This is Evennia's default connection screen. It is imported
|
||||||
# and run from world/connection_screens.py.
|
# and run from server/conf/connection_screens.py.
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
|
||||||
|
|
@ -1093,7 +1093,8 @@ class CmdName(ObjManipCommand):
|
||||||
Usage:
|
Usage:
|
||||||
@name obj = name;alias1;alias2
|
@name obj = name;alias1;alias2
|
||||||
|
|
||||||
Rename an object to something new.
|
Rename an object to something new. Use *obj to
|
||||||
|
rename a player.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -1107,12 +1108,30 @@ class CmdName(ObjManipCommand):
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
if not self.args:
|
if not self.args:
|
||||||
string = "Usage: @name <obj> = <newname>[;alias;alias;...]"
|
caller.msg("Usage: @name <obj> = <newname>[;alias;alias;...]")
|
||||||
caller.msg(string)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.lhs_objs:
|
if self.lhs_objs:
|
||||||
objname = self.lhs_objs[0]['name']
|
objname = self.lhs_objs[0]['name']
|
||||||
|
if objname.startswith("*"):
|
||||||
|
# player mode
|
||||||
|
obj = caller.player.search(objname.lstrip("*"))
|
||||||
|
if obj:
|
||||||
|
if self.rhs_objs[0]['aliases']:
|
||||||
|
caller.msg("Players can't have aliases.")
|
||||||
|
return
|
||||||
|
newname = self.rhs
|
||||||
|
if not newname:
|
||||||
|
caller.msg("No name defined!")
|
||||||
|
return
|
||||||
|
if not obj.access(caller, "edit"):
|
||||||
|
caller.mgs("You don't have right to edit this player %s." % obj)
|
||||||
|
return
|
||||||
|
obj.username = newname
|
||||||
|
obj.save()
|
||||||
|
caller.msg("Player's name changed to '%s'." % newname)
|
||||||
|
return
|
||||||
|
# object search, also with *
|
||||||
obj = caller.search(objname)
|
obj = caller.search(objname)
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
|
|
@ -1125,6 +1144,9 @@ class CmdName(ObjManipCommand):
|
||||||
if not newname and not aliases:
|
if not newname and not aliases:
|
||||||
caller.msg("No names or aliases defined!")
|
caller.msg("No names or aliases defined!")
|
||||||
return
|
return
|
||||||
|
if not obj.access(caller, "edit"):
|
||||||
|
caller.msg("You don't have the right to edit %s." % obj)
|
||||||
|
return
|
||||||
# change the name and set aliases:
|
# change the name and set aliases:
|
||||||
if newname:
|
if newname:
|
||||||
obj.name = newname
|
obj.name = newname
|
||||||
|
|
|
||||||
|
|
@ -519,17 +519,15 @@ class CmdChannelCreate(MuxPlayerCommand):
|
||||||
channame = lhs
|
channame = lhs
|
||||||
aliases = None
|
aliases = None
|
||||||
if ';' in lhs:
|
if ';' in lhs:
|
||||||
channame, aliases = [part.strip().lower()
|
channame, aliases = lhs.split(';', 1)
|
||||||
for part in lhs.split(';', 1) if part.strip()]
|
aliases = [alias.strip().lower() for alias in aliases.split(';')]
|
||||||
aliases = [alias.strip().lower()
|
|
||||||
for alias in aliases.split(';') if alias.strip()]
|
|
||||||
channel = ChannelDB.objects.channel_search(channame)
|
channel = ChannelDB.objects.channel_search(channame)
|
||||||
if channel:
|
if channel:
|
||||||
self.msg("A channel with that name already exists.")
|
self.msg("A channel with that name already exists.")
|
||||||
return
|
return
|
||||||
# Create and set the channel up
|
# Create and set the channel up
|
||||||
lockstring = "send:all();listen:all();control:id(%s)" % caller.id
|
lockstring = "send:all();listen:all();control:id(%s)" % caller.id
|
||||||
new_chan = create.create_channel(channame,
|
new_chan = create.create_channel(channame.strip(),
|
||||||
aliases,
|
aliases,
|
||||||
description,
|
description,
|
||||||
locks=lockstring)
|
locks=lockstring)
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ class CmdHelp(Command):
|
||||||
"""
|
"""
|
||||||
key = "help"
|
key = "help"
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
|
arg_regex = r"\s|$"
|
||||||
|
|
||||||
# this is a special cmdhandler flag that makes the cmdhandler also pack
|
# this is a special cmdhandler flag that makes the cmdhandler also pack
|
||||||
# the current cmdset with the call to self.func().
|
# the current cmdset with the call to self.func().
|
||||||
|
|
|
||||||
|
|
@ -194,23 +194,23 @@ class CmdPy(MuxCommand):
|
||||||
t0 = timemeasure()
|
t0 = timemeasure()
|
||||||
ret = eval(pycode_compiled, {}, available_vars)
|
ret = eval(pycode_compiled, {}, available_vars)
|
||||||
t1 = timemeasure()
|
t1 = timemeasure()
|
||||||
duration = " (%.4f ms)" % ((t1 - t0) * 1000)
|
duration = " (runtime ~ %.4f ms)" % ((t1 - t0) * 1000)
|
||||||
else:
|
else:
|
||||||
ret = eval(pycode_compiled, {}, available_vars)
|
ret = eval(pycode_compiled, {}, available_vars)
|
||||||
if mode == "eval":
|
if mode == "eval":
|
||||||
ret = "{n<<< %s%s" % (str(ret), duration)
|
ret = "<<< %s%s" % (str(ret), duration)
|
||||||
else:
|
else:
|
||||||
ret = "{n<<< Done.%s" % duration
|
ret = "<<< Done (use self.msg() if you want to catch output)%s" % duration
|
||||||
except Exception:
|
except Exception:
|
||||||
errlist = traceback.format_exc().split('\n')
|
errlist = traceback.format_exc().split('\n')
|
||||||
if len(errlist) > 4:
|
if len(errlist) > 4:
|
||||||
errlist = errlist[4:]
|
errlist = errlist[4:]
|
||||||
ret = "\n".join("{n<<< %s" % line for line in errlist if line)
|
ret = "\n".join("<<< %s" % line for line in errlist if line)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.msg(ret, sessid=self.sessid)
|
self.msg(ret, sessid=self.sessid, raw=True)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
self.msg(ret)
|
self.msg(ret, raw=True)
|
||||||
|
|
||||||
|
|
||||||
# helper function. Kept outside so it can be imported and run
|
# helper function. Kept outside so it can be imported and run
|
||||||
|
|
|
||||||
|
|
@ -15,14 +15,14 @@ Install is simple:
|
||||||
|
|
||||||
To your settings file, add/edit the line:
|
To your settings file, add/edit the line:
|
||||||
|
|
||||||
CMDSET_UNLOGGEDIN = "contrib.menu_login.UnloggedInCmdSet"
|
CMDSET_UNLOGGEDIN = "contrib.menu_login.UnloggedinCmdSet"
|
||||||
|
|
||||||
That's it. Reload the server and try to log in to see it.
|
That's it. Reload the server and try to log in to see it.
|
||||||
|
|
||||||
You will want to change the login "graphic", which defaults to give
|
You will want to change the login "graphic", which defaults to give
|
||||||
information about commands which are not used in this version of the
|
information about commands which are not used in this version of the
|
||||||
login. You can change the screen used by editing
|
login. You can change the screen used by editing
|
||||||
`mygame/server/conf/connection_screens.py`.
|
`$GAME_DIR/server/conf/connection_screens.py`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -323,13 +323,13 @@ node3 = MenuNode("node3", text=LOGIN_SCREEN_HELP,
|
||||||
|
|
||||||
# access commands
|
# access commands
|
||||||
|
|
||||||
class UnloggedInCmdSet(CmdSet):
|
class UnloggedinCmdSet(CmdSet):
|
||||||
"Cmdset for the unloggedin state"
|
"Cmdset for the unloggedin state"
|
||||||
key = "DefaultUnloggedin"
|
key = "DefaultUnloggedin"
|
||||||
priority = 0
|
priority = 0
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
"Called when cmdset is first created"
|
"Called when cmdset is first created."
|
||||||
self.add(CmdUnloggedinLook())
|
self.add(CmdUnloggedinLook())
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -337,7 +337,7 @@ class CmdUnloggedinLook(Command):
|
||||||
"""
|
"""
|
||||||
An unloggedin version of the look command. This is called by the server
|
An unloggedin version of the look command. This is called by the server
|
||||||
when the player first connects. It sets up the menu before handing off
|
when the player first connects. It sets up the menu before handing off
|
||||||
to the menu's own look command..
|
to the menu's own look command.
|
||||||
"""
|
"""
|
||||||
key = CMD_LOGINSTART
|
key = CMD_LOGINSTART
|
||||||
# obs, this should NOT have aliases for look or l, this will clash with the menu version!
|
# obs, this should NOT have aliases for look or l, this will clash with the menu version!
|
||||||
|
|
|
||||||
|
|
@ -281,7 +281,7 @@ class MenuNode(object):
|
||||||
text (str, optional): The text that will be displayed at
|
text (str, optional): The text that will be displayed at
|
||||||
top when viewing this node.
|
top when viewing this node.
|
||||||
Kwargs:
|
Kwargs:
|
||||||
links (list): A liist of keys for unique menunodes this is connected to.
|
links (list): A list of keys for unique menunodes this is connected to.
|
||||||
The actual keys will not printed - keywords will be
|
The actual keys will not printed - keywords will be
|
||||||
used (or a number)
|
used (or a number)
|
||||||
linktexts (list)- A list of texts to describe the links. Must
|
linktexts (list)- A list of texts to describe the links. Must
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,8 @@ DATABASES = {{
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Django web features
|
# Django web features
|
||||||
|
# (don't remove these entries, they are needed to override the default
|
||||||
|
# locations with your actual GAME_DIR locations at run-time)
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
# Absolute path to the directory that holds file uploads from web apps.
|
# Absolute path to the directory that holds file uploads from web apps.
|
||||||
|
|
|
||||||
|
|
@ -370,7 +370,7 @@ class DefaultObject(ObjectDB):
|
||||||
|
|
||||||
if quiet:
|
if quiet:
|
||||||
return results
|
return results
|
||||||
return _AT_SEARCH_RESULT(self, searchdata, results, global_search=True)
|
return _AT_SEARCH_RESULT(results, self, query=searchdata)
|
||||||
|
|
||||||
def execute_cmd(self, raw_string, sessid=None, **kwargs):
|
def execute_cmd(self, raw_string, sessid=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -250,7 +250,7 @@ class MsgPortal2Server(amp.Command):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "MsgPortal2Server"
|
key = "MsgPortal2Server"
|
||||||
arguments = [('data', Compressed())]
|
arguments = [('packed_data', Compressed())]
|
||||||
errors = [(Exception, 'EXCEPTION')]
|
errors = [(Exception, 'EXCEPTION')]
|
||||||
response = []
|
response = []
|
||||||
|
|
||||||
|
|
@ -261,7 +261,7 @@ class MsgServer2Portal(amp.Command):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "MsgServer2Portal"
|
key = "MsgServer2Portal"
|
||||||
arguments = [('data', Compressed())]
|
arguments = [('packed_data', Compressed())]
|
||||||
errors = [(Exception, 'EXCEPTION')]
|
errors = [(Exception, 'EXCEPTION')]
|
||||||
response = []
|
response = []
|
||||||
|
|
||||||
|
|
@ -275,7 +275,7 @@ class AdminPortal2Server(amp.Command):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "AdminPortal2Server"
|
key = "AdminPortal2Server"
|
||||||
arguments = [('data', Compressed())]
|
arguments = [('packed_data', Compressed())]
|
||||||
errors = [(Exception, 'EXCEPTION')]
|
errors = [(Exception, 'EXCEPTION')]
|
||||||
response = []
|
response = []
|
||||||
|
|
||||||
|
|
@ -289,7 +289,7 @@ class AdminServer2Portal(amp.Command):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "AdminServer2Portal"
|
key = "AdminServer2Portal"
|
||||||
arguments = [('data', Compressed())]
|
arguments = [('packed_data', Compressed())]
|
||||||
errors = [(Exception, 'EXCEPTION')]
|
errors = [(Exception, 'EXCEPTION')]
|
||||||
response = []
|
response = []
|
||||||
|
|
||||||
|
|
@ -362,7 +362,7 @@ class AMPProtocol(amp.AMP):
|
||||||
sessdata = self.factory.portal.sessions.get_all_sync_data()
|
sessdata = self.factory.portal.sessions.get_all_sync_data()
|
||||||
self.send_AdminPortal2Server(0,
|
self.send_AdminPortal2Server(0,
|
||||||
PSYNC,
|
PSYNC,
|
||||||
data=sessdata)
|
sessiondata=sessdata)
|
||||||
self.factory.portal.sessions.at_server_connection()
|
self.factory.portal.sessions.at_server_connection()
|
||||||
if hasattr(self.factory, "server_restart_mode"):
|
if hasattr(self.factory, "server_restart_mode"):
|
||||||
del self.factory.server_restart_mode
|
del self.factory.server_restart_mode
|
||||||
|
|
@ -399,53 +399,49 @@ class AMPProtocol(amp.AMP):
|
||||||
(sessid, kwargs).
|
(sessid, kwargs).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
batch = dumps((sessid, kwargs))
|
|
||||||
return self.callRemote(command,
|
return self.callRemote(command,
|
||||||
data=batch).addErrback(self.errback, command.key)
|
packed_data=dumps((sessid, kwargs))
|
||||||
|
).addErrback(self.errback, command.key)
|
||||||
|
|
||||||
# Message definition + helper methods to call/create each message type
|
# Message definition + helper methods to call/create each message type
|
||||||
|
|
||||||
# Portal -> Server Msg
|
# Portal -> Server Msg
|
||||||
|
|
||||||
@MsgPortal2Server.responder
|
@MsgPortal2Server.responder
|
||||||
def server_receive_msgportal2server(self, data):
|
def server_receive_msgportal2server(self, packed_data):
|
||||||
"""
|
"""
|
||||||
Receives message arriving to server. This method is executed
|
Receives message arriving to server. This method is executed
|
||||||
on the Server.
|
on the Server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (str): Data to receive (a pickled tuple (sessid,kwargs))
|
packed_data (str): Data to receive (a pickled tuple (sessid,kwargs))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
sessid, kwargs = loads(data)
|
sessid, kwargs = loads(packed_data)
|
||||||
#print "msg portal -> server (server side):", sessid, msg, loads(ret["data"])
|
#print "msg portal -> server (server side):", sessid, msg, loads(ret["data"])
|
||||||
self.factory.server.sessions.data_in(sessid,
|
self.factory.server.sessions.data_in(sessid, **kwargs)
|
||||||
text=kwargs["msg"],
|
|
||||||
data=kwargs["data"])
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def send_MsgPortal2Server(self, sessid, msg="", data=""):
|
def send_MsgPortal2Server(self, sessid, text="", **kwargs):
|
||||||
"""
|
"""
|
||||||
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.
|
sessid (int): Unique Session id.
|
||||||
msg (str): Message to send over the wire.
|
msg (str): Message to send over the wire.
|
||||||
data (str, optional): Optional data.
|
kwargs (any, optional): Optional data.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
deferred (Deferred): Asynchronous return.
|
deferred (Deferred): Asynchronous return.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#print "msg portal->server (portal side):", sessid, msg, data
|
#print "msg portal->server (portal side):", sessid, msg, data
|
||||||
return self.send_data(MsgPortal2Server, sessid,
|
return self.send_data(MsgPortal2Server, sessid, text=text, **kwargs)
|
||||||
msg=msg,
|
|
||||||
data=data)
|
|
||||||
|
|
||||||
# Server -> Portal message
|
# Server -> Portal message
|
||||||
|
|
||||||
@MsgServer2Portal.responder
|
@MsgServer2Portal.responder
|
||||||
def portal_receive_server2portal(self, data):
|
def portal_receive_server2portal(self, packed_data):
|
||||||
"""
|
"""
|
||||||
Receives message arriving to Portal from Server.
|
Receives message arriving to Portal from Server.
|
||||||
This method is executed on the Portal.
|
This method is executed on the Portal.
|
||||||
|
|
@ -456,17 +452,15 @@ class AMPProtocol(amp.AMP):
|
||||||
before continuing.
|
before continuing.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (str): Pickled data (sessid, kwargs) coming over the wire.
|
packed_data (str): Pickled data (sessid, kwargs) coming over the wire.
|
||||||
"""
|
"""
|
||||||
sessid, kwargs = loads(data)
|
sessid, kwargs = loads(packed_data)
|
||||||
#print "msg server->portal (portal side):", sessid, ret["text"], loads(ret["data"])
|
#print "msg server->portal (portal side):", sessid, ret["text"], loads(ret["data"])
|
||||||
self.factory.portal.sessions.data_out(sessid,
|
self.factory.portal.sessions.data_out(sessid, **kwargs)
|
||||||
text=kwargs["msg"],
|
|
||||||
data=kwargs["data"])
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def send_MsgServer2Portal(self, sessid, msg="", data=""):
|
def send_MsgServer2Portal(self, sessid, text="", **kwargs):
|
||||||
"""
|
"""
|
||||||
Access method - executed on the Server for sending data
|
Access method - executed on the Server for sending data
|
||||||
to Portal.
|
to Portal.
|
||||||
|
|
@ -474,39 +468,37 @@ class AMPProtocol(amp.AMP):
|
||||||
Args:
|
Args:
|
||||||
sessid (int): Unique Session id.
|
sessid (int): Unique Session id.
|
||||||
msg (str, optional): Message to send over the wire.
|
msg (str, optional): Message to send over the wire.
|
||||||
data (str, optional): Extra data.
|
kwargs (any, optiona): Extra data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#print "msg server->portal (server side):", sessid, msg, data
|
#print "msg server->portal (server side):", sessid, msg, data
|
||||||
return self.send_data(MsgServer2Portal, sessid, msg=msg, data=data)
|
return self.send_data(MsgServer2Portal, sessid, text=text, **kwargs)
|
||||||
|
|
||||||
# Server administration from the Portal side
|
# Server administration from the Portal side
|
||||||
@AdminPortal2Server.responder
|
@AdminPortal2Server.responder
|
||||||
def server_receive_adminportal2server(self, data):
|
def server_receive_adminportal2server(self, packed_data):
|
||||||
"""
|
"""
|
||||||
Receives admin data from the Portal (allows the portal to
|
Receives admin data from the Portal (allows the portal to
|
||||||
perform admin operations on the server). This is executed on
|
perform admin operations on the server). This is executed on
|
||||||
the Server.
|
the Server.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (str): Data to send (often a part of a batch)
|
packed_data (str): Incoming, pickled data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#print "serveradmin (server side):", hashid, ipart, nparts
|
#print "serveradmin (server side):", hashid, ipart, nparts
|
||||||
sessid, kwargs = loads(data)
|
sessid, kwargs = loads(packed_data)
|
||||||
|
operation = kwargs.pop("operation", "")
|
||||||
operation = kwargs["operation"]
|
|
||||||
data = kwargs["data"]
|
|
||||||
server_sessionhandler = self.factory.server.sessions
|
server_sessionhandler = self.factory.server.sessions
|
||||||
|
|
||||||
#print "serveradmin (server side):", sessid, ord(operation), data
|
#print "serveradmin (server side):", sessid, ord(operation), data
|
||||||
|
|
||||||
if operation == PCONN: # portal_session_connect
|
if operation == PCONN: # portal_session_connect
|
||||||
# create a new session and sync it
|
# create a new session and sync it
|
||||||
server_sessionhandler.portal_connect(data)
|
server_sessionhandler.portal_connect(kwargs.get("sessiondata"))
|
||||||
|
|
||||||
elif operation == PCONNSYNC: #portal_session_sync
|
elif operation == PCONNSYNC: #portal_session_sync
|
||||||
server_sessionhandler.portal_session_sync(data)
|
server_sessionhandler.portal_session_sync(kwargs.get("sessiondata"))
|
||||||
|
|
||||||
elif operation == PDISCONN: # portal_session_disconnect
|
elif operation == PDISCONN: # portal_session_disconnect
|
||||||
# session closed from portal side
|
# session closed from portal side
|
||||||
|
|
@ -518,12 +510,12 @@ class AMPProtocol(amp.AMP):
|
||||||
# contains a dict {sessid: {arg1:val1,...}}
|
# contains a dict {sessid: {arg1:val1,...}}
|
||||||
# representing the attributes to sync for each
|
# representing the attributes to sync for each
|
||||||
# session.
|
# session.
|
||||||
server_sessionhandler.portal_sessions_sync(data)
|
server_sessionhandler.portal_sessions_sync(kwargs.get("sessiondata"))
|
||||||
else:
|
else:
|
||||||
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def send_AdminPortal2Server(self, sessid, operation="", data=""):
|
def send_AdminPortal2Server(self, sessid, operation="", **kwargs):
|
||||||
"""
|
"""
|
||||||
Send Admin instructions from the Portal to the Server.
|
Send Admin instructions from the Portal to the Server.
|
||||||
Executed
|
Executed
|
||||||
|
|
@ -533,42 +525,41 @@ class AMPProtocol(amp.AMP):
|
||||||
sessid (int): Session id.
|
sessid (int): Session id.
|
||||||
operation (char, optional): Identifier for the server operation, as defined by the
|
operation (char, optional): Identifier for the server operation, as defined by the
|
||||||
global variables in `evennia/server/amp.py`.
|
global variables in `evennia/server/amp.py`.
|
||||||
data (str, optional): Data going into the adminstrative operation.
|
data (str or dict, optional): Data used in the administrative operation.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#print "serveradmin (portal side):", sessid, ord(operation), data
|
#print "serveradmin (portal side):", sessid, ord(operation), data
|
||||||
return self.send_data(AdminPortal2Server, sessid, operation=operation, data=data)
|
return self.send_data(AdminPortal2Server, sessid, operation=operation, **kwargs)
|
||||||
|
|
||||||
# Portal administraton from the Server side
|
# Portal administraton from the Server side
|
||||||
|
|
||||||
@AdminServer2Portal.responder
|
@AdminServer2Portal.responder
|
||||||
def portal_receive_adminserver2portal(self, data):
|
def portal_receive_adminserver2portal(self, packed_data):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Receives and handles admin operations sent to the Portal
|
Receives and handles admin operations sent to the Portal
|
||||||
This is executed on the Portal.
|
This is executed on the Portal.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (str): Data received, a pickled tuple (sessid, kwargs).
|
packed_data (str): Data received, a pickled tuple (sessid, kwargs).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
#print "portaladmin (portal side):", sessid, ord(operation), data
|
#print "portaladmin (portal side):", sessid, ord(operation), data
|
||||||
sessid, kwargs = loads(data)
|
sessid, kwargs = loads(packed_data)
|
||||||
operation = kwargs["operation"]
|
operation = kwargs.pop("operation")
|
||||||
data = kwargs["data"]
|
|
||||||
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.
|
||||||
portal_sessionhandler.server_logged_in(sessid, data)
|
portal_sessionhandler.server_logged_in(sessid, kwargs.get("sessiondata"))
|
||||||
|
|
||||||
elif operation == SDISCONN: # server_session_disconnect
|
elif operation == SDISCONN: # server_session_disconnect
|
||||||
# the server is ordering to disconnect the session
|
# the server is ordering to disconnect the session
|
||||||
portal_sessionhandler.server_disconnect(sessid, reason=data)
|
portal_sessionhandler.server_disconnect(sessid, reason=kwargs.get("reason"))
|
||||||
|
|
||||||
elif operation == SDISCONNALL: # server_session_disconnect_all
|
elif operation == SDISCONNALL: # server_session_disconnect_all
|
||||||
# server orders all sessions to disconnect
|
# server orders all sessions to disconnect
|
||||||
portal_sessionhandler.server_disconnect_all(reason=data)
|
portal_sessionhandler.server_disconnect_all(reason=kwargs.get("reason"))
|
||||||
|
|
||||||
elif operation == SSHUTD: # server_shutdown
|
elif operation == SSHUTD: # server_shutdown
|
||||||
# the server orders the portal to shut down
|
# the server orders the portal to shut down
|
||||||
|
|
@ -577,18 +568,18 @@ class AMPProtocol(amp.AMP):
|
||||||
elif operation == SSYNC: # server_session_sync
|
elif operation == SSYNC: # server_session_sync
|
||||||
# server wants to save session data to the portal,
|
# server wants to save session data to the portal,
|
||||||
# maybe because it's about to shut down.
|
# maybe because it's about to shut down.
|
||||||
portal_sessionhandler.server_session_sync(data)
|
portal_sessionhandler.server_session_sync(kwargs.get("sessiondata"))
|
||||||
# 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/imc2 etc)
|
elif operation == SCONN: # server_force_connection (for irc/imc2 etc)
|
||||||
portal_sessionhandler.server_connect(**data)
|
portal_sessionhandler.server_connect(**kwargs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
raise Exception("operation %(op)s not recognized." % {'op': operation})
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def send_AdminServer2Portal(self, sessid, operation="", data=""):
|
def send_AdminServer2Portal(self, sessid, operation="", **kwargs):
|
||||||
"""
|
"""
|
||||||
Administrative access method called by the Server to send an
|
Administrative access method called by the Server to send an
|
||||||
instruction to the Portal.
|
instruction to the Portal.
|
||||||
|
|
@ -598,16 +589,15 @@ class AMPProtocol(amp.AMP):
|
||||||
operation (char, optional): Identifier for the server
|
operation (char, optional): Identifier for the server
|
||||||
operation, as defined by the global variables in
|
operation, as defined by the global variables in
|
||||||
`evennia/server/amp.py`.
|
`evennia/server/amp.py`.
|
||||||
data (str, optional): Data going into the adminstrative
|
data (str or dict, optional): Data going into the adminstrative.
|
||||||
operation.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self.send_data(AdminServer2Portal, sessid, operation=operation, data=data)
|
return self.send_data(AdminServer2Portal, sessid, operation=operation, **kwargs)
|
||||||
|
|
||||||
# Extra functions
|
# Extra functions
|
||||||
|
|
||||||
@FunctionCall.responder
|
@FunctionCall.responder
|
||||||
def receive_functioncall(self, module, function, args, **kwargs):
|
def receive_functioncall(self, module, function, func_args, func_kwargs):
|
||||||
"""
|
"""
|
||||||
This allows Portal- and Server-process to call an arbitrary
|
This allows Portal- and Server-process to call an arbitrary
|
||||||
function in the other process. It is intended for use by
|
function in the other process. It is intended for use by
|
||||||
|
|
@ -618,12 +608,12 @@ class AMPProtocol(amp.AMP):
|
||||||
`function` to call.
|
`function` to call.
|
||||||
function (str): The name of the function to call in
|
function (str): The name of the function to call in
|
||||||
`module`.
|
`module`.
|
||||||
args, kwargs (any): These will be used as args/kwargs to
|
func_args (str): Pickled args tuple for use in `function` call.
|
||||||
`function`.
|
func_kwargs (str): Pickled kwargs dict for use in `function` call.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
args = loads(args)
|
args = loads(func_args)
|
||||||
kwargs = loads(kwargs)
|
kwargs = loads(func_kwargs)
|
||||||
|
|
||||||
# call the function (don't catch tracebacks here)
|
# call the function (don't catch tracebacks here)
|
||||||
result = variable_from_module(module, function)(*args, **kwargs)
|
result = variable_from_module(module, function)(*args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,7 @@ class PortalSessionHandler(SessionHandler):
|
||||||
#print "connecting", session.sessid, " number:", len(self.sessions)
|
#print "connecting", session.sessid, " number:", len(self.sessions)
|
||||||
self.portal.amp_protocol.send_AdminPortal2Server(session.sessid,
|
self.portal.amp_protocol.send_AdminPortal2Server(session.sessid,
|
||||||
operation=PCONN,
|
operation=PCONN,
|
||||||
data=sessdata)
|
sessiondata=sessdata)
|
||||||
|
|
||||||
def sync(self, session):
|
def sync(self, session):
|
||||||
"""
|
"""
|
||||||
|
|
@ -143,7 +143,7 @@ class PortalSessionHandler(SessionHandler):
|
||||||
"server_data",))
|
"server_data",))
|
||||||
self.portal.amp_protocol.send_AdminPortal2Server(session.sessid,
|
self.portal.amp_protocol.send_AdminPortal2Server(session.sessid,
|
||||||
operation=PCONNSYNC,
|
operation=PCONNSYNC,
|
||||||
data=sessdata)
|
sessiondata=sessdata)
|
||||||
|
|
||||||
def disconnect(self, session):
|
def disconnect(self, session):
|
||||||
"""
|
"""
|
||||||
|
|
@ -390,7 +390,6 @@ class PortalSessionHandler(SessionHandler):
|
||||||
# data throttle (anti DoS measure)
|
# data throttle (anti DoS measure)
|
||||||
now = time()
|
now = time()
|
||||||
dT = now - self.command_counter_reset
|
dT = now - self.command_counter_reset
|
||||||
print(" command rate:", _MAX_COMMAND_RATE / dT, dT, self.command_counter)
|
|
||||||
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 = dT < 1.0
|
||||||
|
|
@ -402,8 +401,8 @@ class PortalSessionHandler(SessionHandler):
|
||||||
# relay data to Server
|
# relay data to Server
|
||||||
self.command_counter += 1
|
self.command_counter += 1
|
||||||
self.portal.amp_protocol.send_MsgPortal2Server(session.sessid,
|
self.portal.amp_protocol.send_MsgPortal2Server(session.sessid,
|
||||||
msg=text,
|
text=text,
|
||||||
data=kwargs)
|
**kwargs)
|
||||||
else:
|
else:
|
||||||
# called by the callLater callback
|
# called by the callLater callback
|
||||||
if self.command_overflow:
|
if self.command_overflow:
|
||||||
|
|
|
||||||
|
|
@ -208,12 +208,15 @@ class ServerSession(Session):
|
||||||
idle timers and command counters.
|
idle timers and command counters.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# Idle time used for timeout calcs.
|
||||||
|
self.cmd_last = time()
|
||||||
|
|
||||||
# Store the timestamp of the user's last command.
|
# Store the timestamp of the user's last command.
|
||||||
if not idle:
|
if not idle:
|
||||||
# Increment the user's command counter.
|
# Increment the user's command counter.
|
||||||
self.cmd_total += 1
|
self.cmd_total += 1
|
||||||
# Player-visible idle time, not used in idle timeout calcs.
|
# Player-visible idle time, not used in idle timeout calcs.
|
||||||
self.cmd_last_visible = time()
|
self.cmd_last_visible = self.cmd_last
|
||||||
|
|
||||||
def data_in(self, text=None, **kwargs):
|
def data_in(self, text=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -282,11 +282,8 @@ class ServerSessionHandler(SessionHandler):
|
||||||
the Server.
|
the Server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
data = {"protocol_path":protocol_path,
|
self.server.amp_protocol.send_AdminServer2Portal(0, operation=SCONN,
|
||||||
"config":configdict}
|
protocol_path=protocol_path, config=configdict)
|
||||||
self.server.amp_protocol.send_AdminServer2Portal(0,
|
|
||||||
operation=SCONN,
|
|
||||||
data=data)
|
|
||||||
|
|
||||||
def portal_shutdown(self):
|
def portal_shutdown(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -294,8 +291,7 @@ class ServerSessionHandler(SessionHandler):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.server.amp_protocol.send_AdminServer2Portal(0,
|
self.server.amp_protocol.send_AdminServer2Portal(0,
|
||||||
operation=SSHUTD,
|
operation=SSHUTD)
|
||||||
data="")
|
|
||||||
|
|
||||||
def login(self, session, player, testmode=False):
|
def login(self, session, player, testmode=False):
|
||||||
"""
|
"""
|
||||||
|
|
@ -338,14 +334,12 @@ class ServerSessionHandler(SessionHandler):
|
||||||
string = "Logged in: {player} {address} ({nsessions} session(s) total)"
|
string = "Logged in: {player} {address} ({nsessions} session(s) total)"
|
||||||
string = string.format(player=player,address=session.address, nsessions=nsess)
|
string = string.format(player=player,address=session.address, nsessions=nsess)
|
||||||
session.log(string)
|
session.log(string)
|
||||||
|
|
||||||
session.logged_in = True
|
session.logged_in = True
|
||||||
# sync the portal to the session
|
# sync the portal to the session
|
||||||
sessdata = {"logged_in": True}
|
|
||||||
if not testmode:
|
if not testmode:
|
||||||
self.server.amp_protocol.send_AdminServer2Portal(session.sessid,
|
self.server.amp_protocol.send_AdminServer2Portal(session.sessid,
|
||||||
operation=SLOGIN,
|
operation=SLOGIN,
|
||||||
data=sessdata)
|
sessiondata={"logged_in": True})
|
||||||
player.at_post_login(sessid=session.sessid)
|
player.at_post_login(sessid=session.sessid)
|
||||||
|
|
||||||
def disconnect(self, session, reason=""):
|
def disconnect(self, session, reason=""):
|
||||||
|
|
@ -375,7 +369,7 @@ class ServerSessionHandler(SessionHandler):
|
||||||
# inform portal that session should be closed.
|
# inform portal that session should be closed.
|
||||||
self.server.amp_protocol.send_AdminServer2Portal(sessid,
|
self.server.amp_protocol.send_AdminServer2Portal(sessid,
|
||||||
operation=SDISCONN,
|
operation=SDISCONN,
|
||||||
data=reason)
|
reason=reason)
|
||||||
|
|
||||||
def all_sessions_portal_sync(self):
|
def all_sessions_portal_sync(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -386,7 +380,7 @@ class ServerSessionHandler(SessionHandler):
|
||||||
sessdata = self.get_all_sync_data()
|
sessdata = self.get_all_sync_data()
|
||||||
return self.server.amp_protocol.send_AdminServer2Portal(0,
|
return self.server.amp_protocol.send_AdminServer2Portal(0,
|
||||||
operation=SSYNC,
|
operation=SSYNC,
|
||||||
data=sessdata)
|
sessiondata=sessdata)
|
||||||
|
|
||||||
def disconnect_all_sessions(self, reason="You have been disconnected."):
|
def disconnect_all_sessions(self, reason="You have been disconnected."):
|
||||||
"""
|
"""
|
||||||
|
|
@ -402,7 +396,7 @@ class ServerSessionHandler(SessionHandler):
|
||||||
# tell portal to disconnect all sessions
|
# tell portal to disconnect all sessions
|
||||||
self.server.amp_protocol.send_AdminServer2Portal(0,
|
self.server.amp_protocol.send_AdminServer2Portal(0,
|
||||||
operation=SDISCONNALL,
|
operation=SDISCONNALL,
|
||||||
data=reason)
|
reason=reason)
|
||||||
|
|
||||||
def disconnect_duplicate_sessions(self, curr_session,
|
def disconnect_duplicate_sessions(self, curr_session,
|
||||||
reason=_("Logged in from elsewhere. Disconnecting.")):
|
reason=_("Logged in from elsewhere. Disconnecting.")):
|
||||||
|
|
@ -586,8 +580,8 @@ class ServerSessionHandler(SessionHandler):
|
||||||
# send to all found sessions
|
# send to all found sessions
|
||||||
for session in sessions:
|
for session in sessions:
|
||||||
self.server.amp_protocol.send_MsgServer2Portal(sessid=session.sessid,
|
self.server.amp_protocol.send_MsgServer2Portal(sessid=session.sessid,
|
||||||
msg=text,
|
text=text,
|
||||||
data=kwargs)
|
**kwargs)
|
||||||
|
|
||||||
def data_in(self, sessid, text="", **kwargs):
|
def data_in(self, sessid, text="", **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -45,22 +45,28 @@ entered to get to this node). The node function code will only be
|
||||||
executed once per node-visit and the system will accept nodes with
|
executed once per node-visit and the system will accept nodes with
|
||||||
both one or two arguments interchangeably.
|
both one or two arguments interchangeably.
|
||||||
|
|
||||||
|
The menu tree itself is available on the caller as
|
||||||
|
`caller.ndb._menutree`. This makes it a convenient place to store
|
||||||
|
temporary state variables between nodes, since this NAttribute is
|
||||||
|
deleted when the menu is exited.
|
||||||
|
|
||||||
The return values must be given in the above order, but each can be
|
The return values must be given in the above order, but each can be
|
||||||
returned as None as well. If the options are returned as None, the
|
returned as None as well. If the options are returned as None, the
|
||||||
menu is immediately exited and the default "look" command is called.
|
menu is immediately exited and the default "look" command is called.
|
||||||
|
|
||||||
text (str, tuple or None): Text shown at this node. If a tuple, the second
|
text (str, tuple or None): Text shown at this node. If a tuple, the
|
||||||
element in the tuple is a help text to display at this node when
|
second element in the tuple is a help text to display at this
|
||||||
the user enters the menu help command there.
|
node when the user enters the menu help command there.
|
||||||
options (tuple, dict or None): ( {'key': name, # can also be a list of aliases. A special key is "_default", which
|
options (tuple, dict or None): (
|
||||||
# marks this option as the default fallback when no other
|
{'key': name, # can also be a list of aliases. A special key is
|
||||||
# option matches the user input.
|
# "_default", which marks this option as the default
|
||||||
'desc': description, # option description
|
# fallback when no other option matches the user input.
|
||||||
|
'desc': description, # optional description
|
||||||
'goto': nodekey, # node to go to when chosen
|
'goto': nodekey, # node to go to when chosen
|
||||||
'exec': nodekey, # node or callback to trigger as callback when chosen. If a node
|
'exec': nodekey}, # node or callback to trigger as callback when chosen.
|
||||||
# key is given the node will be executed once but its return u
|
# If a node key is given, the node will be executed once
|
||||||
# values are ignored. If a callable is given, it must accept
|
# but its return values are ignored. If a callable is
|
||||||
# one or two args, like any node.
|
# given, it must accept one or two args, like any node.
|
||||||
{...}, ...)
|
{...}, ...)
|
||||||
|
|
||||||
If key is not given, the option will automatically be identified by
|
If key is not given, the option will automatically be identified by
|
||||||
|
|
@ -122,9 +128,9 @@ The menu tree is exited either by using the in-menu quit command or by
|
||||||
reaching a node without any options.
|
reaching a node without any options.
|
||||||
|
|
||||||
|
|
||||||
For a menu demo, import CmdTestDemo from this module and add it to
|
For a menu demo, import CmdTestMenu from this module and add it to
|
||||||
your default cmdset. Run it with this module, like `testdemo
|
your default cmdset. Run it with this module, like `testmenu
|
||||||
evennia.utils.evdemo`.
|
evennia.utils.evmenu`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
@ -176,65 +182,25 @@ class EvMenuError(RuntimeError):
|
||||||
class CmdEvMenuNode(Command):
|
class CmdEvMenuNode(Command):
|
||||||
"""
|
"""
|
||||||
Menu options.
|
Menu options.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
key = "look"
|
key = _CMD_NOINPUT
|
||||||
aliases = ["l", _CMD_NOMATCH, _CMD_NOINPUT]
|
aliases = [_CMD_NOMATCH]
|
||||||
locks = "cmd:all()"
|
locks = "cmd:all()"
|
||||||
help_category = "Menu"
|
help_category = "Menu"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""
|
"""
|
||||||
Implement all menu commands.
|
Implement all menu commands.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
menu = caller.ndb._menutree
|
menu = caller.ndb._menutree
|
||||||
|
|
||||||
if not menu:
|
if not menu:
|
||||||
err = "Menu object not found as %s.ndb._menutree!" % (caller)
|
err = "Menu object not found as %s.ndb._menutree!" % (caller)
|
||||||
self.caller.msg(err)
|
caller.msg(err)
|
||||||
raise EvMenuError(err)
|
raise EvMenuError(err)
|
||||||
|
|
||||||
# flags and data
|
menu.parse_input(self.raw_string)
|
||||||
raw_string = self.raw_string
|
|
||||||
cmd = raw_string.strip().lower()
|
|
||||||
options = menu.options
|
|
||||||
allow_quit = menu.allow_quit
|
|
||||||
cmd_on_quit = menu.cmd_on_quit
|
|
||||||
default = menu.default
|
|
||||||
|
|
||||||
print("cmd, options:", cmd, options)
|
|
||||||
if cmd in options:
|
|
||||||
# this will overload the other commands
|
|
||||||
# if it has the same name!
|
|
||||||
goto, callback = options[cmd]
|
|
||||||
if callback:
|
|
||||||
menu.callback(callback, raw_string)
|
|
||||||
if goto:
|
|
||||||
menu.goto(goto, raw_string)
|
|
||||||
elif cmd in ("look", "l"):
|
|
||||||
caller.msg(menu.nodetext)
|
|
||||||
elif cmd in ("help", "h"):
|
|
||||||
caller.msg(menu.helptext)
|
|
||||||
elif allow_quit and cmd in ("quit", "q", "exit"):
|
|
||||||
menu.close_menu()
|
|
||||||
if cmd_on_quit is not None:
|
|
||||||
caller.execute_cmd(cmd_on_quit)
|
|
||||||
elif default:
|
|
||||||
goto, callback = default
|
|
||||||
if callback:
|
|
||||||
menu.callback(callback, raw_string)
|
|
||||||
if goto:
|
|
||||||
menu.goto(goto, raw_string)
|
|
||||||
else:
|
|
||||||
caller.msg(_HELP_NO_OPTION_MATCH)
|
|
||||||
|
|
||||||
if not (options or default):
|
|
||||||
# no options - we are at the end of the menu.
|
|
||||||
menu.close_menu()
|
|
||||||
if cmd_on_quit is not None:
|
|
||||||
caller.execute_cmd(cmd_on_quit)
|
|
||||||
|
|
||||||
|
|
||||||
class EvMenuCmdSet(CmdSet):
|
class EvMenuCmdSet(CmdSet):
|
||||||
|
|
@ -269,7 +235,9 @@ class EvMenu(object):
|
||||||
"""
|
"""
|
||||||
def __init__(self, caller, menudata, startnode="start",
|
def __init__(self, caller, menudata, startnode="start",
|
||||||
cmdset_mergetype="Replace", cmdset_priority=1,
|
cmdset_mergetype="Replace", cmdset_priority=1,
|
||||||
allow_quit=True, cmd_on_quit="look"):
|
allow_quit=True, cmd_on_quit="look",
|
||||||
|
nodetext_formatter=None, options_formatter=None,
|
||||||
|
node_formatter=None):
|
||||||
"""
|
"""
|
||||||
Initialize the menu tree and start the caller onto the first node.
|
Initialize the menu tree and start the caller onto the first node.
|
||||||
|
|
||||||
|
|
@ -299,11 +267,35 @@ class EvMenu(object):
|
||||||
allow_quit (bool, optional): Allow user to use quit or
|
allow_quit (bool, optional): Allow user to use quit or
|
||||||
exit to leave the menu at any point. Recommended during
|
exit to leave the menu at any point. Recommended during
|
||||||
development!
|
development!
|
||||||
cmd_on_quit (str or None, optional): When exiting the menu
|
cmd_on_quit (callable, str or None, optional): When exiting the menu
|
||||||
(either by reaching a node with no options or by using the
|
(either by reaching a node with no options or by using the
|
||||||
in-built quit command (activated with `allow_quit`), this
|
in-built quit command (activated with `allow_quit`), this
|
||||||
command string will be executed. Set to None to not call
|
callback function or command string will be executed.
|
||||||
any command.
|
The callback function takes two parameters, the caller then the
|
||||||
|
EvMenu object. This is called after cleanup is complete.
|
||||||
|
Set to None to not call any command.
|
||||||
|
nodetext_formatter (callable, optional): This callable should be on
|
||||||
|
the form `function(nodetext, has_options)`, where `nodetext` is the
|
||||||
|
node text string and `has_options` a boolean specifying if there
|
||||||
|
are options associated with this node. It must return a formatted
|
||||||
|
string.
|
||||||
|
options_formatter (callable, optional): This callable should be on
|
||||||
|
the form `function(optionlist)`, where ` optionlist is a list
|
||||||
|
of option dictionaries, like
|
||||||
|
[{"key":..., "desc",..., "goto": ..., "exec",...}, ...]
|
||||||
|
Each dictionary describes each possible option. Note that this
|
||||||
|
will also be called if there are no options, and so should be
|
||||||
|
able to handle an empty list. This should
|
||||||
|
be formatted into an options list and returned as a string,
|
||||||
|
including the required separator to use between the node text
|
||||||
|
and the options. If not given the default EvMenu style will be used.
|
||||||
|
node_formatter (callable, optional): This callable should be on the
|
||||||
|
form `func(nodetext, optionstext)` where the arguments are strings
|
||||||
|
representing the node text and options respectively (possibly prepared
|
||||||
|
by `nodetext_formatter`/`options_formatter` or by the default styles).
|
||||||
|
It should return a string representing the final look of the node. This
|
||||||
|
can e.g. be used to create line separators that take into account the
|
||||||
|
dynamic width of the parts.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
EvMenuError: If the start/end node is not found in menu tree.
|
EvMenuError: If the start/end node is not found in menu tree.
|
||||||
|
|
@ -313,17 +305,27 @@ class EvMenu(object):
|
||||||
self._startnode = startnode
|
self._startnode = startnode
|
||||||
self._menutree = self._parse_menudata(menudata)
|
self._menutree = self._parse_menudata(menudata)
|
||||||
|
|
||||||
|
self._nodetext_formatter = nodetext_formatter
|
||||||
|
self._options_formatter = nodetext_formatter
|
||||||
|
self._node_formatter = node_formatter
|
||||||
|
|
||||||
if startnode not in self._menutree:
|
if startnode not in self._menutree:
|
||||||
raise EvMenuError("Start node '%s' not in menu tree!" % startnode)
|
raise EvMenuError("Start node '%s' not in menu tree!" % startnode)
|
||||||
|
|
||||||
# variables made available to the command
|
# variables made available to the command
|
||||||
self.allow_quit = allow_quit
|
self.allow_quit = allow_quit
|
||||||
|
if isinstance(cmd_on_quit, str):
|
||||||
|
self.cmd_on_quit = lambda caller, menu: caller.execute_cmd(cmd_on_quit)
|
||||||
|
elif callable(cmd_on_quit):
|
||||||
self.cmd_on_quit = cmd_on_quit
|
self.cmd_on_quit = cmd_on_quit
|
||||||
|
else:
|
||||||
|
self.cmd_on_quit = None
|
||||||
self.default = None
|
self.default = None
|
||||||
self.nodetext = None
|
self.nodetext = None
|
||||||
self.helptext = None
|
self.helptext = None
|
||||||
self.options = None
|
self.options = None
|
||||||
|
|
||||||
|
|
||||||
# store ourself on the object
|
# store ourself on the object
|
||||||
self._caller.ndb._menutree = self
|
self._caller.ndb._menutree = self
|
||||||
|
|
||||||
|
|
@ -382,20 +384,22 @@ class EvMenu(object):
|
||||||
# handle the node text
|
# handle the node text
|
||||||
#
|
#
|
||||||
|
|
||||||
|
if self._nodetext_formatter:
|
||||||
|
# use custom formatter
|
||||||
|
nodetext = self._nodetext_formatter(nodetext, len(optionlist))
|
||||||
|
else:
|
||||||
nodetext = dedent(nodetext).strip()
|
nodetext = dedent(nodetext).strip()
|
||||||
|
|
||||||
nodetext_width_max = max(m_len(line) for line in nodetext.split("\n"))
|
nodetext_width_max = max(m_len(line) for line in nodetext.split("\n"))
|
||||||
|
|
||||||
if not optionlist:
|
|
||||||
# return the node text "naked".
|
|
||||||
separator1 = "_" * nodetext_width_max + "\n\n" if nodetext_width_max else ""
|
|
||||||
separator2 = "\n" if nodetext_width_max else "" + "_" * nodetext_width_max
|
|
||||||
return separator1 + nodetext + separator2
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# handle the options
|
# handle the options
|
||||||
#
|
#
|
||||||
|
|
||||||
|
if self._options_formatter:
|
||||||
|
# use custom formatter
|
||||||
|
optionstext = self._options_formatter(optionlist)
|
||||||
|
elif optionlist:
|
||||||
# column separation distance
|
# column separation distance
|
||||||
colsep = 4
|
colsep = 4
|
||||||
|
|
||||||
|
|
@ -436,20 +440,29 @@ class EvMenu(object):
|
||||||
table = [table[icol*nrows:(icol*nrows) + nrows] for icol in xrange(0, ncols)]
|
table = [table[icol*nrows:(icol*nrows) + nrows] for icol in xrange(0, ncols)]
|
||||||
|
|
||||||
# adjust the width of each column
|
# adjust the width of each column
|
||||||
total_width = 0
|
|
||||||
for icol in xrange(len(table)):
|
for icol in xrange(len(table)):
|
||||||
col_width = max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep
|
col_width = max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep
|
||||||
table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]]
|
table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]]
|
||||||
total_width += col_width
|
|
||||||
|
|
||||||
# format the table into columns
|
# format the table into columns
|
||||||
table = EvTable(table=table, border="none")
|
optionstext = unicode(EvTable(table=table, border="none"))
|
||||||
|
else:
|
||||||
|
optionstext = ""
|
||||||
|
|
||||||
|
options_width_max = max(m_len(line) for line in optionstext.split("\n"))
|
||||||
|
|
||||||
|
#
|
||||||
|
# format the entire node
|
||||||
|
#
|
||||||
|
if self._node_formatter:
|
||||||
|
# use custom formatter
|
||||||
|
return self._node_formatter(nodetext, optionstext)
|
||||||
|
else:
|
||||||
# build the page
|
# build the page
|
||||||
total_width = max(total_width, nodetext_width_max)
|
total_width = max(options_width_max, nodetext_width_max)
|
||||||
separator1 = "_" * total_width + "\n\n" if nodetext_width_max else ""
|
separator1 = "_" * total_width + "\n\n" if nodetext_width_max else ""
|
||||||
separator2 = "\n" + "_" * total_width + "\n\n" if total_width else ""
|
separator2 = "\n" + "_" * total_width + "\n\n" if total_width else ""
|
||||||
return separator1 + nodetext + separator2 + unicode(table)
|
return separator1 + nodetext + separator2 + optionstext
|
||||||
|
|
||||||
def _execute_node(self, nodename, raw_string):
|
def _execute_node(self, nodename, raw_string):
|
||||||
"""
|
"""
|
||||||
|
|
@ -488,6 +501,56 @@ class EvMenu(object):
|
||||||
return nodetext, options
|
return nodetext, options
|
||||||
|
|
||||||
|
|
||||||
|
def _display_nodetext(self):
|
||||||
|
self._caller.msg(self.nodetext)
|
||||||
|
|
||||||
|
|
||||||
|
def _display_helptext(self):
|
||||||
|
self._caller.msg(self.helptext)
|
||||||
|
|
||||||
|
|
||||||
|
def _callback_goto(self, callback, goto, raw_string):
|
||||||
|
if callback:
|
||||||
|
self.callback(callback, raw_string)
|
||||||
|
if goto:
|
||||||
|
self.goto(goto, raw_string)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_input(self, raw_string):
|
||||||
|
"""
|
||||||
|
Processes the user' node inputs.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
raw_string (str): The incoming raw_string from the menu
|
||||||
|
command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
caller = self._caller
|
||||||
|
cmd = raw_string.strip().lower()
|
||||||
|
allow_quit = self.allow_quit
|
||||||
|
|
||||||
|
if cmd in self.options:
|
||||||
|
# this will take precedence over the default commands
|
||||||
|
# below
|
||||||
|
goto, callback = self.options[cmd]
|
||||||
|
self._callback_goto(callback, goto, raw_string)
|
||||||
|
elif cmd in ("look", "l"):
|
||||||
|
self._display_nodetext()
|
||||||
|
elif cmd in ("help", "h"):
|
||||||
|
self._display_helptext()
|
||||||
|
elif allow_quit and cmd in ("quit", "q", "exit"):
|
||||||
|
self.close_menu()
|
||||||
|
elif self.default:
|
||||||
|
goto, callback = self.default
|
||||||
|
self._callback_goto(callback, goto, raw_string)
|
||||||
|
else:
|
||||||
|
caller.msg(_HELP_NO_OPTION_MATCH)
|
||||||
|
|
||||||
|
if not (self.options or self.default):
|
||||||
|
# no options - we are at the end of the menu.
|
||||||
|
self.close_menu()
|
||||||
|
|
||||||
|
|
||||||
def callback(self, nodename, raw_string):
|
def callback(self, nodename, raw_string):
|
||||||
"""
|
"""
|
||||||
Run a node as a callback. This makes no use of the return
|
Run a node as a callback. This makes no use of the return
|
||||||
|
|
@ -582,7 +645,7 @@ class EvMenu(object):
|
||||||
else:
|
else:
|
||||||
self.helptext = _HELP_NO_OPTIONS if self.allow_quit else _HELP_NO_OPTIONS_NO_QUIT
|
self.helptext = _HELP_NO_OPTIONS if self.allow_quit else _HELP_NO_OPTIONS_NO_QUIT
|
||||||
|
|
||||||
self._caller.execute_cmd("look")
|
self._display_nodetext()
|
||||||
|
|
||||||
def close_menu(self):
|
def close_menu(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -590,6 +653,8 @@ class EvMenu(object):
|
||||||
"""
|
"""
|
||||||
self._caller.cmdset.remove(EvMenuCmdSet)
|
self._caller.cmdset.remove(EvMenuCmdSet)
|
||||||
del self._caller.ndb._menutree
|
del self._caller.ndb._menutree
|
||||||
|
if self.cmd_on_quit is not None:
|
||||||
|
self.cmd_on_quit(self._caller, self)
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------------------------------
|
# -------------------------------------------------------------------------------------------------
|
||||||
|
|
@ -689,6 +754,9 @@ def test_start_node(caller):
|
||||||
"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"),
|
||||||
|
"desc": "Look and see a custom message.",
|
||||||
|
"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"},
|
||||||
|
|
@ -700,6 +768,13 @@ def test_start_node(caller):
|
||||||
return text, options
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
|
def test_look_node(caller):
|
||||||
|
text = "Looking again will take you back to the previous message."
|
||||||
|
options = {"key": ("{yL{nook", "l"),
|
||||||
|
"desc": "Go back to the previous menu.",
|
||||||
|
"goto": "test_start_node"}
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -1537,7 +1537,7 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
||||||
error = ""
|
error = ""
|
||||||
if not matches:
|
if not matches:
|
||||||
# no results.
|
# no results.
|
||||||
error = kwargs.get("nofound_string", _("Could not find '%s'." % query))
|
error = kwargs.get("nofound_string") or _("Could not find '%s'." % query)
|
||||||
matches = None
|
matches = None
|
||||||
elif len(matches) > 1:
|
elif len(matches) > 1:
|
||||||
error = kwargs.get("multimatch_string", None)
|
error = kwargs.get("multimatch_string", None)
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,5 @@
|
||||||
django >= 1.8, < 1.9
|
django >= 1.8, < 1.9
|
||||||
twisted >= 15.2.1
|
twisted >= 15.2.1
|
||||||
mock >= 1.0.1
|
mock >= 1.0.1
|
||||||
pillow
|
pillow == 2.9.0
|
||||||
pytz
|
pytz
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,5 @@ pypiwin32
|
||||||
django >= 1.8, < 1.9
|
django >= 1.8, < 1.9
|
||||||
twisted >= 15.2.1
|
twisted >= 15.2.1
|
||||||
mock >= 1.0.1
|
mock >= 1.0.1
|
||||||
pillow
|
pillow == 2.9.0
|
||||||
pytz
|
pytz
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue