mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Migrate! Add nick-templating system, first version. This allows arg templating of nicks to allow a user to completely redefine how they enter commands. Still some bugs.
This commit is contained in:
parent
893cffc3e1
commit
b1de659e8b
3 changed files with 297 additions and 63 deletions
|
|
@ -2,7 +2,8 @@
|
|||
General Character commands usually availabe to all characters
|
||||
"""
|
||||
from django.conf import settings
|
||||
from evennia.utils import utils, prettytable
|
||||
from evennia.utils import utils, evtable
|
||||
from evennia.typeclasses.attributes import NickTemplateInvalid
|
||||
|
||||
COMMAND_DEFAULT_CLASS = utils.class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||
|
||||
|
|
@ -75,33 +76,39 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
|||
define a personal alias/nick
|
||||
|
||||
Usage:
|
||||
nick[/switches] <nickname> [= [<string>]]
|
||||
alias ''
|
||||
nick[/switches] <string> [= [replacement_string]]
|
||||
nick[/switches] <template> = <replacement_template>
|
||||
nick/delete <string> or number
|
||||
nick/test <test string>
|
||||
|
||||
Switches:
|
||||
object - alias an object
|
||||
player - alias a player
|
||||
clearall - clear all your aliases
|
||||
list - show all defined aliases (also "nicks" works)
|
||||
inputline - replace on the inputline (default)
|
||||
object - replace on object-lookup
|
||||
player - replace on player-lookup
|
||||
delete - remove nick by number of by index given by /list
|
||||
clearall - clear all nicks
|
||||
list - show all defined aliases (also "nicks" works)
|
||||
test - test input to see what it matches with
|
||||
|
||||
Examples:
|
||||
nick hi = say Hello, I'm Sarah!
|
||||
nick/object tom = the tall man
|
||||
nick hi (shows the nick substitution)
|
||||
nick hi = (removes nick 'hi')
|
||||
nick build $1 $2 = @create/drop $1;$2 - (template)
|
||||
nick tell $1 $2=@page $1=$2 - (template)
|
||||
|
||||
A 'nick' is a personal shortcut you create for your own use. When
|
||||
you enter the nick, the alternative string will be sent instead.
|
||||
The switches control in which situations the substitution will
|
||||
happen. The default is that it will happen when you enter a
|
||||
command. The 'object' and 'player' nick-types kick in only when
|
||||
you use commands that requires an object or player as a target -
|
||||
you can then use the nick to refer to them.
|
||||
A 'nick' is a personal string replacement. Use $1, $2, ... to catch arguments.
|
||||
Put the last $-marker without an ending space to catch all remaining text. You
|
||||
can also use unix-glob matching:
|
||||
|
||||
* - matches everything
|
||||
? - matches a single character
|
||||
[seq] - matches all chars in sequence
|
||||
[!seq] - matches everything not in sequence
|
||||
|
||||
Note that no objects are actually renamed or changed by this command - your nicks
|
||||
are only available to you. If you want to permanently add keywords to an object
|
||||
for everyone to use, you need build privileges and the @alias command.
|
||||
|
||||
Note that no objects are actually renamed or changed by this
|
||||
command - the nick is only available to you. If you want to
|
||||
permanently add keywords to an object for everyone to use, you
|
||||
need build privileges and to use the @alias command.
|
||||
"""
|
||||
key = "nick"
|
||||
aliases = ["nickname", "nicks", "@nick", "alias"]
|
||||
|
|
@ -112,61 +119,79 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
|||
|
||||
caller = self.caller
|
||||
switches = self.switches
|
||||
nicksinputline = caller.nicks.get(category="inputline", return_obj=True)
|
||||
nicksobjects = caller.nicks.get(category="object", return_obj=True)
|
||||
nicksplayers = caller.nicks.get(category="player", return_obj=True)
|
||||
nicktypes = [switch for switch in switches if switch in ("object", "player", "inputline")] or ["inputline"]
|
||||
|
||||
nicklist = utils.make_iter(caller.nicks.get(return_obj=True) or [])
|
||||
|
||||
if 'list' in switches:
|
||||
if not nicksinputline and not nicksobjects and not nicksplayers:
|
||||
|
||||
if not nicklist:
|
||||
string = "{wNo nicks defined.{n"
|
||||
else:
|
||||
table = prettytable.PrettyTable(["{wNickType",
|
||||
"{wNickname",
|
||||
"{wTranslates-to"])
|
||||
for nicks in (nicksinputline, nicksobjects, nicksplayers):
|
||||
for nick in utils.make_iter(nicks):
|
||||
table.add_row([nick.db_category, nick.db_key, nick.db_strvalue])
|
||||
table = evtable.EvTable("#", "Type", "Nick match", "Replacement")
|
||||
for inum, nickobj in enumerate(nicklist):
|
||||
_, _, nickvalue, replacement = nickobj.value
|
||||
table.add_row(str(inum + 1), nickobj.db_category, nickvalue, replacement)
|
||||
string = "{wDefined Nicks:{n\n%s" % table
|
||||
caller.msg(string)
|
||||
return
|
||||
|
||||
if 'clearall' in switches:
|
||||
caller.nicks.clear()
|
||||
caller.msg("Cleared all aliases.")
|
||||
caller.msg("Cleared all nicks.")
|
||||
return
|
||||
|
||||
if not self.args or not self.lhs:
|
||||
caller.msg("Usage: nick[/switches] nickname = [realname]")
|
||||
return
|
||||
nick = self.lhs
|
||||
real = self.rhs
|
||||
|
||||
if real == nick:
|
||||
nickstring = self.lhs
|
||||
replstring = self.rhs
|
||||
|
||||
if replstring == nickstring:
|
||||
caller.msg("No point in setting nick same as the string to replace...")
|
||||
return
|
||||
|
||||
# check so we have a suitable nick type
|
||||
if not any(True for switch in switches if switch in ("object", "player", "inputline")):
|
||||
switches = ["inputline"]
|
||||
string = ""
|
||||
for switch in switches:
|
||||
oldnick = caller.nicks.get(key=nick, category=switch)
|
||||
if not real:
|
||||
if "=" in self.args:
|
||||
if oldnick:
|
||||
# clear the alias
|
||||
string += "\nNick cleared: '{w%s{n' (-> '{w%s{n')." % (nick, oldnick)
|
||||
caller.nicks.remove(nick, category=switch)
|
||||
else:
|
||||
string += "\nNo nick '%s' found, so it could not be removed." % nick
|
||||
for nicktype in nicktypes:
|
||||
oldnick = caller.nicks.get(key=nickstring, category=nicktype, return_obj=True)
|
||||
oldnick = oldnick if oldnick.key is not None else None
|
||||
if "delete" in switches or "del" in switches:
|
||||
if oldnick:
|
||||
_, _, nickstring, replstring = oldnick.value
|
||||
else:
|
||||
string += "\nNick: '{w%s{n'{n -> '{w%s{n'." % (nick, oldnick)
|
||||
# no old nick, see if a number was given
|
||||
if self.args.isdigit():
|
||||
# we are given a index in nicklist
|
||||
delindex = int(self.args)
|
||||
if 0 < delindex <= len(nicklist):
|
||||
oldnick = nicklist[delindex-1]
|
||||
_, _, nickstring, replstring = oldnick.value
|
||||
else:
|
||||
caller.msg("Not a valid nick index.")
|
||||
return
|
||||
else:
|
||||
caller.msg("Nick not found.")
|
||||
return
|
||||
# clear the nick
|
||||
string += "\nNick removed: '|w%s|n' -> |w%s|n." % (nickstring, replstring)
|
||||
caller.nicks.remove(nickstring, category=nicktype)
|
||||
|
||||
else:
|
||||
elif replstring:
|
||||
# creating new nick
|
||||
if oldnick:
|
||||
string += "\nNick '{w%s{n' reset from '{w%s{n' to '{w%s{n'." % (nick, oldnick, real)
|
||||
string += "\nNick '{w%s{n' updated to map to '{w%s{n'." % (nickstring, replstring)
|
||||
else:
|
||||
string += "\nNick set: '{w%s{n' -> '{w%s{n'." % (nick, real)
|
||||
caller.nicks.add(nick, real, category=switch)
|
||||
string += "\nNick '{w%s{n' mapped to '{w%s{n'." % (nickstring, replstring)
|
||||
try:
|
||||
caller.nicks.add(nickstring, replstring, category=nicktype)
|
||||
except NickTemplateInvalid:
|
||||
caller.msg("You must use the same $-markers both in the nick and in the replacement.")
|
||||
return
|
||||
else:
|
||||
# just looking at the nick
|
||||
string += "\nNick: '{w%s{n'{n -> '{w%s{n'." % (nickstring, oldnick.key)
|
||||
caller.msg(string)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -279,6 +279,7 @@ class AttributeHandler(object):
|
|||
class RetDefault(object):
|
||||
"Holds default values"
|
||||
def __init__(self):
|
||||
self.key = None
|
||||
self.value = default
|
||||
self.strvalue = str(default) if default is not None else None
|
||||
|
||||
|
|
@ -532,6 +533,103 @@ class AttributeHandler(object):
|
|||
return attrs
|
||||
|
||||
|
||||
# Nick templating
|
||||
#
|
||||
|
||||
"""
|
||||
This supports the use of replacement templates in nicks:
|
||||
|
||||
This happens in two steps:
|
||||
|
||||
1) The user supplies a template that is converted to a regex according
|
||||
to the unix-like templating language.
|
||||
2) This regex is tested against nicks depending on which nick replacement
|
||||
strategy is considered (most commonly inputline).
|
||||
3) If there is a template match and there are templating markers,
|
||||
these are replaced with the arguments actually given.
|
||||
|
||||
@desc $1 $2 $3
|
||||
|
||||
This will be converted to the following regex:
|
||||
|
||||
\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+)
|
||||
|
||||
Supported template markers (through fnmatch)
|
||||
* matches anything (non-greedy) -> .*?
|
||||
? matches any single character ->
|
||||
[seq] matches any entry in sequence
|
||||
[!seq] matches entries not in sequence
|
||||
Custom arg markers
|
||||
$N argument position (1-99)
|
||||
|
||||
"""
|
||||
import fnmatch
|
||||
_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)")
|
||||
_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)")
|
||||
_RE_NICK_SPACE = re.compile(r"\\ ")
|
||||
|
||||
|
||||
class NickTemplateInvalid(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def initialize_nick_templates(in_template, out_template):
|
||||
"""
|
||||
Initialize the nick templates for matching and remapping a string.
|
||||
|
||||
Args:
|
||||
in_template (str): The template to be used for nick recognition.
|
||||
out_template (str): The template to be used to replace the string
|
||||
matched by the in_template.
|
||||
|
||||
Returns:
|
||||
regex (regex): Regex to match against strings
|
||||
template (str): Template with markers {arg1}, {arg2}, etc for
|
||||
replacement using the standard .format method.
|
||||
|
||||
Raises:
|
||||
NickTemplateInvalid: If the in/out template does not have a matching
|
||||
number of $args.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# create the regex for in_template
|
||||
regex_string = fnmatch.translate(in_template)
|
||||
|
||||
# validate the templates
|
||||
regex_args = [match.group(2) for match in _RE_NICK_ARG.finditer(regex_string)]
|
||||
temp_args = [match.group(2) for match in _RE_NICK_TEMPLATE_ARG.finditer(out_template)]
|
||||
if set(regex_args) != set(temp_args):
|
||||
# We don't have the same $-tags in input/output.
|
||||
raise NickTemplateInvalid
|
||||
|
||||
regex_string = _RE_NICK_SPACE.sub("\s+", regex_string)
|
||||
regex_string = _RE_NICK_ARG.sub(lambda m: "(?P<arg%s>.+?)" % m.group(2), regex_string)
|
||||
template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template)
|
||||
|
||||
return regex_string, template_string
|
||||
|
||||
|
||||
def parse_nick_template(string, template_regex, outtemplate):
|
||||
"""
|
||||
Parse a text using a template and map it to another template
|
||||
|
||||
Args:
|
||||
string (str): The input string to processj
|
||||
template_regex (regex): A template regex created with
|
||||
initialize_nick_template.
|
||||
outtemplate (str): The template to which to map the matches
|
||||
produced by the template_regex. This should have $1, $2,
|
||||
etc to match the regex.
|
||||
|
||||
"""
|
||||
match = template_regex.match(string)
|
||||
if match:
|
||||
return True, outtemplate.format(**match.groupdict())
|
||||
return False, string
|
||||
|
||||
|
||||
class NickHandler(AttributeHandler):
|
||||
"""
|
||||
Handles the addition and removal of Nicks. Nicks are special
|
||||
|
|
@ -541,6 +639,10 @@ class NickHandler(AttributeHandler):
|
|||
"""
|
||||
_attrtype = "nick"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NickHandler, self).__init__(*args, **kwargs)
|
||||
self._regex_cache = {}
|
||||
|
||||
def has(self, key, category="inputline"):
|
||||
"""
|
||||
Args:
|
||||
|
|
@ -570,22 +672,23 @@ class NickHandler(AttributeHandler):
|
|||
kwargs (any, optional): These are passed on to `AttributeHandler.get`.
|
||||
|
||||
"""
|
||||
return super(NickHandler, self).get(key=key, category=category, strattr=True, **kwargs)
|
||||
return super(NickHandler, self).get(key=key, category=category, **kwargs)
|
||||
|
||||
def add(self, key, replacement, category="inputline", **kwargs):
|
||||
"""
|
||||
Add a new nick.
|
||||
|
||||
Args:
|
||||
key (str): A key for the nick to match for.
|
||||
replacement (str): The string to replace `key` with (the "nickname").
|
||||
key (str): A key (or template) for the nick to match for.
|
||||
replacement (str): The string (or template) to replace `key` with (the "nickname").
|
||||
category (str, optional): the category within which to
|
||||
retrieve the nick. The "inputline" means replacing data
|
||||
sent by the user.
|
||||
kwargs (any, optional): These are passed on to `AttributeHandler.get`.
|
||||
|
||||
"""
|
||||
super(NickHandler, self).add(key, replacement, category=category, strattr=True, **kwargs)
|
||||
nick_regex, nick_template = initialize_nick_templates(key, replacement)
|
||||
super(NickHandler, self).add(key, (nick_regex, nick_template, key, replacement), category=category, **kwargs)
|
||||
|
||||
def remove(self, key, category="inputline", **kwargs):
|
||||
"""
|
||||
|
|
@ -620,17 +723,27 @@ class NickHandler(AttributeHandler):
|
|||
their nick equivalents.
|
||||
|
||||
"""
|
||||
obj_nicks, player_nicks = [], []
|
||||
nicks = {}
|
||||
for category in make_iter(categories):
|
||||
obj_nicks.extend([n for n in make_iter(self.get(category=category, return_obj=True)) if n])
|
||||
nicks.update({nick.key: nick
|
||||
for nick in make_iter(self.get(category=category, return_obj=True)) if nick and nick.key})
|
||||
if include_player and self.obj.has_player:
|
||||
for category in make_iter(categories):
|
||||
player_nicks.extend([n for n in make_iter(self.obj.player.nicks.get(category=category, return_obj=True)) if n])
|
||||
for nick in obj_nicks + player_nicks:
|
||||
# make a case-insensitive match here
|
||||
match = re.match(re.escape(nick.db_key), raw_string, re.IGNORECASE)
|
||||
if match:
|
||||
raw_string = raw_string.replace(match.group(), nick.db_strvalue, 1)
|
||||
nicks.update({nick.key: nick
|
||||
for nick in make_iter(self.obj.player.nicks.get(category=category, return_obj=True))
|
||||
if nick and nick.key})
|
||||
for key, nick in nicks.iteritems():
|
||||
print "iterting over nicks:", key
|
||||
nick_regex, template, _, _ = nick.value
|
||||
regex = self._regex_cache.get(nick_regex)
|
||||
if not regex:
|
||||
regex = re.compile(nick_regex, re.I + re.DOTALL)
|
||||
self._regex_cache[nick_regex] = regex
|
||||
|
||||
print "before parse_nick_template:", nick.value
|
||||
is_match, raw_string = parse_nick_template(raw_string, regex, template)
|
||||
print "is_match:", is_match, raw_string
|
||||
if is_match:
|
||||
break
|
||||
return raw_string
|
||||
|
||||
|
|
|
|||
|
|
@ -356,3 +356,99 @@ def parse_inlinefunc(string, strip=False, **kwargs):
|
|||
# execute the stack from the cache
|
||||
return "".join(_run_stack(item) for item in _PARSING_CACHE[string])
|
||||
|
||||
# Nick templating
|
||||
#
|
||||
|
||||
"""
|
||||
This supports the use of replacement templates in nicks:
|
||||
|
||||
This happens in two steps:
|
||||
|
||||
1) The user supplies a template that is converted to a regex according
|
||||
to the unix-like templating language.
|
||||
2) This regex is tested against nicks depending on which nick replacement
|
||||
strategy is considered (most commonly inputline).
|
||||
3) If there is a template match and there are templating markers,
|
||||
these are replaced with the arguments actually given.
|
||||
|
||||
@desc $1 $2 $3
|
||||
|
||||
This will be converted to the following regex:
|
||||
|
||||
\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+)
|
||||
|
||||
Supported template markers (through fnmatch)
|
||||
* matches anything (non-greedy) -> .*?
|
||||
? matches any single character ->
|
||||
[seq] matches any entry in sequence
|
||||
[!seq] matches entries not in sequence
|
||||
Custom arg markers
|
||||
$N argument position (1-99)
|
||||
|
||||
"""
|
||||
import fnmatch
|
||||
_RE_NICK_ARG = re.compile(r"\\(\$)([1-9][0-9]?)")
|
||||
_RE_NICK_TEMPLATE_ARG = re.compile(r"(\$)([1-9][0-9]?)")
|
||||
_RE_NICK_SPACE = re.compile(r"\\ ")
|
||||
|
||||
|
||||
class NickTemplateInvalid(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def initialize_nick_templates(in_template, out_template):
|
||||
"""
|
||||
Initialize the nick templates for matching and remapping a string.
|
||||
|
||||
Args:
|
||||
in_template (str): The template to be used for nick recognition.
|
||||
out_template (str): The template to be used to replace the string
|
||||
matched by the in_template.
|
||||
|
||||
Returns:
|
||||
regex (regex): Regex to match against strings
|
||||
template (str): Template with markers {arg1}, {arg2}, etc for
|
||||
replacement using the standard .format method.
|
||||
|
||||
Raises:
|
||||
NickTemplateInvalid: If the in/out template does not have a matching
|
||||
number of $args.
|
||||
|
||||
"""
|
||||
# create the regex for in_template
|
||||
regex_string = fnmatch.translate(in_template)
|
||||
n_inargs = len(_RE_NICK_ARG.findall(regex_string))
|
||||
regex_string = _RE_NICK_SPACE.sub("\s+", regex_string)
|
||||
regex_string = _RE_NICK_ARG.sub(lambda m: "(?P<arg%s>.+?)" % m.group(2), regex_string)
|
||||
|
||||
# create the out_template
|
||||
template_string = _RE_NICK_TEMPLATE_ARG.sub(lambda m: "{arg%s}" % m.group(2), out_template)
|
||||
|
||||
# validate the tempaltes - they should at least have the same number of args
|
||||
n_outargs = len(_RE_NICK_TEMPLATE_ARG.findall(out_template))
|
||||
if n_inargs != n_outargs:
|
||||
print n_inargs, n_outargs
|
||||
raise NickTemplateInvalid
|
||||
|
||||
return re.compile(regex_string), template_string
|
||||
|
||||
|
||||
def parse_nick_template(string, template_regex, outtemplate):
|
||||
"""
|
||||
Parse a text using a template and map it to another template
|
||||
|
||||
Args:
|
||||
string (str): The input string to processj
|
||||
template_regex (regex): A template regex created with
|
||||
initialize_nick_template.
|
||||
outtemplate (str): The template to which to map the matches
|
||||
produced by the template_regex. This should have $1, $2,
|
||||
etc to match the regex.
|
||||
|
||||
"""
|
||||
match = template_regex.match(string)
|
||||
if match:
|
||||
return outtemplate.format(**match.groupdict())
|
||||
return string
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue