Resolve merge conflicts with master.
This commit is contained in:
commit
7ff783fea1
21 changed files with 900 additions and 122 deletions
|
|
@ -82,6 +82,7 @@ EvEditor = None
|
||||||
|
|
||||||
# Handlers
|
# Handlers
|
||||||
SESSION_HANDLER = None
|
SESSION_HANDLER = None
|
||||||
|
TASK_HANDLER = None
|
||||||
TICKER_HANDLER = None
|
TICKER_HANDLER = None
|
||||||
MONITOR_HANDLER = None
|
MONITOR_HANDLER = None
|
||||||
CHANNEL_HANDLER = None
|
CHANNEL_HANDLER = None
|
||||||
|
|
@ -124,7 +125,7 @@ def _init():
|
||||||
global search_object, search_script, search_account, search_channel, search_help, search_tag
|
global search_object, search_script, search_account, search_channel, search_help, search_tag
|
||||||
global create_object, create_script, create_account, create_channel, create_message, create_help_entry
|
global create_object, create_script, create_account, create_channel, create_message, create_help_entry
|
||||||
global settings,lockfuncs, logger, utils, gametime, ansi, spawn, managers
|
global settings,lockfuncs, logger, utils, gametime, ansi, spawn, managers
|
||||||
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER
|
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER, TASK_HANDLER
|
||||||
|
|
||||||
from .accounts.accounts import DefaultAccount
|
from .accounts.accounts import DefaultAccount
|
||||||
from .accounts.accounts import DefaultGuest
|
from .accounts.accounts import DefaultGuest
|
||||||
|
|
@ -178,6 +179,7 @@ def _init():
|
||||||
|
|
||||||
# handlers
|
# handlers
|
||||||
from .scripts.tickerhandler import TICKER_HANDLER
|
from .scripts.tickerhandler import TICKER_HANDLER
|
||||||
|
from .scripts.taskhandler import TASK_HANDLER
|
||||||
from .server.sessionhandler import SESSION_HANDLER
|
from .server.sessionhandler import SESSION_HANDLER
|
||||||
from .comms.channelhandler import CHANNEL_HANDLER
|
from .comms.channelhandler import CHANNEL_HANDLER
|
||||||
from .scripts.monitorhandler import MONITOR_HANDLER
|
from .scripts.monitorhandler import MONITOR_HANDLER
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,8 @@ __all__ = ("import_cmdset", "CmdSetHandler")
|
||||||
_CACHED_CMDSETS = {}
|
_CACHED_CMDSETS = {}
|
||||||
_CMDSET_PATHS = utils.make_iter(settings.CMDSET_PATHS)
|
_CMDSET_PATHS = utils.make_iter(settings.CMDSET_PATHS)
|
||||||
_IN_GAME_ERRORS = settings.IN_GAME_ERRORS
|
_IN_GAME_ERRORS = settings.IN_GAME_ERRORS
|
||||||
|
_CMDSET_FALLBACKS = settings.CMDSET_FALLBACKS
|
||||||
|
|
||||||
|
|
||||||
# Output strings
|
# Output strings
|
||||||
|
|
||||||
|
|
@ -102,6 +104,16 @@ _ERROR_CMDSET_EXCEPTION = _(
|
||||||
Compile/Run error when loading cmdset '{path}'.",
|
Compile/Run error when loading cmdset '{path}'.",
|
||||||
(Traceback was logged {timestamp})""")
|
(Traceback was logged {timestamp})""")
|
||||||
|
|
||||||
|
_ERROR_CMDSET_FALLBACK = _(
|
||||||
|
"""
|
||||||
|
Error encountered for cmdset at path '{path}'.
|
||||||
|
Replacing with fallback '{fallback_path}'.
|
||||||
|
""")
|
||||||
|
|
||||||
|
_ERROR_CMDSET_NO_FALLBACK = _(
|
||||||
|
"""Fallback path '{fallback_path}' failed to generate a cmdset."""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class _ErrorCmdSet(CmdSet):
|
class _ErrorCmdSet(CmdSet):
|
||||||
"""
|
"""
|
||||||
|
|
@ -351,6 +363,22 @@ class CmdSetHandler(object):
|
||||||
elif path:
|
elif path:
|
||||||
cmdset = self._import_cmdset(path)
|
cmdset = self._import_cmdset(path)
|
||||||
if cmdset:
|
if cmdset:
|
||||||
|
if cmdset.key == '_CMDSET_ERROR':
|
||||||
|
# If a cmdset fails to load, check if we have a fallback path to use
|
||||||
|
fallback_path = _CMDSET_FALLBACKS.get(path, None)
|
||||||
|
if fallback_path:
|
||||||
|
err = _ERROR_CMDSET_FALLBACK.format(path=path, fallback_path=fallback_path)
|
||||||
|
logger.log_err(err)
|
||||||
|
if _IN_GAME_ERRORS:
|
||||||
|
self.obj.msg(err)
|
||||||
|
cmdset = self._import_cmdset(fallback_path)
|
||||||
|
# If no cmdset is returned from the fallback, we can't go further
|
||||||
|
if not cmdset:
|
||||||
|
err = _ERROR_CMDSET_NO_FALLBACK.format(fallback_path=fallback_path)
|
||||||
|
logger.log_err(err)
|
||||||
|
if _IN_GAME_ERRORS:
|
||||||
|
self.obj.msg(err)
|
||||||
|
continue
|
||||||
cmdset.permanent = cmdset.key != '_CMDSET_ERROR'
|
cmdset.permanent = cmdset.key != '_CMDSET_ERROR'
|
||||||
self.cmdset_stack.append(cmdset)
|
self.cmdset_stack.append(cmdset)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,8 @@ class CmdCopy(ObjManipCommand):
|
||||||
copy an object and its properties
|
copy an object and its properties
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@copy[/reset] <original obj> [= new_name][;alias;alias..][:new_location] [,new_name2 ...]
|
@copy[/reset] <original obj> [= <new_name>][;alias;alias..]
|
||||||
|
[:<new_location>] [,<new_name2> ...]
|
||||||
|
|
||||||
switch:
|
switch:
|
||||||
reset - make a 'clean' copy off the object, thus
|
reset - make a 'clean' copy off the object, thus
|
||||||
|
|
@ -205,7 +206,8 @@ class CmdCopy(ObjManipCommand):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
args = self.args
|
args = self.args
|
||||||
if not args:
|
if not args:
|
||||||
caller.msg("Usage: @copy <obj> [=new_name[;alias;alias..]][:new_location] [, new_name2...]")
|
caller.msg("Usage: @copy <obj> [=<new_name>[;alias;alias..]]"
|
||||||
|
"[:<new_location>] [, <new_name2>...]")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.rhs:
|
if not self.rhs:
|
||||||
|
|
@ -446,7 +448,7 @@ class CmdCreate(ObjManipCommand):
|
||||||
create new objects
|
create new objects
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@create[/drop] objname[;alias;alias...][:typeclass], objname...
|
@create[/drop] <objname>[;alias;alias...][:typeclass], <objname>...
|
||||||
|
|
||||||
switch:
|
switch:
|
||||||
drop - automatically drop the new object into your current
|
drop - automatically drop the new object into your current
|
||||||
|
|
@ -481,7 +483,7 @@ class CmdCreate(ObjManipCommand):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
if not self.args:
|
if not self.args:
|
||||||
string = "Usage: @create[/drop] <newname>[;alias;alias...] [:typeclass_path]"
|
string = "Usage: @create[/drop] <newname>[;alias;alias...] [:typeclass.path]"
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -684,9 +686,9 @@ class CmdDig(ObjManipCommand):
|
||||||
build new rooms and connect them to the current location
|
build new rooms and connect them to the current location
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@dig[/switches] roomname[;alias;alias...][:typeclass]
|
@dig[/switches] <roomname>[;alias;alias...][:typeclass]
|
||||||
[= exit_to_there[;alias][:typeclass]]
|
[= <exit_to_there>[;alias][:typeclass]]
|
||||||
[, exit_to_here[;alias][:typeclass]]
|
[, <exit_to_here>[;alias][:typeclass]]
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
tel or teleport - move yourself to the new room
|
tel or teleport - move yourself to the new room
|
||||||
|
|
@ -718,9 +720,10 @@ class CmdDig(ObjManipCommand):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
if not self.lhs:
|
if not self.lhs:
|
||||||
string = "Usage: @dig[/teleport] roomname[;alias;alias...][:parent] [= exit_there"
|
string = "Usage: @dig[/teleport] <roomname>[;alias;alias...]" \
|
||||||
|
"[:parent] [= <exit_there>"
|
||||||
string += "[;alias;alias..][:parent]] "
|
string += "[;alias;alias..][:parent]] "
|
||||||
string += "[, exit_back_here[;alias;alias..][:parent]]"
|
string += "[, <exit_back_here>[;alias;alias..][:parent]]"
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -823,7 +826,7 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
|
||||||
create new rooms in cardinal directions only
|
create new rooms in cardinal directions only
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@tunnel[/switch] <direction> [= roomname[;alias;alias;...][:typeclass]]
|
@tunnel[/switch] <direction> [= <roomname>[;alias;alias;...][:typeclass]]
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
oneway - do not create an exit back to the current location
|
oneway - do not create an exit back to the current location
|
||||||
|
|
@ -868,7 +871,8 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
|
||||||
"Implements the tunnel command"
|
"Implements the tunnel command"
|
||||||
|
|
||||||
if not self.args or not self.lhs:
|
if not self.args or not self.lhs:
|
||||||
string = "Usage: @tunnel[/switch] <direction> [= roomname[;alias;alias;...][:typeclass]]"
|
string = "Usage: @tunnel[/switch] <direction> [= <roomname>" \
|
||||||
|
"[;alias;alias;...][:typeclass]]"
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
return
|
return
|
||||||
if self.lhs not in self.directions:
|
if self.lhs not in self.directions:
|
||||||
|
|
@ -1025,7 +1029,7 @@ class CmdSetHome(CmdLink):
|
||||||
set an object's home location
|
set an object's home location
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@home <obj> [= home_location]
|
@home <obj> [= <home_location>]
|
||||||
|
|
||||||
The "home" location is a "safety" location for objects; they
|
The "home" location is a "safety" location for objects; they
|
||||||
will be moved there if their current location ceases to exist. All
|
will be moved there if their current location ceases to exist. All
|
||||||
|
|
@ -1042,7 +1046,7 @@ class CmdSetHome(CmdLink):
|
||||||
def func(self):
|
def func(self):
|
||||||
"implement the command"
|
"implement the command"
|
||||||
if not self.args:
|
if not self.args:
|
||||||
string = "Usage: @home <obj> [= home_location]"
|
string = "Usage: @home <obj> [= <home_location>]"
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -1076,7 +1080,7 @@ class CmdListCmdSets(COMMAND_DEFAULT_CLASS):
|
||||||
list command sets defined on an object
|
list command sets defined on an object
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@cmdsets [obj]
|
@cmdsets <obj>
|
||||||
|
|
||||||
This displays all cmdsets assigned
|
This displays all cmdsets assigned
|
||||||
to a user. Defaults to yourself.
|
to a user. Defaults to yourself.
|
||||||
|
|
@ -1105,7 +1109,7 @@ class CmdName(ObjManipCommand):
|
||||||
change the name and/or aliases of an object
|
change the name and/or aliases of an object
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@name obj = name;alias1;alias2
|
@name <obj> = <newname>;alias1;alias2
|
||||||
|
|
||||||
Rename an object to something new. Use *obj to
|
Rename an object to something new. Use *obj to
|
||||||
rename an account.
|
rename an account.
|
||||||
|
|
@ -1322,7 +1326,7 @@ def _convert_from_string(cmd, strobj):
|
||||||
Python 2.6 and later:
|
Python 2.6 and later:
|
||||||
Supports all Python structures through literal_eval as long as they
|
Supports all Python structures through literal_eval as long as they
|
||||||
are valid Python syntax. If they are not (such as [test, test2], ie
|
are valid Python syntax. If they are not (such as [test, test2], ie
|
||||||
withtout the quotes around the strings), the entire structure will
|
without the quotes around the strings), the entire structure will
|
||||||
be converted to a string and a warning will be given.
|
be converted to a string and a warning will be given.
|
||||||
|
|
||||||
We need to convert like this since all data being sent over the
|
We need to convert like this since all data being sent over the
|
||||||
|
|
@ -1379,6 +1383,7 @@ def _convert_from_string(cmd, strobj):
|
||||||
# nested lists/dicts)
|
# nested lists/dicts)
|
||||||
return rec_convert(strobj.strip())
|
return rec_convert(strobj.strip())
|
||||||
|
|
||||||
|
|
||||||
class CmdSetAttribute(ObjManipCommand):
|
class CmdSetAttribute(ObjManipCommand):
|
||||||
"""
|
"""
|
||||||
set attribute on an object or account
|
set attribute on an object or account
|
||||||
|
|
@ -1398,7 +1403,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
(if any).
|
(if any).
|
||||||
|
|
||||||
The most common data to save with this command are strings and
|
The most common data to save with this command are strings and
|
||||||
numbers. You can however also set Python primities such as lists,
|
numbers. You can however also set Python primitives such as lists,
|
||||||
dictionaries and tuples on objects (this might be important for
|
dictionaries and tuples on objects (this might be important for
|
||||||
the functionality of certain custom objects). This is indicated
|
the functionality of certain custom objects). This is indicated
|
||||||
by you starting your value with one of |c'|n, |c"|n, |c(|n, |c[|n
|
by you starting your value with one of |c'|n, |c"|n, |c(|n, |c[|n
|
||||||
|
|
@ -1557,7 +1562,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
set or change an object's typeclass
|
set or change an object's typeclass
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@typclass[/switch] <object> [= <typeclass.path>]
|
@typeclass[/switch] <object> [= typeclass.path]
|
||||||
@type ''
|
@type ''
|
||||||
@parent ''
|
@parent ''
|
||||||
@swap - this is a shorthand for using /force/reset flags.
|
@swap - this is a shorthand for using /force/reset flags.
|
||||||
|
|
@ -1574,7 +1579,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
Example:
|
Example:
|
||||||
@type button = examples.red_button.RedButton
|
@type button = examples.red_button.RedButton
|
||||||
|
|
||||||
If the typeclass.path is not given, the current object's
|
If the typeclass_path is not given, the current object's
|
||||||
typeclass is assumed.
|
typeclass is assumed.
|
||||||
|
|
||||||
View or set an object's typeclass. If setting, the creation hooks
|
View or set an object's typeclass. If setting, the creation hooks
|
||||||
|
|
@ -1603,7 +1608,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
if not self.args:
|
if not self.args:
|
||||||
caller.msg("Usage: %s <object> [=<typeclass]" % self.cmdstring)
|
caller.msg("Usage: %s <object> [= typeclass]" % self.cmdstring)
|
||||||
return
|
return
|
||||||
|
|
||||||
# get object to swap on
|
# get object to swap on
|
||||||
|
|
@ -1674,7 +1679,7 @@ class CmdWipe(ObjManipCommand):
|
||||||
clear all attributes from an object
|
clear all attributes from an object
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@wipe <object>[/attribute[/attribute...]]
|
@wipe <object>[/<attr>[/<attr>...]]
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@wipe box
|
@wipe box
|
||||||
|
|
@ -1695,7 +1700,7 @@ class CmdWipe(ObjManipCommand):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
if not self.args:
|
if not self.args:
|
||||||
caller.msg("Usage: @wipe <object>[/attribute/attribute...]")
|
caller.msg("Usage: @wipe <object>[/<attr>/<attr>...]")
|
||||||
return
|
return
|
||||||
|
|
||||||
# get the attributes set by our custom parser
|
# get the attributes set by our custom parser
|
||||||
|
|
@ -1727,7 +1732,7 @@ class CmdLock(ObjManipCommand):
|
||||||
Usage:
|
Usage:
|
||||||
@lock <object>[ = <lockstring>]
|
@lock <object>[ = <lockstring>]
|
||||||
or
|
or
|
||||||
@lock[/switch] object/<access_type>
|
@lock[/switch] <object>/<access_type>
|
||||||
|
|
||||||
Switch:
|
Switch:
|
||||||
del - delete given access type
|
del - delete given access type
|
||||||
|
|
@ -1747,7 +1752,7 @@ class CmdLock(ObjManipCommand):
|
||||||
an object locked with this string will only be possible to
|
an object locked with this string will only be possible to
|
||||||
pick up by Admins or by object with id=25.
|
pick up by Admins or by object with id=25.
|
||||||
|
|
||||||
You can add several access_types after oneanother by separating
|
You can add several access_types after one another by separating
|
||||||
them by ';', i.e:
|
them by ';', i.e:
|
||||||
'get:id(25);delete:perm(Builder)'
|
'get:id(25);delete:perm(Builder)'
|
||||||
"""
|
"""
|
||||||
|
|
@ -1761,7 +1766,8 @@ class CmdLock(ObjManipCommand):
|
||||||
|
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
if not self.args:
|
if not self.args:
|
||||||
string = "@lock <object>[ = <lockstring>] or @lock[/switch] object/<access_type>"
|
string = "@lock <object>[ = <lockstring>] or @lock[/switch] " \
|
||||||
|
"<object>/<access_type>"
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -2334,7 +2340,7 @@ class CmdScript(COMMAND_DEFAULT_CLASS):
|
||||||
attach a script to an object
|
attach a script to an object
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@script[/switch] <obj> [= <script.path or scriptkey>]
|
@script[/switch] <obj> [= script_path or <scriptkey>]
|
||||||
|
|
||||||
Switches:
|
Switches:
|
||||||
start - start all non-running scripts on object, or a given script only
|
start - start all non-running scripts on object, or a given script only
|
||||||
|
|
@ -2360,7 +2366,7 @@ class CmdScript(COMMAND_DEFAULT_CLASS):
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
|
|
||||||
if not self.args:
|
if not self.args:
|
||||||
string = "Usage: @script[/switch] <obj> [= <script.path or script key>]"
|
string = "Usage: @script[/switch] <obj> [= script_path or <script key>]"
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -2561,7 +2567,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
@spawn
|
@spawn
|
||||||
@spawn[/switch] prototype_name
|
@spawn[/switch] <prototype_name>
|
||||||
@spawn[/switch] {prototype dictionary}
|
@spawn[/switch] {prototype dictionary}
|
||||||
|
|
||||||
Switch:
|
Switch:
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ from evennia.utils.eveditor import EvEditor
|
||||||
from evennia.utils.utils import string_suggestions, class_from_module
|
from evennia.utils.utils import string_suggestions, class_from_module
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
HELP_MORE = settings.HELP_MORE
|
||||||
|
|
||||||
# limit symbol import for API
|
# limit symbol import for API
|
||||||
__all__ = ("CmdHelp", "CmdSetHelp")
|
__all__ = ("CmdHelp", "CmdSetHelp")
|
||||||
|
|
@ -45,9 +46,9 @@ class CmdHelp(Command):
|
||||||
return_cmdset = True
|
return_cmdset = True
|
||||||
|
|
||||||
# Help messages are wrapped in an EvMore call (unless using the webclient
|
# Help messages are wrapped in an EvMore call (unless using the webclient
|
||||||
# with separate help popups) If you want to avoid this, simply set the
|
# with separate help popups) If you want to avoid this, simply add
|
||||||
# 'help_more' flag to False.
|
# 'HELP_MORE = False' in your settings/conf/settings.py
|
||||||
help_more = True
|
help_more = HELP_MORE
|
||||||
|
|
||||||
# suggestion cutoff, between 0 and 1 (1 => perfect match)
|
# suggestion cutoff, between 0 and 1 (1 => perfect match)
|
||||||
suggestion_cutoff = 0.6
|
suggestion_cutoff = 0.6
|
||||||
|
|
@ -197,7 +198,8 @@ class CmdHelp(Command):
|
||||||
# retrieve all available commands and database topics
|
# retrieve all available commands and database topics
|
||||||
all_cmds = [cmd for cmd in cmdset if self.check_show_help(cmd, caller)]
|
all_cmds = [cmd for cmd in cmdset if self.check_show_help(cmd, caller)]
|
||||||
all_topics = [topic for topic in HelpEntry.objects.all() if topic.access(caller, 'view', default=True)]
|
all_topics = [topic for topic in HelpEntry.objects.all() if topic.access(caller, 'view', default=True)]
|
||||||
all_categories = list(set([cmd.help_category.lower() for cmd in all_cmds] + [topic.help_category.lower() for topic in all_topics]))
|
all_categories = list(set([cmd.help_category.lower() for cmd in all_cmds] + [topic.help_category.lower()
|
||||||
|
for topic in all_topics]))
|
||||||
|
|
||||||
if query in ("list", "all"):
|
if query in ("list", "all"):
|
||||||
# we want to list all available help entries, grouped by category
|
# we want to list all available help entries, grouped by category
|
||||||
|
|
@ -221,7 +223,8 @@ class CmdHelp(Command):
|
||||||
if suggestion_maxnum > 0:
|
if suggestion_maxnum > 0:
|
||||||
vocabulary = [cmd.key for cmd in all_cmds if cmd] + [topic.key for topic in all_topics] + all_categories
|
vocabulary = [cmd.key for cmd in all_cmds if cmd] + [topic.key for topic in all_topics] + all_categories
|
||||||
[vocabulary.extend(cmd.aliases) for cmd in all_cmds]
|
[vocabulary.extend(cmd.aliases) for cmd in all_cmds]
|
||||||
suggestions = [sugg for sugg in string_suggestions(query, set(vocabulary), cutoff=suggestion_cutoff, maxnum=suggestion_maxnum)
|
suggestions = [sugg for sugg in string_suggestions(query, set(vocabulary), cutoff=suggestion_cutoff,
|
||||||
|
maxnum=suggestion_maxnum)
|
||||||
if sugg != query]
|
if sugg != query]
|
||||||
if not suggestions:
|
if not suggestions:
|
||||||
suggestions = [sugg for sugg in vocabulary if sugg != query and sugg.startswith(query)]
|
suggestions = [sugg for sugg in vocabulary if sugg != query and sugg.startswith(query)]
|
||||||
|
|
@ -230,9 +233,9 @@ class CmdHelp(Command):
|
||||||
match = [cmd for cmd in all_cmds if cmd == query]
|
match = [cmd for cmd in all_cmds if cmd == query]
|
||||||
if len(match) == 1:
|
if len(match) == 1:
|
||||||
formatted = self.format_help_entry(match[0].key,
|
formatted = self.format_help_entry(match[0].key,
|
||||||
match[0].get_help(caller, cmdset),
|
match[0].get_help(caller, cmdset),
|
||||||
aliases=match[0].aliases,
|
aliases=match[0].aliases,
|
||||||
suggested=suggestions)
|
suggested=suggestions)
|
||||||
self.msg_help(formatted)
|
self.msg_help(formatted)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -240,16 +243,17 @@ class CmdHelp(Command):
|
||||||
match = list(HelpEntry.objects.find_topicmatch(query, exact=True))
|
match = list(HelpEntry.objects.find_topicmatch(query, exact=True))
|
||||||
if len(match) == 1:
|
if len(match) == 1:
|
||||||
formatted = self.format_help_entry(match[0].key,
|
formatted = self.format_help_entry(match[0].key,
|
||||||
match[0].entrytext,
|
match[0].entrytext,
|
||||||
aliases=match[0].aliases.all(),
|
aliases=match[0].aliases.all(),
|
||||||
suggested=suggestions)
|
suggested=suggestions)
|
||||||
self.msg_help(formatted)
|
self.msg_help(formatted)
|
||||||
return
|
return
|
||||||
|
|
||||||
# try to see if a category name was entered
|
# try to see if a category name was entered
|
||||||
if query in all_categories:
|
if query in all_categories:
|
||||||
self.msg_help(self.format_help_list({query:[cmd.key for cmd in all_cmds if cmd.help_category==query]},
|
self.msg_help(self.format_help_list({query: [cmd.key for cmd in all_cmds if cmd.help_category == query]},
|
||||||
{query:[topic.key for topic in all_topics if topic.help_category==query]}))
|
{query: [topic.key for topic in all_topics
|
||||||
|
if topic.help_category == query]}))
|
||||||
return
|
return
|
||||||
|
|
||||||
# no exact matches found. Just give suggestions.
|
# no exact matches found. Just give suggestions.
|
||||||
|
|
@ -263,6 +267,7 @@ def _loadhelp(caller):
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _savehelp(caller, buffer):
|
def _savehelp(caller, buffer):
|
||||||
entry = caller.db._editing_help
|
entry = caller.db._editing_help
|
||||||
caller.msg("Saved help entry.")
|
caller.msg("Saved help entry.")
|
||||||
|
|
@ -274,6 +279,7 @@ def _quithelp(caller):
|
||||||
caller.msg("Closing the editor.")
|
caller.msg("Closing the editor.")
|
||||||
del caller.db._editing_help
|
del caller.db._editing_help
|
||||||
|
|
||||||
|
|
||||||
class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
Edit the help database.
|
Edit the help database.
|
||||||
|
|
@ -305,7 +311,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
||||||
help_category = "Building"
|
help_category = "Building"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"Implement the function"
|
"""Implement the function"""
|
||||||
|
|
||||||
switches = self.switches
|
switches = self.switches
|
||||||
lhslist = self.lhslist
|
lhslist = self.lhslist
|
||||||
|
|
@ -327,7 +333,7 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
||||||
# check if we have an old entry with the same name
|
# check if we have an old entry with the same name
|
||||||
try:
|
try:
|
||||||
for querystr in topicstrlist:
|
for querystr in topicstrlist:
|
||||||
old_entry = HelpEntry.objects.find_topicmatch(querystr) # also search by alias
|
old_entry = HelpEntry.objects.find_topicmatch(querystr) # also search by alias
|
||||||
if old_entry:
|
if old_entry:
|
||||||
old_entry = list(old_entry)[0]
|
old_entry = list(old_entry)[0]
|
||||||
break
|
break
|
||||||
|
|
@ -350,12 +356,12 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
||||||
else:
|
else:
|
||||||
helpentry = create.create_help_entry(topicstr,
|
helpentry = create.create_help_entry(topicstr,
|
||||||
self.rhs, category=category,
|
self.rhs, category=category,
|
||||||
locks=lockstring,aliases=aliases)
|
locks=lockstring, aliases=aliases)
|
||||||
self.caller.db._editing_help = helpentry
|
self.caller.db._editing_help = helpentry
|
||||||
|
|
||||||
EvEditor(self.caller, loadfunc=_loadhelp, savefunc=_savehelp,
|
EvEditor(self.caller, loadfunc=_loadhelp, savefunc=_savehelp,
|
||||||
quitfunc=_quithelp, key="topic {}".format(topicstr),
|
quitfunc=_quithelp, key="topic {}".format(topicstr),
|
||||||
persistent=True)
|
persistent=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'append' in switches or "merge" in switches or "extend" in switches:
|
if 'append' in switches or "merge" in switches or "extend" in switches:
|
||||||
|
|
@ -399,21 +405,21 @@ class CmdSetHelp(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg("Overwrote the old topic '%s'%s." % (topicstr, aliastxt))
|
self.msg("Overwrote the old topic '%s'%s." % (topicstr, aliastxt))
|
||||||
else:
|
else:
|
||||||
self.msg("Topic '%s'%s already exists. Use /replace to overwrite "
|
self.msg("Topic '%s'%s already exists. Use /replace to overwrite "
|
||||||
"or /append or /merge to add text to it." % (topicstr, aliastxt))
|
"or /append or /merge to add text to it." % (topicstr, aliastxt))
|
||||||
else:
|
else:
|
||||||
# no old entry. Create a new one.
|
# no old entry. Create a new one.
|
||||||
new_entry = create.create_help_entry(topicstr,
|
new_entry = create.create_help_entry(topicstr,
|
||||||
self.rhs, category=category,
|
self.rhs, category=category,
|
||||||
locks=lockstring,aliases=aliases)
|
locks=lockstring, aliases=aliases)
|
||||||
if new_entry:
|
if new_entry:
|
||||||
self.msg("Topic '%s'%s was successfully created." % (topicstr, aliastxt))
|
self.msg("Topic '%s'%s was successfully created." % (topicstr, aliastxt))
|
||||||
if 'edit' in switches:
|
if 'edit' in switches:
|
||||||
# open the line editor to edit the helptext
|
# open the line editor to edit the helptext
|
||||||
self.caller.db._editing_help = new_entry
|
self.caller.db._editing_help = new_entry
|
||||||
EvEditor(self.caller, loadfunc=_loadhelp,
|
EvEditor(self.caller, loadfunc=_loadhelp,
|
||||||
savefunc=_savehelp, quitfunc=_quithelp,
|
savefunc=_savehelp, quitfunc=_quithelp,
|
||||||
key="topic {}".format(new_entry.key),
|
key="topic {}".format(new_entry.key),
|
||||||
persistent=True)
|
persistent=True)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.msg("Error when creating topic '%s'%s! Contact an admin." % (topicstr, aliastxt))
|
self.msg("Error when creating topic '%s'%s! Contact an admin." % (topicstr, aliastxt))
|
||||||
|
|
|
||||||
|
|
@ -483,8 +483,10 @@ class SubscriptionHandler(object):
|
||||||
self._cache = None
|
self._cache = None
|
||||||
|
|
||||||
def _recache(self):
|
def _recache(self):
|
||||||
self._cache = {account : True for account in self.obj.db_account_subscriptions.all()}
|
self._cache = {account: True for account in self.obj.db_account_subscriptions.all()
|
||||||
self._cache.update({obj : True for obj in self.obj.db_object_subscriptions.all()})
|
if hasattr(account, 'pk') and account.pk}
|
||||||
|
self._cache.update({obj: True for obj in self.obj.db_object_subscriptions.all()
|
||||||
|
if hasattr(obj, 'pk') and obj.pk})
|
||||||
|
|
||||||
def has(self, entity):
|
def has(self, entity):
|
||||||
"""
|
"""
|
||||||
|
|
@ -576,14 +578,23 @@ class SubscriptionHandler(object):
|
||||||
are puppeted by an online account.
|
are puppeted by an online account.
|
||||||
"""
|
"""
|
||||||
subs = []
|
subs = []
|
||||||
|
recache_needed = False
|
||||||
for obj in self.all():
|
for obj in self.all():
|
||||||
if hasattr(obj, 'account'):
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
if not obj.account:
|
try:
|
||||||
|
if hasattr(obj, 'account'):
|
||||||
|
if not obj.account:
|
||||||
|
continue
|
||||||
|
obj = obj.account
|
||||||
|
if not obj.is_connected:
|
||||||
continue
|
continue
|
||||||
obj = obj.account
|
except ObjectDoesNotExist:
|
||||||
if not obj.is_connected:
|
# a subscribed object has already been deleted. Mark that we need a recache and ignore it
|
||||||
|
recache_needed = True
|
||||||
continue
|
continue
|
||||||
subs.append(obj)
|
subs.append(obj)
|
||||||
|
if recache_needed:
|
||||||
|
self._recache()
|
||||||
return subs
|
return subs
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,6 @@ things you want from here into your game folder and change them there.
|
||||||
multiple descriptions for time and season as well as details.
|
multiple descriptions for time and season as well as details.
|
||||||
* GenderSub (Griatch 2015) - Simple example (only) of storing gender
|
* GenderSub (Griatch 2015) - Simple example (only) of storing gender
|
||||||
on a character and access it in an emote with a custom marker.
|
on a character and access it in an emote with a custom marker.
|
||||||
* In-game Python (Vincent Le Geoff 2017) - Allow trusted builders to script
|
|
||||||
objects and events using Python from in-game.
|
|
||||||
* Mail (grungies1138 2016) - An in-game mail system for communication.
|
* Mail (grungies1138 2016) - An in-game mail system for communication.
|
||||||
* Menu login (Griatch 2011) - A login system using menus asking
|
* Menu login (Griatch 2011) - A login system using menus asking
|
||||||
for name/password rather than giving them as one command
|
for name/password rather than giving them as one command
|
||||||
|
|
@ -41,6 +39,8 @@ things you want from here into your game folder and change them there.
|
||||||
* Menu Login (Vincent-lg 2016) - Alternate login system using EvMenu.
|
* Menu Login (Vincent-lg 2016) - Alternate login system using EvMenu.
|
||||||
* Multidescer (Griatch 2016) - Advanced descriptions combined from
|
* Multidescer (Griatch 2016) - Advanced descriptions combined from
|
||||||
many separate description components, inspired by MUSH.
|
many separate description components, inspired by MUSH.
|
||||||
|
* Random String Generator (Vincent Le Goff 2017) - Simple pseudo-random
|
||||||
|
generator of strings with rules, avoiding repetitions.
|
||||||
* RPLanguage (Griatch 2015) - Dynamic obfuscation of emotes when
|
* RPLanguage (Griatch 2015) - Dynamic obfuscation of emotes when
|
||||||
speaking unfamiliar languages. Also obfuscates whispers.
|
speaking unfamiliar languages. Also obfuscates whispers.
|
||||||
* RPSystem (Griatch 2015) - Full director-style emoting system
|
* RPSystem (Griatch 2015) - Full director-style emoting system
|
||||||
|
|
@ -60,6 +60,8 @@ things you want from here into your game folder and change them there.
|
||||||
|
|
||||||
* EGI_Client (gtaylor 2016) - Client for reporting game status
|
* EGI_Client (gtaylor 2016) - Client for reporting game status
|
||||||
to the Evennia game index (games.evennia.com)
|
to the Evennia game index (games.evennia.com)
|
||||||
|
* In-game Python (Vincent Le Goff 2017) - Allow trusted builders to script
|
||||||
|
objects and events using Python from in-game.
|
||||||
* Tutorial examples (Griatch 2011, 2015) - A folder of basic
|
* Tutorial examples (Griatch 2011, 2015) - A folder of basic
|
||||||
example objects, commands and scripts.
|
example objects, commands and scripts.
|
||||||
* Tutorial world (Griatch 2011, 2015) - A folder containing the
|
* Tutorial world (Griatch 2011, 2015) - A folder containing the
|
||||||
|
|
|
||||||
|
|
@ -428,7 +428,7 @@ class CmdExtendedDesc(default_cmds.CmdDesc):
|
||||||
if self.switches and self.switches[0] in ("spring", "summer", "autumn", "winter"):
|
if self.switches and self.switches[0] in ("spring", "summer", "autumn", "winter"):
|
||||||
# a seasonal switch was given
|
# a seasonal switch was given
|
||||||
if self.rhs:
|
if self.rhs:
|
||||||
caller.msg("Seasonal descs only works with rooms, not objects.")
|
caller.msg("Seasonal descs only work with rooms, not objects.")
|
||||||
return
|
return
|
||||||
switch = self.switches[0]
|
switch = self.switches[0]
|
||||||
if not location:
|
if not location:
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import traceback
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia import DefaultObject, DefaultScript, ChannelDB, ScriptDB
|
from evennia import DefaultObject, DefaultScript, ChannelDB, ScriptDB
|
||||||
from evennia import logger
|
from evennia import logger, ObjectDB
|
||||||
from evennia.utils.ansi import raw
|
from evennia.utils.ansi import raw
|
||||||
from evennia.utils.create import create_channel
|
from evennia.utils.create import create_channel
|
||||||
from evennia.utils.dbserialize import dbserialize
|
from evennia.utils.dbserialize import dbserialize
|
||||||
|
|
@ -101,21 +101,29 @@ class EventHandler(DefaultScript):
|
||||||
Return a dictionary of events on this object.
|
Return a dictionary of events on this object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (Object): the connected object.
|
obj (Object or typeclass): the connected object or a general typeclass.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A dictionary of the object's events.
|
A dictionary of the object's events.
|
||||||
|
|
||||||
Note:
|
Notes:
|
||||||
Events would define what the object can have as
|
Events would define what the object can have as
|
||||||
callbacks. Note, however, that chained callbacks will not
|
callbacks. Note, however, that chained callbacks will not
|
||||||
appear in events and are handled separately.
|
appear in events and are handled separately.
|
||||||
|
|
||||||
|
You can also request the events of a typeclass, not a
|
||||||
|
connected object. This is useful to get the global list
|
||||||
|
of events for a typeclass that has no object yet.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
events = {}
|
events = {}
|
||||||
all_events = self.ndb.events
|
all_events = self.ndb.events
|
||||||
classes = Queue()
|
classes = Queue()
|
||||||
classes.put(type(obj))
|
if isinstance(obj, type):
|
||||||
|
classes.put(obj)
|
||||||
|
else:
|
||||||
|
classes.put(type(obj))
|
||||||
|
|
||||||
invalid = []
|
invalid = []
|
||||||
while not classes.empty():
|
while not classes.empty():
|
||||||
typeclass = classes.get()
|
typeclass = classes.get()
|
||||||
|
|
|
||||||
345
evennia/contrib/random_string_generator.py
Normal file
345
evennia/contrib/random_string_generator.py
Normal file
|
|
@ -0,0 +1,345 @@
|
||||||
|
"""
|
||||||
|
Pseudo-random generator and registry
|
||||||
|
|
||||||
|
Evennia contribution - Vincent Le Goff 2017
|
||||||
|
|
||||||
|
This contrib can be used to generate pseudo-random strings of information
|
||||||
|
with specific criteria. You could, for instance, use it to generate
|
||||||
|
phone numbers, license plate numbers, validation codes, non-sensivite
|
||||||
|
passwords and so on. The strings generated by the generator will be
|
||||||
|
stored and won't be available again in order to avoid repetition.
|
||||||
|
Here's a very simple example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from evennia.contrib.random_string_generator import RandomStringGenerator
|
||||||
|
# Create a generator for phone numbers
|
||||||
|
phone_generator = RandomStringGenerator("phone number", r"555-[0-9]{3}-[0-9]{4}")
|
||||||
|
# Generate a phone number (555-XXX-XXXX with X as numbers)
|
||||||
|
number = phone_generator.get()
|
||||||
|
# `number` will contain something like: "555-981-2207"
|
||||||
|
# If you call `phone_generator.get`, it won't give the same anymore.phone_generator.all()
|
||||||
|
# Will return a list of all currently-used phone numbers
|
||||||
|
phone_generator.remove("555-981-2207")
|
||||||
|
# The number can be generated again
|
||||||
|
```
|
||||||
|
|
||||||
|
To use it, you will need to:
|
||||||
|
|
||||||
|
1. Import the `RandomStringGenerator` class from the contrib.
|
||||||
|
2. Create an instance of this class taking two arguments:
|
||||||
|
- The name of the gemerator (like "phone number", "license plate"...).
|
||||||
|
- The regular expression representing the expected results.
|
||||||
|
3. Use the generator's `all`, `get` and `remove` methods as shown above.
|
||||||
|
|
||||||
|
To understand how to read and create regular expressions, you can refer to
|
||||||
|
[the documentation on the re module](https://docs.python.org/2/library/re.html).
|
||||||
|
Some examples of regular expressions you could use:
|
||||||
|
|
||||||
|
- `r"555-\d{3}-\d{4}"`: 555, a dash, 3 digits, another dash, 4 digits.
|
||||||
|
- `r"[0-9]{3}[A-Z][0-9]{3}"`: 3 digits, a capital letter, 3 digits.
|
||||||
|
- `r"[A-Za-z0-9]{8,15}"`: between 8 and 15 letters and digits.
|
||||||
|
- ...
|
||||||
|
|
||||||
|
Behind the scenes, a script is created to store the generated information
|
||||||
|
for a single generator. The `RandomStringGenerator` object will also
|
||||||
|
read the regular expression you give to it to see what information is
|
||||||
|
required (letters, digits, a more restricted class, simple characters...)...
|
||||||
|
More complex regular expressions (with branches for instance) might not be
|
||||||
|
available.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from random import choice, randint, seed
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
import time
|
||||||
|
|
||||||
|
from evennia import DefaultScript, ScriptDB
|
||||||
|
from evennia.utils.create import create_script
|
||||||
|
|
||||||
|
class RejectedRegex(RuntimeError):
|
||||||
|
|
||||||
|
"""The provided regular expression has been rejected.
|
||||||
|
|
||||||
|
More details regarding why this error occurred will be provided in
|
||||||
|
the message. The usual reason is the provided regular expression is
|
||||||
|
not specific enough and could lead to inconsistent generating.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ExhaustedGenerator(RuntimeError):
|
||||||
|
|
||||||
|
"""The generator hasn't any available strings to generate anymore."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class RandomStringGeneratorScript(DefaultScript):
|
||||||
|
|
||||||
|
"""
|
||||||
|
The global script to hold all generators.
|
||||||
|
|
||||||
|
It will be automatically created the first time `generate` is called
|
||||||
|
on a RandomStringGenerator object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_script_creation(self):
|
||||||
|
"""Hook called when the script is created."""
|
||||||
|
self.key = "generator_script"
|
||||||
|
self.desc = "Global generator script"
|
||||||
|
self.persistent = True
|
||||||
|
|
||||||
|
# Permanent data to be stored
|
||||||
|
self.db.generated = {}
|
||||||
|
|
||||||
|
|
||||||
|
class RandomStringGenerator(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A generator class to generate pseudo-random strings with a rule.
|
||||||
|
|
||||||
|
The "rule" defining what the generator should provide in terms of
|
||||||
|
string is given as a regular expression when creating instances of
|
||||||
|
this class. You can use the `all` method to get all generated strings,
|
||||||
|
the `get` method to generate a new string, the `remove` method
|
||||||
|
to remove a generated string, or the `clear` method to remove all
|
||||||
|
generated strings.
|
||||||
|
|
||||||
|
Bear in mind, however, that while the generated strings will be
|
||||||
|
stored to avoid repetition, the generator will not concern itself
|
||||||
|
with how the string is stored on the object you use. You probably
|
||||||
|
want to create a tag to mark this object. This is outside of the scope
|
||||||
|
of this class.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We keep the script as a class variable to optimize querying
|
||||||
|
# with multiple instandces
|
||||||
|
script = None
|
||||||
|
|
||||||
|
def __init__(self, name, regex):
|
||||||
|
"""
|
||||||
|
Create a new generator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): name of the generator to create.
|
||||||
|
regex (str): regular expression describing the generator.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
`name` should be an explicit name. If you use more than one
|
||||||
|
generator in your game, be sure to give them different names.
|
||||||
|
This name will be used to store the generated information
|
||||||
|
in the global script, and in case of errors.
|
||||||
|
|
||||||
|
The regular expression should describe the generator, what
|
||||||
|
it should generate: a phone number, a license plate, a password
|
||||||
|
or something else. Regular expressions allow you to use
|
||||||
|
pretty advanced criteria, but be aware that some regular
|
||||||
|
expressions will be rejected if not specific enough.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RejectedRegex: the provided regular expression couldn't be
|
||||||
|
accepted as a valid generator description.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.elements = []
|
||||||
|
self.total = 1
|
||||||
|
|
||||||
|
# Analyze the regex if any
|
||||||
|
if regex:
|
||||||
|
self._find_elements(regex)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<evennia.contrib.random_string_generator.RandomStringGenerator for {}>".format(self.name)
|
||||||
|
|
||||||
|
def _get_script(self):
|
||||||
|
"""Get or create the script."""
|
||||||
|
if type(self).script:
|
||||||
|
return type(self).script
|
||||||
|
|
||||||
|
try:
|
||||||
|
script = ScriptDB.objects.get(db_key="generator_script")
|
||||||
|
except ScriptDB.DoesNotExist:
|
||||||
|
script = create_script("contrib.random_string_generator.RandomStringGeneratorScript")
|
||||||
|
|
||||||
|
type(self).script = script
|
||||||
|
return script
|
||||||
|
|
||||||
|
def _find_elements(self, regex):
|
||||||
|
"""
|
||||||
|
Find the elements described in the regular expression. This will
|
||||||
|
analyze the provided regular expression and try to find elements.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
regex (str): the regular expression.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.total = 1
|
||||||
|
self.elements = []
|
||||||
|
tree = re.sre_parse.parse(regex).data
|
||||||
|
# `tree` contains a list of elements in the regular expression
|
||||||
|
for element in tree:
|
||||||
|
# `eleemnt` is also a list, the first element is a string
|
||||||
|
name = element[0]
|
||||||
|
desc = {"min": 1, "max": 1}
|
||||||
|
|
||||||
|
# If `.`, break here
|
||||||
|
if name == "any":
|
||||||
|
raise RejectedRegex("the . definition is too broad, specify what you need more precisely")
|
||||||
|
elif name == "at":
|
||||||
|
# Either the beginning or end, we ignore it
|
||||||
|
continue
|
||||||
|
elif name == "min_repeat":
|
||||||
|
raise RejectedRegex("you have to provide a maximum number of this character class")
|
||||||
|
elif name == "max_repeat":
|
||||||
|
desc["min"] = element[1][0]
|
||||||
|
desc["max"] = element[1][1]
|
||||||
|
desc["chars"] = self._find_literal(element[1][2][0])
|
||||||
|
elif name == "in":
|
||||||
|
desc["chars"] = self._find_literal(element)
|
||||||
|
elif name == "literal":
|
||||||
|
desc["chars"] = self._find_literal(element)
|
||||||
|
else:
|
||||||
|
raise RejectedRegex("unhandled regex syntax:: {}".format(repr(name)))
|
||||||
|
|
||||||
|
self.elements.append(desc)
|
||||||
|
self.total *= len(desc["chars"]) ** desc["max"]
|
||||||
|
|
||||||
|
def _find_literal(self, element):
|
||||||
|
"""Find the literal corresponding to a piece of regular expression."""
|
||||||
|
chars = []
|
||||||
|
if element[0] == "literal":
|
||||||
|
chars.append(chr(element[1]))
|
||||||
|
elif element[0] == "in":
|
||||||
|
negate = False
|
||||||
|
if element[1][0][0] == "negate":
|
||||||
|
negate = True
|
||||||
|
chars = list(string.ascii_letters + string.digits)
|
||||||
|
|
||||||
|
for part in element[1]:
|
||||||
|
if part[0] == "negate":
|
||||||
|
continue
|
||||||
|
|
||||||
|
sublist = self._find_literal(part)
|
||||||
|
for char in sublist:
|
||||||
|
if negate:
|
||||||
|
if char in chars:
|
||||||
|
chars.remove(char)
|
||||||
|
else:
|
||||||
|
chars.append(char)
|
||||||
|
elif element[0] == "range":
|
||||||
|
chars = [chr(i) for i in range(element[1][0], element[1][1] + 1)]
|
||||||
|
elif element[0] == "category":
|
||||||
|
category = element[1]
|
||||||
|
if category == "category_digit":
|
||||||
|
chars = list(string.digits)
|
||||||
|
elif category == "category_word":
|
||||||
|
chars = list(string.letters)
|
||||||
|
else:
|
||||||
|
raise RejectedRegex("unknown category: {}".format(category))
|
||||||
|
else:
|
||||||
|
raise RejectedRegex("cannot find the literal: {}".format(element[0]))
|
||||||
|
|
||||||
|
return chars
|
||||||
|
|
||||||
|
def all(self):
|
||||||
|
"""
|
||||||
|
Return all generated strings for this generator.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
strings (list of strr): the list of strings that are already
|
||||||
|
used. The strings that were generated first come first in the list.
|
||||||
|
|
||||||
|
"""
|
||||||
|
script = self._get_script()
|
||||||
|
generated = list(script.db.generated.get(self.name, []))
|
||||||
|
return generated
|
||||||
|
|
||||||
|
def get(self, store=True, unique=True):
|
||||||
|
"""
|
||||||
|
Generate a pseudo-random string according to the regular expression.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
store (bool, optional): store the generated string in the script.
|
||||||
|
unique (bool, optional): keep on trying if the string is already used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The newly-generated string.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ExhaustedGenerator: if there's no available string in this generator.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
Unless asked explicitly, the returned string can't repeat itself.
|
||||||
|
|
||||||
|
"""
|
||||||
|
script = self._get_script()
|
||||||
|
generated = script.db.generated.get(self.name)
|
||||||
|
if generated is None:
|
||||||
|
script.db.generated[self.name] = []
|
||||||
|
generated = script.db.generated[self.name]
|
||||||
|
|
||||||
|
if len(generated) >= self.total:
|
||||||
|
raise ExhaustedGenerator
|
||||||
|
|
||||||
|
# Generate a pseudo-random string that might be used already
|
||||||
|
result = ""
|
||||||
|
for element in self.elements:
|
||||||
|
number = randint(element["min"], element["max"])
|
||||||
|
chars = element["chars"]
|
||||||
|
for index in range(number):
|
||||||
|
char = choice(chars)
|
||||||
|
result += char
|
||||||
|
|
||||||
|
# If the string has already been generated, try again
|
||||||
|
if result in generated and unique:
|
||||||
|
# Change the random seed, incrementing it slowly
|
||||||
|
epoch = time.time()
|
||||||
|
while result in generated:
|
||||||
|
epoch += 1
|
||||||
|
seed(epoch)
|
||||||
|
result = self.get(store=False, unique=False)
|
||||||
|
|
||||||
|
if store:
|
||||||
|
generated.append(result)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def remove(self, element):
|
||||||
|
"""
|
||||||
|
Remove a generated string from the list of stored strings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
element (str): the string to remove from the list of generated strings.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: the specified value hasn't been generated and is not present.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
The specified string has to be present in the script (so
|
||||||
|
has to have been generated). It will remove this entry
|
||||||
|
from the script, so this string could be generated again by
|
||||||
|
calling the `get` method.
|
||||||
|
|
||||||
|
"""
|
||||||
|
script = self._get_script()
|
||||||
|
generated = script.db.generated.get(self.name, [])
|
||||||
|
if element not in generated:
|
||||||
|
raise ValueError("the string {} isn't stored as generated by the generator {}".format(
|
||||||
|
element, self.name))
|
||||||
|
|
||||||
|
generated.remove(element)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Clear the generator of all generated strings.
|
||||||
|
|
||||||
|
"""
|
||||||
|
script = self._get_script()
|
||||||
|
generated = script.db.generated.get(self.name, [])
|
||||||
|
generated[:] = []
|
||||||
|
|
@ -1046,3 +1046,23 @@ class TestColorMarkup(EvenniaTest):
|
||||||
bright_map = color_markups.MUX_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP
|
bright_map = color_markups.MUX_COLOR_ANSI_XTERM256_BRIGHT_BG_EXTRA_MAP
|
||||||
self.assertEqual(bright_map[0][1], '%c[500')
|
self.assertEqual(bright_map[0][1], '%c[500')
|
||||||
self.assertEqual(bright_map[-1][1], '%c[222')
|
self.assertEqual(bright_map[-1][1], '%c[222')
|
||||||
|
|
||||||
|
from evennia.contrib import random_string_generator
|
||||||
|
|
||||||
|
SIMPLE_GENERATOR = random_string_generator.RandomStringGenerator("simple", "[01]{2}")
|
||||||
|
|
||||||
|
class TestRandomStringGenerator(EvenniaTest):
|
||||||
|
|
||||||
|
def test_generate(self):
|
||||||
|
"""Generate and fail when exhausted."""
|
||||||
|
generated = []
|
||||||
|
for i in range(4):
|
||||||
|
generated.append(SIMPLE_GENERATOR.get())
|
||||||
|
|
||||||
|
generated.sort()
|
||||||
|
self.assertEqual(generated, ["00", "01", "10", "11"])
|
||||||
|
|
||||||
|
# At this point, we have generated 4 strings.
|
||||||
|
# We can't generate one more
|
||||||
|
with self.assertRaises(random_string_generator.ExhaustedGenerator):
|
||||||
|
SIMPLE_GENERATOR.get()
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ Customisation example:
|
||||||
def at_prepare_room(self, coordinates, caller, room):
|
def at_prepare_room(self, coordinates, caller, room):
|
||||||
"Any other changes done to the room before showing it"
|
"Any other changes done to the room before showing it"
|
||||||
x, y = coordinates
|
x, y = coordinates
|
||||||
desc = "This is a room in the pyramid.
|
desc = "This is a room in the pyramid."
|
||||||
if y == 3 :
|
if y == 3 :
|
||||||
desc = "You can see far and wide from the top of the pyramid."
|
desc = "You can see far and wide from the top of the pyramid."
|
||||||
room.db.desc = desc
|
room.db.desc = desc
|
||||||
|
|
@ -157,7 +157,7 @@ def enter_wilderness(obj, coordinates=(0, 0), name="default"):
|
||||||
default one
|
default one
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if obj succesfully moved into the wilderness.
|
bool: True if obj successfully moved into the wilderness.
|
||||||
"""
|
"""
|
||||||
if not WildernessScript.objects.filter(db_key=name).exists():
|
if not WildernessScript.objects.filter(db_key=name).exists():
|
||||||
return False
|
return False
|
||||||
|
|
@ -253,6 +253,11 @@ class WildernessScript(DefaultScript):
|
||||||
room.ndb.wildernessscript = self
|
room.ndb.wildernessscript = self
|
||||||
room.ndb.active_coordinates = coordinates
|
room.ndb.active_coordinates = coordinates
|
||||||
for item in self.db.itemcoordinates.keys():
|
for item in self.db.itemcoordinates.keys():
|
||||||
|
# Items deleted from the wilderness leave None type 'ghosts'
|
||||||
|
# that must be cleaned out
|
||||||
|
if item is None:
|
||||||
|
del self.db.itemcoordinates[item]
|
||||||
|
continue
|
||||||
item.ndb.wilderness = self
|
item.ndb.wilderness = self
|
||||||
|
|
||||||
def is_valid_coordinates(self, coordinates):
|
def is_valid_coordinates(self, coordinates):
|
||||||
|
|
@ -298,6 +303,11 @@ class WildernessScript(DefaultScript):
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
for item, item_coordinates in self.itemcoordinates.items():
|
for item, item_coordinates in self.itemcoordinates.items():
|
||||||
|
# Items deleted from the wilderness leave None type 'ghosts'
|
||||||
|
# that must be cleaned out
|
||||||
|
if item is None:
|
||||||
|
del self.db.itemcoordinates[item]
|
||||||
|
continue
|
||||||
if coordinates == item_coordinates:
|
if coordinates == item_coordinates:
|
||||||
result.append(item)
|
result.append(item)
|
||||||
return result
|
return result
|
||||||
|
|
@ -503,7 +513,7 @@ class WildernessRoom(DefaultRoom):
|
||||||
moved_obj (Object): The object moved into this one.
|
moved_obj (Object): The object moved into this one.
|
||||||
source_location (Object): Where `moved_obj` came from.
|
source_location (Object): Where `moved_obj` came from.
|
||||||
"""
|
"""
|
||||||
if moved_obj.destination and moved_obj.destination == moved_obj.location:
|
if isinstance(moved_obj, WildernessExit):
|
||||||
# Ignore exits looping back to themselves: those are the regular
|
# Ignore exits looping back to themselves: those are the regular
|
||||||
# n, ne, ... exits.
|
# n, ne, ... exits.
|
||||||
return
|
return
|
||||||
|
|
|
||||||
188
evennia/scripts/taskhandler.py
Normal file
188
evennia/scripts/taskhandler.py
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
"""
|
||||||
|
Module containing the task handler for Evennia deferred tasks, persistent or not.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from twisted.internet import reactor, task
|
||||||
|
from evennia.server.models import ServerConfig
|
||||||
|
from evennia.utils.logger import log_trace, log_err
|
||||||
|
from evennia.utils.dbserialize import dbserialize, dbunserialize
|
||||||
|
|
||||||
|
TASK_HANDLER = None
|
||||||
|
|
||||||
|
class TaskHandler(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
A light singleton wrapper allowing to access permanent tasks.
|
||||||
|
|
||||||
|
When `utils.delay` is called, the task handler is used to create
|
||||||
|
the task. If `utils.delay` is called with `persistent=True`, the
|
||||||
|
task handler stores the new task and saves.
|
||||||
|
|
||||||
|
It's easier to access these tasks (should it be necessary) using
|
||||||
|
`evennia.scripts.taskhandler.TASK_HANDLER`, which contains one
|
||||||
|
instance of this class, and use its `add` and `remove` methods.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tasks = {}
|
||||||
|
self.to_save = {}
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""Load from the ServerConfig.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This should be automatically called when Evennia starts.
|
||||||
|
It populates `self.tasks` according to the ServerConfig.
|
||||||
|
|
||||||
|
"""
|
||||||
|
value = ServerConfig.objects.conf("delayed_tasks", default={})
|
||||||
|
if isinstance(value, basestring):
|
||||||
|
tasks = dbunserialize(value)
|
||||||
|
else:
|
||||||
|
tasks = value
|
||||||
|
|
||||||
|
# At this point, `tasks` contains a dictionary of still-serialized tasks
|
||||||
|
for task_id, value in tasks.items():
|
||||||
|
date, callback, args, kwargs = dbunserialize(value)
|
||||||
|
if isinstance(callback, tuple):
|
||||||
|
# `callback` can be an object and name for instance methods
|
||||||
|
obj, method = callback
|
||||||
|
callback = getattr(obj, method)
|
||||||
|
self.tasks[task_id] = (date, callback, args, kwargs)
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
"""Save the tasks in ServerConfig."""
|
||||||
|
for task_id, (date, callback, args, kwargs) in self.tasks.items():
|
||||||
|
if task_id in self.to_save:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if getattr(callback, "__self__", None):
|
||||||
|
# `callback` is an instance method
|
||||||
|
obj = callback.__self__
|
||||||
|
name = callback.__name__
|
||||||
|
callback = (obj, name)
|
||||||
|
|
||||||
|
# Check if callback can be pickled. args and kwargs have been checked
|
||||||
|
safe_callback = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
dbserialize(callback)
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
raise ValueError("the specified callback {} cannot be pickled. " \
|
||||||
|
"It must be a top-level function in a module or an " \
|
||||||
|
"instance method.".format(callback))
|
||||||
|
else:
|
||||||
|
safe_callback = callback
|
||||||
|
|
||||||
|
self.to_save[task_id] = dbserialize((date, safe_callback, args, kwargs))
|
||||||
|
ServerConfig.objects.conf("delayed_tasks", self.to_save)
|
||||||
|
|
||||||
|
def add(self, timedelay, callback, *args, **kwargs):
|
||||||
|
"""Add a new persistent task in the configuration.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timedelay (int or float): time in sedconds before calling the callback.
|
||||||
|
callback (function or instance method): the callback itself
|
||||||
|
any (any): any additional positional arguments to send to the callback
|
||||||
|
|
||||||
|
Kwargs:
|
||||||
|
persistent (bool, optional): persist the task (store it).
|
||||||
|
any (any): additional keyword arguments to send to the callback
|
||||||
|
|
||||||
|
"""
|
||||||
|
persistent = kwargs.get("persistent", False)
|
||||||
|
if persistent:
|
||||||
|
del kwargs["persistent"]
|
||||||
|
now = datetime.now()
|
||||||
|
delta = timedelta(seconds=timedelay)
|
||||||
|
|
||||||
|
# Choose a free task_id
|
||||||
|
safe_args = []
|
||||||
|
safe_kwargs = {}
|
||||||
|
used_ids = self.tasks.keys()
|
||||||
|
task_id = 1
|
||||||
|
while task_id in used_ids:
|
||||||
|
task_id += 1
|
||||||
|
|
||||||
|
# Check that args and kwargs contain picklable information
|
||||||
|
for arg in args:
|
||||||
|
try:
|
||||||
|
dbserialize(arg)
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
logger.log_err("The positional argument {} cannot be " \
|
||||||
|
"pickled and will not be present in the arguments " \
|
||||||
|
"fed to the callback {}".format(arg, callback))
|
||||||
|
else:
|
||||||
|
safe_args.append(arg)
|
||||||
|
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
try:
|
||||||
|
dbserialize(value)
|
||||||
|
except (TypeError, AttributeError):
|
||||||
|
logger.log_err("The {} keyword argument {} cannot be " \
|
||||||
|
"pickled and will not be present in the arguments " \
|
||||||
|
"fed to the callback {}".format(key, value, callback))
|
||||||
|
else:
|
||||||
|
safe_kwargs[key] = value
|
||||||
|
|
||||||
|
self.tasks[task_id] = (now + delta, callback, safe_args, safe_kwargs)
|
||||||
|
self.save()
|
||||||
|
callback = self.do_task
|
||||||
|
args = [task_id]
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
return task.deferLater(reactor, timedelay, callback, *args, **kwargs)
|
||||||
|
|
||||||
|
def remove(self, task_id):
|
||||||
|
"""Remove a persistent task without executing it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_id (int): an existing task ID.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
A non-persistent task doesn't have a task_id, it is not stored
|
||||||
|
in the TaskHandler.
|
||||||
|
|
||||||
|
"""
|
||||||
|
del self.tasks[task_id]
|
||||||
|
if task_id in self.to_save:
|
||||||
|
del self.to_save[task_id]
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def do_task(self, task_id):
|
||||||
|
"""Execute the task (call its callback).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
task_id (int): a valid task ID.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This will also remove it from the list of current tasks.
|
||||||
|
|
||||||
|
"""
|
||||||
|
date, callback, args, kwargs = self.tasks.pop(task_id)
|
||||||
|
if task_id in self.to_save:
|
||||||
|
del self.to_save[task_id]
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
callback(*args, **kwargs)
|
||||||
|
|
||||||
|
def create_delays(self):
|
||||||
|
"""Create the delayed tasks for the persistent tasks.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
This method should be automatically called when Evennia starts.
|
||||||
|
|
||||||
|
"""
|
||||||
|
now = datetime.now()
|
||||||
|
for task_id, (date, callbac, args, kwargs) in self.tasks.items():
|
||||||
|
seconds = max(0, (date - now).total_seconds())
|
||||||
|
task.deferLater(reactor, seconds, self.do_task, task_id)
|
||||||
|
|
||||||
|
|
||||||
|
# Create the soft singleton
|
||||||
|
TASK_HANDLER = TaskHandler()
|
||||||
|
|
||||||
|
|
@ -161,7 +161,7 @@ ERROR_SETTINGS = \
|
||||||
1) You are not running this command from your game directory.
|
1) You are not running this command from your game directory.
|
||||||
Change directory to your game directory and try again (or
|
Change directory to your game directory and try again (or
|
||||||
create a new game directory using evennia --init <dirname>)
|
create a new game directory using evennia --init <dirname>)
|
||||||
2) The ettings file contains a syntax error. If you see a
|
2) The settings file contains a syntax error. If you see a
|
||||||
traceback above, review it, resolve the problem and try again.
|
traceback above, review it, resolve the problem and try again.
|
||||||
3) Django is not correctly installed. This usually shows as
|
3) Django is not correctly installed. This usually shows as
|
||||||
errors mentioning 'DJANGO_SETTINGS_MODULE'. If you run a
|
errors mentioning 'DJANGO_SETTINGS_MODULE'. If you run a
|
||||||
|
|
@ -315,7 +315,7 @@ ERROR_LOGDIR_MISSING = \
|
||||||
will be created automatically).
|
will be created automatically).
|
||||||
|
|
||||||
(Explanation: Evennia creates the log directory automatically when
|
(Explanation: Evennia creates the log directory automatically when
|
||||||
initializating a new game directory. This error usually happens if
|
initializing a new game directory. This error usually happens if
|
||||||
you used git to clone a pre-created game directory - since log
|
you used git to clone a pre-created game directory - since log
|
||||||
files are in .gitignore they will not be cloned, which leads to
|
files are in .gitignore they will not be cloned, which leads to
|
||||||
the log directory also not being created.)
|
the log directory also not being created.)
|
||||||
|
|
@ -1329,7 +1329,7 @@ def main():
|
||||||
arg, value = [p.strip() for p in arg.split("=", 1)]
|
arg, value = [p.strip() for p in arg.split("=", 1)]
|
||||||
else:
|
else:
|
||||||
value = True
|
value = True
|
||||||
kwargs[arg.lstrip("--")] = [value]
|
kwargs[arg.lstrip("--")] = value
|
||||||
else:
|
else:
|
||||||
args.append(arg)
|
args.append(arg)
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
This runner is controlled by the evennia launcher and should normally
|
This runner is controlled by the evennia launcher and should normally
|
||||||
not be launched directly. It manages the two main Evennia processes
|
not be launched directly. It manages the two main Evennia processes
|
||||||
(Server and Portal) and most importanly runs a passive, threaded loop
|
(Server and Portal) and most importantly runs a passive, threaded loop
|
||||||
that makes sure to restart Server whenever it shuts down.
|
that makes sure to restart Server whenever it shuts down.
|
||||||
|
|
||||||
Since twistd does not allow for returning an optional exit code we
|
Since twistd does not allow for returning an optional exit code we
|
||||||
|
|
@ -137,7 +137,7 @@ def cycle_logfile(logfile):
|
||||||
|
|
||||||
def start_services(server_argv, portal_argv, doexit=False):
|
def start_services(server_argv, portal_argv, doexit=False):
|
||||||
"""
|
"""
|
||||||
This calls a threaded loop that launces the Portal and Server
|
This calls a threaded loop that launches the Portal and Server
|
||||||
and then restarts them when they finish.
|
and then restarts them when they finish.
|
||||||
"""
|
"""
|
||||||
global SERVER, PORTAL
|
global SERVER, PORTAL
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ IRC_COLOR_MAP = dict([
|
||||||
(r'|_', " "), # space
|
(r'|_', " "), # space
|
||||||
(r'|*', ""), # invert
|
(r'|*', ""), # invert
|
||||||
(r'|^', ""), # blinking text
|
(r'|^', ""), # blinking text
|
||||||
|
(r'|h', IRC_BOLD), # highlight, use bold instead
|
||||||
|
|
||||||
(r'|r', IRC_COLOR + IRC_RED),
|
(r'|r', IRC_COLOR + IRC_RED),
|
||||||
(r'|g', IRC_COLOR + IRC_GREEN),
|
(r'|g', IRC_COLOR + IRC_GREEN),
|
||||||
|
|
|
||||||
|
|
@ -455,6 +455,11 @@ class Evennia(object):
|
||||||
# (this also starts any that didn't yet start)
|
# (this also starts any that didn't yet start)
|
||||||
ScriptDB.objects.validate(init_mode=mode)
|
ScriptDB.objects.validate(init_mode=mode)
|
||||||
|
|
||||||
|
# start the task handler
|
||||||
|
from evennia.scripts.taskhandler import TASK_HANDLER
|
||||||
|
TASK_HANDLER.load()
|
||||||
|
TASK_HANDLER.create_delays()
|
||||||
|
|
||||||
# delete the temporary setting
|
# delete the temporary setting
|
||||||
ServerConfig.objects.conf("server_restart_mode", delete=True)
|
ServerConfig.objects.conf("server_restart_mode", delete=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,12 @@ class SessionHandler(dict):
|
||||||
if _INLINEFUNC_ENABLED and not raw and isinstance(self, ServerSessionHandler):
|
if _INLINEFUNC_ENABLED and not raw and isinstance(self, ServerSessionHandler):
|
||||||
# only parse inlinefuncs on the outgoing path (sessionhandler->)
|
# only parse inlinefuncs on the outgoing path (sessionhandler->)
|
||||||
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)
|
data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)
|
||||||
return data
|
# At this point the object is certainly the right encoding, but may still be a unicode object--
|
||||||
|
# to_str does not actually force objects to become bytestrings.
|
||||||
|
# If the unicode object is a subclass of unicode, such as ANSIString, this can cause a problem,
|
||||||
|
# as special behavior for that class will still be in play. Since we're now transferring raw data,
|
||||||
|
# we must now force this to be a proper bytestring.
|
||||||
|
return str(data)
|
||||||
elif hasattr(data, "id") and hasattr(data, "db_date_created") \
|
elif hasattr(data, "id") and hasattr(data, "db_date_created") \
|
||||||
and hasattr(data, '__dbclass__'):
|
and hasattr(data, '__dbclass__'):
|
||||||
# convert database-object to their string representation.
|
# convert database-object to their string representation.
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ AMP_INTERFACE = '127.0.0.1'
|
||||||
EVENNIA_DIR = os.path.dirname(os.path.abspath(__file__))
|
EVENNIA_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
# Path to the game directory (containing the server/conf/settings.py file)
|
# Path to the game directory (containing the server/conf/settings.py file)
|
||||||
# This is dynamically created- there is generally no need to change this!
|
# This is dynamically created- there is generally no need to change this!
|
||||||
if sys.argv[1] == 'test' if len(sys.argv)>1 else False:
|
if sys.argv[1] == 'test' if len(sys.argv) > 1 else False:
|
||||||
# unittesting mode
|
# unittesting mode
|
||||||
GAME_DIR = os.getcwd()
|
GAME_DIR = os.getcwd()
|
||||||
else:
|
else:
|
||||||
|
|
@ -208,14 +208,14 @@ MAX_CONNECTION_RATE = 2
|
||||||
# from the client! To turn the limiter off, set to <= 0.
|
# from the client! To turn the limiter off, set to <= 0.
|
||||||
MAX_COMMAND_RATE = 80
|
MAX_COMMAND_RATE = 80
|
||||||
# The warning to echo back to users if they send commands too fast
|
# The warning to echo back to users if they send commands too fast
|
||||||
COMMAND_RATE_WARNING ="You entered commands too fast. Wait a moment and try again."
|
COMMAND_RATE_WARNING = "You entered commands too fast. Wait a moment and try again."
|
||||||
# Determine how large of a string can be sent to the server in number
|
# Determine how large of a string can be sent to the server in number
|
||||||
# of characters. If they attempt to enter a string over this character
|
# of characters. If they attempt to enter a string over this character
|
||||||
# limit, we stop them and send a message. To make unlimited, set to
|
# limit, we stop them and send a message. To make unlimited, set to
|
||||||
# 0 or less.
|
# 0 or less.
|
||||||
MAX_CHAR_LIMIT = 6000
|
MAX_CHAR_LIMIT = 6000
|
||||||
# The warning to echo back to users if they enter a very large string
|
# The warning to echo back to users if they enter a very large string
|
||||||
MAX_CHAR_LIMIT_WARNING="You entered a string that was too long. Please break it up into multiple parts."
|
MAX_CHAR_LIMIT_WARNING = "You entered a string that was too long. Please break it up into multiple parts."
|
||||||
# If this is true, errors and tracebacks from the engine will be
|
# If this is true, errors and tracebacks from the engine will be
|
||||||
# echoed as text in-game as well as to the log. This can speed up
|
# echoed as text in-game as well as to the log. This can speed up
|
||||||
# debugging. OBS: Showing full tracebacks to regular users could be a
|
# debugging. OBS: Showing full tracebacks to regular users could be a
|
||||||
|
|
@ -381,6 +381,12 @@ CMDSET_CHARACTER = "commands.default_cmdsets.CharacterCmdSet"
|
||||||
CMDSET_ACCOUNT = "commands.default_cmdsets.AccountCmdSet"
|
CMDSET_ACCOUNT = "commands.default_cmdsets.AccountCmdSet"
|
||||||
# Location to search for cmdsets if full path not given
|
# Location to search for cmdsets if full path not given
|
||||||
CMDSET_PATHS = ["commands", "evennia", "contribs"]
|
CMDSET_PATHS = ["commands", "evennia", "contribs"]
|
||||||
|
# Fallbacks for cmdset paths that fail to load. Note that if you change the path for your default cmdsets,
|
||||||
|
# you will also need to copy CMDSET_FALLBACKS after your change in your settings file for it to detect the change.
|
||||||
|
CMDSET_FALLBACKS = {CMDSET_CHARACTER: 'evennia.commands.default.cmdset_character.CharacterCmdSet',
|
||||||
|
CMDSET_ACCOUNT: 'evennia.commands.default.cmdset_account.AccountCmdSet',
|
||||||
|
CMDSET_SESSION: 'evennia.commands.default.cmdset_session.SessionCmdSet',
|
||||||
|
CMDSET_UNLOGGEDIN: 'evennia.commands.default.cmdset_unloggedin.UnloggedinCmdSet'}
|
||||||
# Parent class for all default commands. Changing this class will
|
# Parent class for all default commands. Changing this class will
|
||||||
# modify all default commands, so do so carefully.
|
# modify all default commands, so do so carefully.
|
||||||
COMMAND_DEFAULT_CLASS = "evennia.commands.default.muxcommand.MuxCommand"
|
COMMAND_DEFAULT_CLASS = "evennia.commands.default.muxcommand.MuxCommand"
|
||||||
|
|
@ -527,8 +533,12 @@ PERMISSION_ACCOUNT_DEFAULT = "Player"
|
||||||
# Default sizes for client window (in number of characters), if client
|
# Default sizes for client window (in number of characters), if client
|
||||||
# is not supplying this on its own
|
# is not supplying this on its own
|
||||||
CLIENT_DEFAULT_WIDTH = 78
|
CLIENT_DEFAULT_WIDTH = 78
|
||||||
CLIENT_DEFAULT_HEIGHT = 45 # telnet standard is 24 but does anyone use such
|
# telnet standard height is 24; does anyone use such low-res displays anymore?
|
||||||
# low-res displays anymore?
|
CLIENT_DEFAULT_HEIGHT = 45
|
||||||
|
# Help output from CmdHelp are wrapped in an EvMore call
|
||||||
|
# (excluding webclient with separate help popups). If continuous scroll
|
||||||
|
# is preferred, change 'HELP_MORE' to False. EvMORE uses CLIENT_DEFAULT_HEIGHT
|
||||||
|
HELP_MORE = True
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Guest accounts
|
# Guest accounts
|
||||||
|
|
@ -602,8 +612,8 @@ IRC_ENABLED = False
|
||||||
# active. OBS: RSS support requires the python-feedparser package to
|
# active. OBS: RSS support requires the python-feedparser package to
|
||||||
# be installed (through package manager or from the website
|
# be installed (through package manager or from the website
|
||||||
# http://code.google.com/p/feedparser/)
|
# http://code.google.com/p/feedparser/)
|
||||||
RSS_ENABLED=False
|
RSS_ENABLED = False
|
||||||
RSS_UPDATE_INTERVAL = 60*10 # 10 minutes
|
RSS_UPDATE_INTERVAL = 60*10 # 10 minutes
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Django web features
|
# Django web features
|
||||||
|
|
@ -619,7 +629,7 @@ DEBUG = False
|
||||||
TEMPLATE_DEBUG = DEBUG
|
TEMPLATE_DEBUG = DEBUG
|
||||||
# Emails are sent to these people if the above DEBUG value is False. If you'd
|
# Emails are sent to these people if the above DEBUG value is False. If you'd
|
||||||
# rather prefer nobody receives emails, leave this commented out or empty.
|
# rather prefer nobody receives emails, leave this commented out or empty.
|
||||||
ADMINS = () #'Your Name', 'your_email@domain.com'),)
|
ADMINS = () # 'Your Name', 'your_email@domain.com'),)
|
||||||
# These guys get broken link notifications when SEND_BROKEN_LINK_EMAILS is True.
|
# These guys get broken link notifications when SEND_BROKEN_LINK_EMAILS is True.
|
||||||
MANAGERS = ADMINS
|
MANAGERS = ADMINS
|
||||||
# Absolute path to the directory that holds file uploads from web apps.
|
# Absolute path to the directory that holds file uploads from web apps.
|
||||||
|
|
@ -680,13 +690,13 @@ WEBSITE_TEMPLATE = 'website'
|
||||||
WEBCLIENT_TEMPLATE = 'webclient'
|
WEBCLIENT_TEMPLATE = 'webclient'
|
||||||
# The default options used by the webclient
|
# The default options used by the webclient
|
||||||
WEBCLIENT_OPTIONS = {
|
WEBCLIENT_OPTIONS = {
|
||||||
"gagprompt": True, # Gags prompt from the output window and keep them
|
"gagprompt": True, # Gags prompt from the output window and keep them
|
||||||
# together with the input bar
|
# together with the input bar
|
||||||
"helppopup": True, # Shows help files in a new popup window
|
"helppopup": True, # Shows help files in a new popup window
|
||||||
"notification_popup": False, # Shows notifications of new messages as
|
"notification_popup": False, # Shows notifications of new messages as
|
||||||
# popup windows
|
# popup windows
|
||||||
"notification_sound": False # Plays a sound for notifications of new
|
"notification_sound": False # Plays a sound for notifications of new
|
||||||
# messages
|
# messages
|
||||||
}
|
}
|
||||||
|
|
||||||
# We setup the location of the website template as well as the admin site.
|
# We setup the location of the website template as well as the admin site.
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ class ANSIParser(object):
|
||||||
We also allow to escape colour codes
|
We also allow to escape colour codes
|
||||||
by prepending with a \ for xterm256,
|
by prepending with a \ for xterm256,
|
||||||
an extra | for Merc-style codes
|
an extra | for Merc-style codes
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Mapping using {r {n etc
|
# Mapping using {r {n etc
|
||||||
|
|
@ -508,6 +509,7 @@ def strip_raw_ansi(string, parser=ANSI_PARSER):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
string (str): the stripped string.
|
string (str): the stripped string.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return parser.strip_raw_codes(string)
|
return parser.strip_raw_codes(string)
|
||||||
|
|
||||||
|
|
@ -524,13 +526,6 @@ def raw(string):
|
||||||
return string.replace('{', '{{').replace('|', '||')
|
return string.replace('{', '{{').replace('|', '||')
|
||||||
|
|
||||||
|
|
||||||
def group(lst, n):
|
|
||||||
for i in range(0, len(lst), n):
|
|
||||||
val = lst[i:i+n]
|
|
||||||
if len(val) == n:
|
|
||||||
yield tuple(val)
|
|
||||||
|
|
||||||
|
|
||||||
def _spacing_preflight(func):
|
def _spacing_preflight(func):
|
||||||
"""
|
"""
|
||||||
This wrapper function is used to do some preflight checks on
|
This wrapper function is used to do some preflight checks on
|
||||||
|
|
@ -544,10 +539,10 @@ def _spacing_preflight(func):
|
||||||
raise TypeError("must be char, not %s" % type(fillchar))
|
raise TypeError("must be char, not %s" % type(fillchar))
|
||||||
if not isinstance(width, int):
|
if not isinstance(width, int):
|
||||||
raise TypeError("integer argument expected, got %s" % type(width))
|
raise TypeError("integer argument expected, got %s" % type(width))
|
||||||
difference = width - len(self)
|
_difference = width - len(self)
|
||||||
if difference <= 0:
|
if _difference <= 0:
|
||||||
return self
|
return self
|
||||||
return func(self, width, fillchar, difference)
|
return func(self, width, fillchar, _difference)
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -634,20 +629,30 @@ class ANSIMeta(type):
|
||||||
|
|
||||||
class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
"""
|
"""
|
||||||
String-like object that is aware of ANSI codes.
|
Unicode-like object that is aware of ANSI codes.
|
||||||
|
|
||||||
This isn't especially efficient, as it doesn't really have an
|
This class can be used nearly identically to unicode, in that it will
|
||||||
|
report string length, handle slices, etc, much like a unicode or
|
||||||
|
string object would. The methods should be used identically as unicode
|
||||||
|
methods are.
|
||||||
|
|
||||||
|
There is at least one exception to this (and there may be more, though
|
||||||
|
they have not come up yet). When using ''.join() or u''.join() on an
|
||||||
|
ANSIString, color information will get lost. You must use
|
||||||
|
ANSIString('').join() to preserve color information.
|
||||||
|
|
||||||
|
This implementation isn't perfectly clean, as it doesn't really have an
|
||||||
understanding of what the codes mean in order to eliminate
|
understanding of what the codes mean in order to eliminate
|
||||||
redundant characters. This could be made as an enhancement to ANSI_PARSER.
|
redundant characters-- though cleaning up the strings might end up being
|
||||||
|
inefficient and slow without some C code when dealing with larger values.
|
||||||
|
Such enhancements could be made as an enhancement to ANSI_PARSER
|
||||||
|
if needed, however.
|
||||||
|
|
||||||
If one is going to use ANSIString, one should generally avoid converting
|
If one is going to use ANSIString, one should generally avoid converting
|
||||||
away from it until one is about to send information on the wire. This is
|
away from it until one is about to send information on the wire. This is
|
||||||
because escape sequences in the string may otherwise already be decoded,
|
because escape sequences in the string may otherwise already be decoded,
|
||||||
and taken literally the second time around.
|
and taken literally the second time around.
|
||||||
|
|
||||||
Please refer to the Metaclass, ANSIMeta, which is used to apply wrappers
|
|
||||||
for several of the methods that need not be defined directly here.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
|
|
@ -895,6 +900,9 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
"""
|
"""
|
||||||
Return a unicode object without the ANSI escapes.
|
Return a unicode object without the ANSI escapes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
clean_string (unicode): A unicode object with no ANSI escapes.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._clean_string
|
return self._clean_string
|
||||||
|
|
||||||
|
|
@ -902,20 +910,31 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
"""
|
"""
|
||||||
Return a unicode object with the ANSI escapes.
|
Return a unicode object with the ANSI escapes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
raw (unicode): A unicode object with the raw ANSI escape sequences.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._raw_string
|
return self._raw_string
|
||||||
|
|
||||||
def partition(self, sep, reverse=False):
|
def partition(self, sep, reverse=False):
|
||||||
"""
|
"""
|
||||||
Similar to split, but always creates a tuple with three items:
|
Splits once into three sections (with the separator being the middle section)
|
||||||
|
|
||||||
1. The part before the separator
|
|
||||||
2. The separator itself.
|
|
||||||
3. The part after.
|
|
||||||
|
|
||||||
We use the same techniques we used in split() to make sure each are
|
We use the same techniques we used in split() to make sure each are
|
||||||
colored.
|
colored.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sep (str): The separator to split the string on.
|
||||||
|
reverse (boolean): Whether to split the string on the last
|
||||||
|
occurrence of the separator rather than the first.
|
||||||
|
Returns:
|
||||||
|
result (tuple):
|
||||||
|
prefix (ANSIString): The part of the string before the
|
||||||
|
separator
|
||||||
|
sep (ANSIString): The separator itself
|
||||||
|
postfix (ANSIString): The part of the string after the
|
||||||
|
separator.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if hasattr(sep, '_clean_string'):
|
if hasattr(sep, '_clean_string'):
|
||||||
sep = sep.clean()
|
sep = sep.clean()
|
||||||
|
|
@ -1005,12 +1024,26 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
|
|
||||||
def split(self, by=None, maxsplit=-1):
|
def split(self, by=None, maxsplit=-1):
|
||||||
"""
|
"""
|
||||||
|
Splits a string based on a separator.
|
||||||
|
|
||||||
Stolen from PyPy's pure Python string implementation, tweaked for
|
Stolen from PyPy's pure Python string implementation, tweaked for
|
||||||
ANSIString.
|
ANSIString.
|
||||||
|
|
||||||
PyPy is distributed under the MIT licence.
|
PyPy is distributed under the MIT licence.
|
||||||
http://opensource.org/licenses/MIT
|
http://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
Args:
|
||||||
|
by (str): A string to search for which will be used to split
|
||||||
|
the string. For instance, ',' for 'Hello,world' would
|
||||||
|
result in ['Hello', 'world']
|
||||||
|
maxsplit (int): The maximum number of times to split the string.
|
||||||
|
For example, a maxsplit of 2 with a by of ',' on the string
|
||||||
|
'Hello,world,test,string' would result in
|
||||||
|
['Hello', 'world', 'test,string']
|
||||||
|
Returns:
|
||||||
|
result (list of ANSIStrings): A list of ANSIStrings derived from
|
||||||
|
this string.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
drop_spaces = by is None
|
drop_spaces = by is None
|
||||||
if drop_spaces:
|
if drop_spaces:
|
||||||
|
|
@ -1038,12 +1071,27 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
|
|
||||||
def rsplit(self, by=None, maxsplit=-1):
|
def rsplit(self, by=None, maxsplit=-1):
|
||||||
"""
|
"""
|
||||||
|
Like split, but starts from the end of the string rather than the
|
||||||
|
beginning.
|
||||||
|
|
||||||
Stolen from PyPy's pure Python string implementation, tweaked for
|
Stolen from PyPy's pure Python string implementation, tweaked for
|
||||||
ANSIString.
|
ANSIString.
|
||||||
|
|
||||||
PyPy is distributed under the MIT licence.
|
PyPy is distributed under the MIT licence.
|
||||||
http://opensource.org/licenses/MIT
|
http://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
Args:
|
||||||
|
by (str): A string to search for which will be used to split
|
||||||
|
the string. For instance, ',' for 'Hello,world' would
|
||||||
|
result in ['Hello', 'world']
|
||||||
|
maxsplit (int): The maximum number of times to split the string.
|
||||||
|
For example, a maxsplit of 2 with a by of ',' on the string
|
||||||
|
'Hello,world,test,string' would result in
|
||||||
|
['Hello,world', 'test', 'string']
|
||||||
|
Returns:
|
||||||
|
result (list of ANSIStrings): A list of ANSIStrings derived from
|
||||||
|
this string.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
res = []
|
res = []
|
||||||
end = len(self)
|
end = len(self)
|
||||||
|
|
@ -1072,6 +1120,15 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
def strip(self, chars=None):
|
def strip(self, chars=None):
|
||||||
"""
|
"""
|
||||||
Strip from both ends, taking ANSI markers into account.
|
Strip from both ends, taking ANSI markers into account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chars (str, optional): A string containing individual characters
|
||||||
|
to strip off of both ends of the string. By default, any blank
|
||||||
|
spaces are trimmed.
|
||||||
|
Returns:
|
||||||
|
result (ANSIString): A new ANSIString with the ends trimmed of the
|
||||||
|
relevant characters.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
clean = self._clean_string
|
clean = self._clean_string
|
||||||
raw = self._raw_string
|
raw = self._raw_string
|
||||||
|
|
@ -1109,6 +1166,15 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
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.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chars (str, optional): A string containing individual characters
|
||||||
|
to strip off of the left end of the string. By default, any
|
||||||
|
blank spaces are trimmed.
|
||||||
|
Returns:
|
||||||
|
result (ANSIString): A new ANSIString with the left end trimmed of
|
||||||
|
the relevant characters.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
clean = self._clean_string
|
clean = self._clean_string
|
||||||
raw = self._raw_string
|
raw = self._raw_string
|
||||||
|
|
@ -1133,6 +1199,15 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
def rstrip(self, chars=None):
|
def rstrip(self, chars=None):
|
||||||
"""
|
"""
|
||||||
Strip from the right, taking ANSI markers into account.
|
Strip from the right, taking ANSI markers into account.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chars (str, optional): A string containing individual characters
|
||||||
|
to strip off of the right end of the string. By default, any
|
||||||
|
blank spaces are trimmed.
|
||||||
|
Returns:
|
||||||
|
result (ANSIString): A new ANSIString with the right end trimmed of
|
||||||
|
the relevant characters.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
clean = self._clean_string
|
clean = self._clean_string
|
||||||
raw = self._raw_string
|
raw = self._raw_string
|
||||||
|
|
@ -1153,7 +1228,22 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
|
|
||||||
def join(self, iterable):
|
def join(self, iterable):
|
||||||
"""
|
"""
|
||||||
Joins together strings in an iterable.
|
Joins together strings in an iterable, using this string between each
|
||||||
|
one.
|
||||||
|
|
||||||
|
NOTE: This should always be used for joining strings when ANSIStrings
|
||||||
|
are involved. Otherwise color information will be discarded by
|
||||||
|
python, due to details in the C implementation of unicode strings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
iterable (list of strings): A list of strings to join together
|
||||||
|
Returns:
|
||||||
|
result (ANSIString): A single string with all of the iterable's
|
||||||
|
contents concatenated, with this string between each. For
|
||||||
|
example:
|
||||||
|
ANSIString(', ').join(['up', 'right', 'left', 'down'])
|
||||||
|
...Would return:
|
||||||
|
ANSIString('up, right, left, down')
|
||||||
|
|
||||||
"""
|
"""
|
||||||
result = ANSIString('')
|
result = ANSIString('')
|
||||||
|
|
@ -1195,30 +1285,53 @@ class ANSIString(with_metaclass(ANSIMeta, unicode)):
|
||||||
raw_string, clean_string=line, char_indexes=char_indexes,
|
raw_string, clean_string=line, char_indexes=char_indexes,
|
||||||
code_indexes=code_indexes)
|
code_indexes=code_indexes)
|
||||||
|
|
||||||
|
# The following methods should not be called with the '_difference' argument explicitly. This is
|
||||||
|
# data provided by the wrapper _spacing_preflight.
|
||||||
@_spacing_preflight
|
@_spacing_preflight
|
||||||
def center(self, width, fillchar, difference):
|
def center(self, width, fillchar, _difference):
|
||||||
"""
|
"""
|
||||||
Center some text with some spaces padding both sides.
|
Center some text with some spaces padding both sides.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
width (int): The target width of the output string.
|
||||||
|
fillchar (str): A single character string to pad the output string
|
||||||
|
with.
|
||||||
|
Returns:
|
||||||
|
result (ANSIString): A string padded on both ends with fillchar.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
remainder = difference % 2
|
remainder = _difference % 2
|
||||||
difference /= 2
|
_difference /= 2
|
||||||
spacing = self._filler(fillchar, difference)
|
spacing = self._filler(fillchar, _difference)
|
||||||
result = spacing + self + spacing + self._filler(fillchar, remainder)
|
result = spacing + self + spacing + self._filler(fillchar, remainder)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@_spacing_preflight
|
@_spacing_preflight
|
||||||
def ljust(self, width, fillchar, difference):
|
def ljust(self, width, fillchar, _difference):
|
||||||
"""
|
"""
|
||||||
Left justify some text.
|
Left justify some text.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
width (int): The target width of the output string.
|
||||||
|
fillchar (str): A single character string to pad the output string
|
||||||
|
with.
|
||||||
|
Returns:
|
||||||
|
result (ANSIString): A string padded on the right with fillchar.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self + self._filler(fillchar, difference)
|
return self + self._filler(fillchar, _difference)
|
||||||
|
|
||||||
@_spacing_preflight
|
@_spacing_preflight
|
||||||
def rjust(self, width, fillchar, difference):
|
def rjust(self, width, fillchar, _difference):
|
||||||
"""
|
"""
|
||||||
Right justify some text.
|
Right justify some text.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
width (int): The target width of the output string.
|
||||||
|
fillchar (str): A single character string to pad the output string
|
||||||
|
with.
|
||||||
|
Returns:
|
||||||
|
result (ANSIString): A string padded on the left with fillchar.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._filler(fillchar, difference) + self
|
return self._filler(fillchar, _difference) + self
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,8 @@ class EvMenu(object):
|
||||||
self.helptext = _HELP_NO_OPTIONS if self.auto_quit else _HELP_NO_OPTIONS_NO_QUIT
|
self.helptext = _HELP_NO_OPTIONS if self.auto_quit else _HELP_NO_OPTIONS_NO_QUIT
|
||||||
|
|
||||||
self.display_nodetext()
|
self.display_nodetext()
|
||||||
|
if not options:
|
||||||
|
self.close_menu()
|
||||||
|
|
||||||
def close_menu(self):
|
def close_menu(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,6 @@ _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.
|
||||||
|
|
@ -920,6 +919,8 @@ def uses_database(name="sqlite3"):
|
||||||
return engine == "django.db.backends.%s" % name
|
return engine == "django.db.backends.%s" % name
|
||||||
|
|
||||||
|
|
||||||
|
_TASK_HANDLER = None
|
||||||
|
|
||||||
def delay(timedelay, callback, *args, **kwargs):
|
def delay(timedelay, callback, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Delay the return of a value.
|
Delay the return of a value.
|
||||||
|
|
@ -930,7 +931,9 @@ def delay(timedelay, callback, *args, **kwargs):
|
||||||
arguments after `timedelay` seconds.
|
arguments after `timedelay` seconds.
|
||||||
args (any, optional): Will be used as arguments to callback
|
args (any, optional): Will be used as arguments to callback
|
||||||
Kwargs:
|
Kwargs:
|
||||||
any (any): Will be used to call the callback.
|
persistent (bool, optional): should make the delay persistent
|
||||||
|
over a reboot or reload
|
||||||
|
any (any): Will be used to call the callback.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
deferred (deferred): Will fire fire with callback after
|
deferred (deferred): Will fire fire with callback after
|
||||||
|
|
@ -939,8 +942,21 @@ def delay(timedelay, callback, *args, **kwargs):
|
||||||
defined directly in the command body and don't need to be
|
defined directly in the command body and don't need to be
|
||||||
specified here.
|
specified here.
|
||||||
|
|
||||||
|
Note:
|
||||||
|
The task handler (`evennia.scripts.taskhandler.TASK_HANDLEr`) will
|
||||||
|
be called for persistent or non-persistent tasks.
|
||||||
|
If persistent is set to True, the callback, its arguments
|
||||||
|
and other keyword arguments will be saved in the database,
|
||||||
|
assuming they can be. The callback will be executed even after
|
||||||
|
a server restart/reload, taking into account the specified delay
|
||||||
|
(and server down time).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return task.deferLater(reactor, timedelay, callback, *args, **kwargs)
|
global _TASK_HANDLER
|
||||||
|
# Do some imports here to avoid circular import and speed things up
|
||||||
|
if _TASK_HANDLER is None:
|
||||||
|
from evennia.scripts.taskhandler import TASK_HANDLER as _TASK_HANDLER
|
||||||
|
return _TASK_HANDLER.add(timedelay, callback, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
_TYPECLASSMODELS = None
|
_TYPECLASSMODELS = None
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue