I18n string cleanup and refactoring
This commit is contained in:
parent
59dd0b007a
commit
7ff8cbb341
62 changed files with 890 additions and 738 deletions
|
|
@ -1,88 +1,99 @@
|
||||||
# Internationalization
|
# Internationalization
|
||||||
|
|
||||||
|
*Internationalization* (often abbreviated *i18n* since there are 18 characters
|
||||||
*Internationalization* (often abbreviated *i18n* since there are 18 characters between the first "i"
|
between the first "i" and the last "n" in that word) allows Evennia's core
|
||||||
and the last "n" in that word) allows Evennia's core server to return texts in other languages than
|
server to return texts in other languages than English - without anyone having
|
||||||
English - without anyone having to edit the source code. Take a look at the `locale` directory of
|
to edit the source code. Take a look at the `locale` directory of the Evennia
|
||||||
the Evennia installation, there you will find which languages are currently supported.
|
installation, there you will find which languages are currently supported.
|
||||||
|
|
||||||
## Changing server language
|
## Changing server language
|
||||||
|
|
||||||
Change language by adding the following to your `mygame/server/conf/settings.py` file:
|
Change language by adding the following to your `mygame/server/conf/settings.py`
|
||||||
|
file:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
LANGUAGE_CODE = 'en'
|
LANGUAGE_CODE = 'en'
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Here `'en'` should be changed to the abbreviation for one of the supported languages found in
|
Here `'en'` should be changed to the abbreviation for one of the supported
|
||||||
`locale/`. Restart the server to activate i18n. The two-character international language codes are
|
languages found in `locale/`. Restart the server to activate i18n. The
|
||||||
found [here](http://www.science.co.il/Language/Codes.asp).
|
two-character international language codes are found
|
||||||
|
[here](http://www.science.co.il/Language/Codes.asp).
|
||||||
|
|
||||||
> Windows Note: If you get errors concerning `gettext` or `xgettext` on Windows, see the [Django
|
> Windows Note: If you get errors concerning `gettext` or `xgettext` on Windows,
|
||||||
documentation](https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#gettext-on-windows). A
|
> see the
|
||||||
self-installing and up-to-date version of gettext for Windows (32/64-bit) is available on
|
> [Django documentation](https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#gettext-on-windows).
|
||||||
[Github](https://github.com/mlocati/gettext-iconv-windows).
|
> A self-installing and up-to-date version of gettext for Windows (32/64-bit) is
|
||||||
|
> available on [Github](https://github.com/mlocati/gettext-iconv-windows).
|
||||||
|
|
||||||
## Translating Evennia
|
## Translating Evennia
|
||||||
|
|
||||||
> **Important Note:** Evennia offers translations of hard-coded strings in the server, things like
|
```important::
|
||||||
"Connection closed" or "Server restarted", strings that end users will see and which game devs are
|
|
||||||
not supposed to change on their own. Text you see in the log file or on the command line (like error
|
|
||||||
messages) are generally *not* translated (this is a part of Python).
|
|
||||||
|
|
||||||
> In addition, text in default Commands and in default Typeclasses will *not* be translated by
|
Evennia offers translations of hard-coded strings in the server, things like
|
||||||
switching *i18n* language. To translate Commands and Typeclass hooks you must overload them in your
|
"Connection closed" or "Server restarted", strings that end users will see and
|
||||||
game directory and translate their returns to the language you want. This is because from Evennia's
|
which game devs are not supposed to change on their own. Text you see in the log
|
||||||
perspective, adding *i18n* code to commands tend to add complexity to code that is *meant* to be
|
file or on the command line (like error messages) are generally *not* translated
|
||||||
changed anyway. One of the goals of Evennia is to keep the user-changeable code as clean and easy-
|
(this is a part of Python).
|
||||||
to-read as possible.
|
|
||||||
|
|
||||||
If you cannot find your language in `evennia/locale/` it's because noone has translated it yet.
|
In addition, text in default Commands and in default Typeclasses will *not* be
|
||||||
Alternatively you might have the language but find the translation bad ... You are welcome to help
|
translated by switching *i18n* language. To translate Commands and Typeclass
|
||||||
improve the situation!
|
hooks you must overload them in your game directory and translate their returns
|
||||||
|
to the language you want. This is because from Evennia's perspective, adding
|
||||||
|
*i18n* code to commands tend to add complexity to code that is *meant* to be
|
||||||
|
changed anyway. One of the goals of Evennia is to keep the user-changeable code
|
||||||
|
as clean and easy- to-read as possible.
|
||||||
|
```
|
||||||
|
|
||||||
To start a new translation you need to first have cloned the Evennia repositry with GIT and
|
If you cannot find your language in `evennia/locale/` it's because noone has
|
||||||
activated a python virtualenv as described on the [Setup Quickstart](../Setup/Setup-Quickstart) page. You now
|
translated it yet. Alternatively you might have the language but find the
|
||||||
need to `cd` to the `evennia/` directory. This is *not* your created game folder but the main
|
translation bad ... You are welcome to help improve the situation!
|
||||||
Evennia library folder. If you see a folder `locale/` then you are in the right place. From here you
|
|
||||||
run:
|
|
||||||
|
|
||||||
evennia makemessages <language-code>
|
To start a new translation you need to first have cloned the Evennia repositry
|
||||||
|
with GIT and activated a python virtualenv as described on the [Setup
|
||||||
|
Quickstart](../Setup/Setup-Quickstart) page.
|
||||||
|
|
||||||
|
Go to your game dir and make sure your `virtualenv` is active so the `evennia`
|
||||||
|
command is available. Then run
|
||||||
|
|
||||||
|
evennia makemessages --locale <language-code>
|
||||||
|
|
||||||
where `<language-code>` is the [two-letter locale code](http://www.science.co.il/Language/Codes.asp)
|
where `<language-code>` is the [two-letter locale code](http://www.science.co.il/Language/Codes.asp)
|
||||||
for the language you want, like 'sv' for Swedish or 'es' for Spanish. After a moment it will tell
|
for the language you want to translate, like 'sv' for Swedish or 'es' for
|
||||||
you the language has been processed. For instance:
|
Spanish. After a moment it will tell you the language has been processed. For
|
||||||
|
instance:
|
||||||
|
|
||||||
evennia makemessages sv
|
evennia makemessages --locale sv
|
||||||
|
|
||||||
If you started a new language a new folder for that language will have emerged in the `locale/`
|
If you started a new language, a new folder for that language will have emerged
|
||||||
folder. Otherwise the system will just have updated the existing translation with eventual new
|
in the `locale/` folder. Otherwise the system will just have updated the
|
||||||
strings found in the server. Running this command will not overwrite any existing strings so you can
|
existing translation with eventual new strings found in the server. Running this
|
||||||
run it as much as you want.
|
command will not overwrite any existing strings so you can run it as much as you
|
||||||
|
want.
|
||||||
|
|
||||||
> Note: in Django, the `makemessages` command prefixes the locale name by the `-l` option (`...
|
Next head to `locale/<language-code>/LC_MESSAGES` and edit the `**.po` file you
|
||||||
makemessages -l sv` for instance). This syntax is not allowed in Evennia, due to the fact that `-l`
|
find there. You can edit this with a normal text editor but it is easiest if
|
||||||
is the option to tail log files. Hence, `makemessages` doesn't use the `-l` flag.
|
you use a special po-file editor from the web (search the web for "po editor"
|
||||||
|
for many free alternatives).
|
||||||
|
|
||||||
Next head to `locale/<language-code>/LC_MESSAGES` and edit the `**.po` file you find there. You can
|
The concept of translating is simple, it's just a matter of taking the english
|
||||||
edit this with a normal text editor but it is easiest if you use a special po-file editor from the
|
strings you find in the `**.po` file and add your language's translation best
|
||||||
web (search the web for "po editor" for many free alternatives).
|
you can. The `**.po` format (and many supporting editors) allow you to mark
|
||||||
|
translations as "fuzzy". This tells the system (and future translators) that you
|
||||||
The concept of translating is simple, it's just a matter of taking the english strings you find in
|
are unsure about the translation, or that you couldn't find a translation that
|
||||||
the `**.po` file and add your language's translation best you can. The `**.po` format (and many
|
exactly matched the intention of the original text. Other translators will see
|
||||||
supporting editors) allow you to mark translations as "fuzzy". This tells the system (and future
|
this and might be able to improve it later. Finally, you need to compile your
|
||||||
translators) that you are unsure about the translation, or that you couldn't find a translation that
|
translation into a more efficient form. Do so from the `evennia` folder again:
|
||||||
exactly matched the intention of the original text. Other translators will see this and might be
|
|
||||||
able to improve it later.
|
|
||||||
Finally, you need to compile your translation into a more efficient form. Do so from the `evennia`
|
|
||||||
folder
|
|
||||||
again:
|
|
||||||
|
|
||||||
evennia compilemessages
|
evennia compilemessages
|
||||||
|
|
||||||
This will go through all languages and create/update compiled files (`**.mo`) for them. This needs
|
This will go through all languages and create/update compiled files (`**.mo`)
|
||||||
to be done whenever a `**.po` file is updated.
|
for them. This needs to be done whenever a `**.po` file is updated.
|
||||||
|
|
||||||
When you are done, send the `**.po` and `*.mo` file to the Evennia developer list (or push it into
|
When you are done, make sure that everyone can benefit from your translation!
|
||||||
your own repository clone) so we can integrate your translation into Evennia!
|
Make a PR against Evennia with the updated `**.po` and `*.mo` files. Less
|
||||||
|
ideally (if git is not your thing) you can also attach them to a new post in our
|
||||||
|
forums.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Typeclass for Account objects
|
Typeclass for Account objects.
|
||||||
|
|
||||||
Note that this object is primarily intended to
|
Note that this object is primarily intended to
|
||||||
store OOC information, not game info! This
|
store OOC information, not game info! This
|
||||||
|
|
@ -54,7 +54,8 @@ _CMDHANDLER = None
|
||||||
|
|
||||||
# Create throttles for too many account-creations and login attempts
|
# Create throttles for too many account-creations and login attempts
|
||||||
CREATION_THROTTLE = Throttle(
|
CREATION_THROTTLE = Throttle(
|
||||||
name='creation', limit=settings.CREATION_THROTTLE_LIMIT, timeout=settings.CREATION_THROTTLE_TIMEOUT
|
name='creation', limit=settings.CREATION_THROTTLE_LIMIT,
|
||||||
|
timeout=settings.CREATION_THROTTLE_TIMEOUT
|
||||||
)
|
)
|
||||||
LOGIN_THROTTLE = Throttle(
|
LOGIN_THROTTLE = Throttle(
|
||||||
name='login', limit=settings.LOGIN_THROTTLE_LIMIT, timeout=settings.LOGIN_THROTTLE_TIMEOUT
|
name='login', limit=settings.LOGIN_THROTTLE_LIMIT, timeout=settings.LOGIN_THROTTLE_TIMEOUT
|
||||||
|
|
@ -292,11 +293,11 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
raise RuntimeError("Session not found")
|
raise RuntimeError("Session not found")
|
||||||
if self.get_puppet(session) == obj:
|
if self.get_puppet(session) == obj:
|
||||||
# already puppeting this object
|
# already puppeting this object
|
||||||
self.msg(_("You are already puppeting this object."))
|
self.msg("You are already puppeting this object.")
|
||||||
return
|
return
|
||||||
if not obj.access(self, "puppet"):
|
if not obj.access(self, "puppet"):
|
||||||
# no access
|
# no access
|
||||||
self.msg(_("You don't have permission to puppet '{key}'.").format(key=obj.key))
|
self.msg("You don't have permission to puppet '{obj.key}'.")
|
||||||
return
|
return
|
||||||
if obj.account:
|
if obj.account:
|
||||||
# object already puppeted
|
# object already puppeted
|
||||||
|
|
@ -312,8 +313,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
else:
|
else:
|
||||||
txt1 = f"Taking over |c{obj.name}|n from another of your sessions."
|
txt1 = f"Taking over |c{obj.name}|n from another of your sessions."
|
||||||
txt2 = f"|c{obj.name}|n|R is now acted from another of your sessions.|n"
|
txt2 = f"|c{obj.name}|n|R is now acted from another of your sessions.|n"
|
||||||
self.msg(_(txt1), session=session)
|
self.msg(txt1, session=session)
|
||||||
self.msg(_(txt2), session=obj.sessions.all())
|
self.msg(txt2, session=obj.sessions.all())
|
||||||
self.unpuppet_object(obj.sessions.get())
|
self.unpuppet_object(obj.sessions.get())
|
||||||
elif obj.account.is_connected:
|
elif obj.account.is_connected:
|
||||||
# controlled by another account
|
# controlled by another account
|
||||||
|
|
@ -543,7 +544,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
# Update throttle
|
# Update throttle
|
||||||
if ip:
|
if ip:
|
||||||
LOGIN_THROTTLE.update(ip, "Too many authentication failures.")
|
LOGIN_THROTTLE.update(ip, _("Too many authentication failures."))
|
||||||
|
|
||||||
# Try to call post-failure hook
|
# Try to call post-failure hook
|
||||||
session = kwargs.get("session", None)
|
session = kwargs.get("session", None)
|
||||||
|
|
@ -658,8 +659,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
password (str): Password to set.
|
password (str): Password to set.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
This is called by Django also when logging in; it should not be mixed up with validation, since that
|
This is called by Django also when logging in; it should not be mixed up with
|
||||||
would mean old passwords in the database (pre validation checks) could get invalidated.
|
validation, since that would mean old passwords in the database (pre validation checks)
|
||||||
|
could get invalidated.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
super(DefaultAccount, self).set_password(password)
|
super(DefaultAccount, self).set_password(password)
|
||||||
|
|
@ -798,12 +800,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
)
|
)
|
||||||
logger.log_sec(f"Account Created: {account} (IP: {ip}).")
|
logger.log_sec(f"Account Created: {account} (IP: {ip}).")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
errors.append(
|
errors.append(
|
||||||
_(
|
_("There was an error creating the Account. "
|
||||||
"There was an error creating the Account. If this problem persists, contact an admin."
|
"If this problem persists, contact an admin."))
|
||||||
)
|
|
||||||
)
|
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
return None, errors
|
return None, errors
|
||||||
|
|
||||||
|
|
@ -819,7 +819,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
# join the new account to the public channel
|
# join the new account to the public channel
|
||||||
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
|
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
|
||||||
if not pchannel or not pchannel.connect(account):
|
if not pchannel or not pchannel.connect(account):
|
||||||
string = f"New account '{account.key}' could not connect to public channel!"
|
string = "New account '{account.key}' could not connect to public channel!"
|
||||||
errors.append(string)
|
errors.append(string)
|
||||||
logger.log_err(string)
|
logger.log_err(string)
|
||||||
|
|
||||||
|
|
@ -1574,7 +1574,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
if hasattr(target, "return_appearance"):
|
if hasattr(target, "return_appearance"):
|
||||||
return target.return_appearance(self)
|
return target.return_appearance(self)
|
||||||
else:
|
else:
|
||||||
return _("{target} has no in-game appearance.").format(target=target)
|
return f"{target} has no in-game appearance."
|
||||||
else:
|
else:
|
||||||
# list of targets - make list to disconnect from db
|
# list of targets - make list to disconnect from db
|
||||||
characters = list(tar for tar in target if tar) if target else []
|
characters = list(tar for tar in target if tar) if target else []
|
||||||
|
|
@ -1617,19 +1617,18 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
if is_su or len(characters) < charmax:
|
if is_su or len(characters) < charmax:
|
||||||
if not characters:
|
if not characters:
|
||||||
result.append(
|
result.append(
|
||||||
_(
|
"\n\n You don't have any characters yet. See |whelp charcreate|n for "
|
||||||
"\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one."
|
"creating one."
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result.append("\n |w@charcreate <name> [=description]|n - create new character")
|
result.append("\n |wcharcreate <name> [=description]|n - create new character")
|
||||||
result.append(
|
result.append(
|
||||||
"\n |w@chardelete <name>|n - delete a character (cannot be undone!)"
|
"\n |wchardelete <name>|n - delete a character (cannot be undone!)"
|
||||||
)
|
)
|
||||||
|
|
||||||
if characters:
|
if characters:
|
||||||
string_s_ending = len(characters) > 1 and "s" or ""
|
string_s_ending = len(characters) > 1 and "s" or ""
|
||||||
result.append("\n |w@ic <character>|n - enter the game (|w@ooc|n to get back here)")
|
result.append("\n |wic <character>|n - enter the game (|wooc|n to get back here)")
|
||||||
if is_su:
|
if is_su:
|
||||||
result.append(
|
result.append(
|
||||||
f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):"
|
f"\n\nAvailable character{string_s_ending} ({len(characters)}/unlimited):"
|
||||||
|
|
@ -1651,11 +1650,12 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
sid = sess in sessions and sessions.index(sess) + 1
|
sid = sess in sessions and sessions.index(sess) + 1
|
||||||
if sess and sid:
|
if sess and sid:
|
||||||
result.append(
|
result.append(
|
||||||
f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] (played by you in session {sid})"
|
f"\n - |G{char.key}|n [{', '.join(char.permissions.all())}] "
|
||||||
)
|
f"(played by you in session {sid})")
|
||||||
else:
|
else:
|
||||||
result.append(
|
result.append(
|
||||||
f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] (played by someone else)"
|
f"\n - |R{char.key}|n [{', '.join(char.permissions.all())}] "
|
||||||
|
"(played by someone else)"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# character is "free to puppet"
|
# character is "free to puppet"
|
||||||
|
|
@ -1668,6 +1668,7 @@ class DefaultGuest(DefaultAccount):
|
||||||
"""
|
"""
|
||||||
This class is used for guest logins. Unlike Accounts, Guests and
|
This class is used for guest logins. Unlike Accounts, Guests and
|
||||||
their characters are deleted after disconnection.
|
their characters are deleted after disconnection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
@ -1675,6 +1676,7 @@ class DefaultGuest(DefaultAccount):
|
||||||
"""
|
"""
|
||||||
Forwards request to cls.authenticate(); returns a DefaultGuest object
|
Forwards request to cls.authenticate(); returns a DefaultGuest object
|
||||||
if one is available for use.
|
if one is available for use.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return cls.authenticate(**kwargs)
|
return cls.authenticate(**kwargs)
|
||||||
|
|
||||||
|
|
@ -1742,7 +1744,7 @@ class DefaultGuest(DefaultAccount):
|
||||||
|
|
||||||
return account, errors
|
return account, errors
|
||||||
|
|
||||||
except Exception as e:
|
except Exception:
|
||||||
# We are in the middle between logged in and -not, so we have
|
# We are in the middle between logged in and -not, so we have
|
||||||
# to handle tracebacks ourselves at this point. If we don't,
|
# to handle tracebacks ourselves at this point. If we don't,
|
||||||
# we won't see any errors at all.
|
# we won't see any errors at all.
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,7 @@ class IRCBot(Bot):
|
||||||
nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower()))
|
nicklist = ", ".join(sorted(kwargs["nicklist"], key=lambda n: n.lower()))
|
||||||
for obj in self._nicklist_callers:
|
for obj in self._nicklist_callers:
|
||||||
obj.msg(
|
obj.msg(
|
||||||
_("Nicks at {chstr}:\n {nicklist}").format(chstr=chstr, nicklist=nicklist)
|
"Nicks at {chstr}:\n {nicklist}".format(chstr=chstr, nicklist=nicklist)
|
||||||
)
|
)
|
||||||
self._nicklist_callers = []
|
self._nicklist_callers = []
|
||||||
return
|
return
|
||||||
|
|
@ -341,7 +341,7 @@ class IRCBot(Bot):
|
||||||
chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})"
|
chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})"
|
||||||
for obj in self._ping_callers:
|
for obj in self._ping_callers:
|
||||||
obj.msg(
|
obj.msg(
|
||||||
_("IRC ping return from {chstr} took {time}s.").format(
|
"IRC ping return from {chstr} took {time}s.".format(
|
||||||
chstr=chstr, time=kwargs["timing"]
|
chstr=chstr, time=kwargs["timing"]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -397,6 +397,7 @@ class IRCBot(Bot):
|
||||||
|
|
||||||
#
|
#
|
||||||
# RSS
|
# RSS
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
class RSSBot(Bot):
|
class RSSBot(Bot):
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,8 @@ class AccountDB(TypedObject, AbstractUser):
|
||||||
"cmdset",
|
"cmdset",
|
||||||
max_length=255,
|
max_length=255,
|
||||||
null=True,
|
null=True,
|
||||||
help_text="optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.",
|
help_text="optional python path to a cmdset class. If creating a Character, this will "
|
||||||
|
"default to settings.CMDSET_CHARACTER.",
|
||||||
)
|
)
|
||||||
# marks if this is a "virtual" bot account object
|
# marks if this is a "virtual" bot account object
|
||||||
db_is_bot = models.BooleanField(
|
db_is_bot = models.BooleanField(
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import sys
|
|
||||||
from mock import Mock, MagicMock, patch
|
from mock import Mock, MagicMock, patch
|
||||||
from random import randint
|
from random import randint
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
@ -12,8 +11,6 @@ from evennia.utils.test_resources import EvenniaTest
|
||||||
from evennia.utils import create
|
from evennia.utils import create
|
||||||
from evennia.utils.utils import uses_database
|
from evennia.utils.utils import uses_database
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
class TestAccountSessionHandler(TestCase):
|
class TestAccountSessionHandler(TestCase):
|
||||||
"Check AccountSessionHandler class"
|
"Check AccountSessionHandler class"
|
||||||
|
|
|
||||||
|
|
@ -80,50 +80,50 @@ _SEARCH_AT_RESULT = utils.variable_from_module(*settings.SEARCH_AT_RESULT.rsplit
|
||||||
# is the normal "production message to echo to the account.
|
# is the normal "production message to echo to the account.
|
||||||
|
|
||||||
_ERROR_UNTRAPPED = (
|
_ERROR_UNTRAPPED = (
|
||||||
"""
|
_("""
|
||||||
An untrapped error occurred.
|
An untrapped error occurred.
|
||||||
""",
|
"""),
|
||||||
"""
|
_("""
|
||||||
An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
|
An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
|
||||||
""",
|
"""),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ERROR_CMDSETS = (
|
_ERROR_CMDSETS = (
|
||||||
"""
|
_("""
|
||||||
A cmdset merger-error occurred. This is often due to a syntax
|
A cmdset merger-error occurred. This is often due to a syntax
|
||||||
error in one of the cmdsets to merge.
|
error in one of the cmdsets to merge.
|
||||||
""",
|
"""),
|
||||||
"""
|
_("""
|
||||||
A cmdset merger-error occurred. Please file a bug report detailing the
|
A cmdset merger-error occurred. Please file a bug report detailing the
|
||||||
steps to reproduce.
|
steps to reproduce.
|
||||||
""",
|
"""),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ERROR_NOCMDSETS = (
|
_ERROR_NOCMDSETS = (
|
||||||
"""
|
_("""
|
||||||
No command sets found! This is a critical bug that can have
|
No command sets found! This is a critical bug that can have
|
||||||
multiple causes.
|
multiple causes.
|
||||||
""",
|
"""),
|
||||||
"""
|
_("""
|
||||||
No command sets found! This is a sign of a critical bug. If
|
No command sets found! This is a sign of a critical bug. If
|
||||||
disconnecting/reconnecting doesn't" solve the problem, try to contact
|
disconnecting/reconnecting doesn't" solve the problem, try to contact
|
||||||
the server admin through" some other means for assistance.
|
the server admin through" some other means for assistance.
|
||||||
""",
|
"""),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ERROR_CMDHANDLER = (
|
_ERROR_CMDHANDLER = (
|
||||||
"""
|
_("""
|
||||||
A command handler bug occurred. If this is not due to a local change,
|
A command handler bug occurred. If this is not due to a local change,
|
||||||
please file a bug report with the Evennia project, including the
|
please file a bug report with the Evennia project, including the
|
||||||
traceback and steps to reproduce.
|
traceback and steps to reproduce.
|
||||||
""",
|
"""),
|
||||||
"""
|
_("""
|
||||||
A command handler bug occurred. Please notify staff - they should
|
A command handler bug occurred. Please notify staff - they should
|
||||||
likely file a bug report with the Evennia project.
|
likely file a bug report with the Evennia project.
|
||||||
""",
|
"""),
|
||||||
)
|
)
|
||||||
|
|
||||||
_ERROR_RECURSION_LIMIT = (
|
_ERROR_RECURSION_LIMIT = _(
|
||||||
"Command recursion limit ({recursion_limit}) " "reached for '{raw_cmdname}' ({cmdclass})."
|
"Command recursion limit ({recursion_limit}) " "reached for '{raw_cmdname}' ({cmdclass})."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -146,7 +146,7 @@ def _msg_err(receiver, stringtuple):
|
||||||
production string (with a timestamp) to be shown to the user.
|
production string (with a timestamp) to be shown to the user.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
string = "{traceback}\n{errmsg}\n(Traceback was logged {timestamp})."
|
string = _("{traceback}\n{errmsg}\n(Traceback was logged {timestamp}).")
|
||||||
timestamp = logger.timeformat()
|
timestamp = logger.timeformat()
|
||||||
tracestring = format_exc()
|
tracestring = format_exc()
|
||||||
logger.log_trace()
|
logger.log_trace()
|
||||||
|
|
@ -299,6 +299,7 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
|
||||||
def _get_local_obj_cmdsets(obj):
|
def _get_local_obj_cmdsets(obj):
|
||||||
"""
|
"""
|
||||||
Helper-method; Get Object-level cmdsets
|
Helper-method; Get Object-level cmdsets
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Gather cmdsets from location, objects in location or carried
|
# Gather cmdsets from location, objects in location or carried
|
||||||
try:
|
try:
|
||||||
|
|
@ -352,6 +353,7 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
|
||||||
"""
|
"""
|
||||||
Helper method; Get cmdset while making sure to trigger all
|
Helper method; Get cmdset while making sure to trigger all
|
||||||
hooks safely. Returns the stack and the valid options.
|
hooks safely. Returns the stack and the valid options.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
yield obj.at_cmdset_get()
|
yield obj.at_cmdset_get()
|
||||||
|
|
@ -384,13 +386,6 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
|
||||||
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
|
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
|
||||||
]
|
]
|
||||||
cmdsets += local_obj_cmdsets
|
cmdsets += local_obj_cmdsets
|
||||||
# if not current.no_channels:
|
|
||||||
# # also objs may have channels
|
|
||||||
# channel_cmdsets = yield _get_channel_cmdset(obj)
|
|
||||||
# cmdsets += channel_cmdsets
|
|
||||||
# if not current.no_channels:
|
|
||||||
# channel_cmdsets = yield _get_channel_cmdset(account)
|
|
||||||
# cmdsets += channel_cmdsets
|
|
||||||
|
|
||||||
elif callertype == "account":
|
elif callertype == "account":
|
||||||
# we are calling the command from the account level
|
# we are calling the command from the account level
|
||||||
|
|
@ -408,11 +403,6 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
|
||||||
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
|
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
|
||||||
]
|
]
|
||||||
cmdsets += local_obj_cmdsets
|
cmdsets += local_obj_cmdsets
|
||||||
# if not current.no_channels:
|
|
||||||
# # also objs may have channels
|
|
||||||
# cmdsets += yield _get_channel_cmdset(obj)
|
|
||||||
# if not current.no_channels:
|
|
||||||
# cmdsets += yield _get_channel_cmdset(account)
|
|
||||||
|
|
||||||
elif callertype == "object":
|
elif callertype == "object":
|
||||||
# we are calling the command from the object level
|
# we are calling the command from the object level
|
||||||
|
|
@ -426,9 +416,6 @@ def get_and_merge_cmdsets(caller, session, account, obj, callertype, raw_string)
|
||||||
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
|
cmdset for cmdset in local_obj_cmdsets if cmdset.key != "ExitCmdSet"
|
||||||
]
|
]
|
||||||
cmdsets += yield local_obj_cmdsets
|
cmdsets += yield local_obj_cmdsets
|
||||||
# if not current.no_channels:
|
|
||||||
# # also objs may have channels
|
|
||||||
# cmdsets += yield _get_channel_cmdset(obj)
|
|
||||||
else:
|
else:
|
||||||
raise Exception("get_and_merge_cmdsets: callertype %s is not valid." % callertype)
|
raise Exception("get_and_merge_cmdsets: callertype %s is not valid." % callertype)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -364,8 +364,8 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
||||||
if getattr(self, opt) is not None
|
if getattr(self, opt) is not None
|
||||||
])
|
])
|
||||||
options = (", " + options) if options else ""
|
options = (", " + options) if options else ""
|
||||||
return f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: " + ", ".join(
|
return (f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: "
|
||||||
[str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])
|
+ ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)]))
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -477,7 +477,8 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
||||||
# This is used for diagnosis.
|
# This is used for diagnosis.
|
||||||
cmdset_c.actual_mergetype = mergetype
|
cmdset_c.actual_mergetype = mergetype
|
||||||
|
|
||||||
# print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority, cmdset_a.key, cmdset_a.priority)
|
# print "__add__ for %s (prio %i) called with %s (prio %i)." % (self.key, self.priority,
|
||||||
|
# cmdset_a.key, cmdset_a.priority)
|
||||||
|
|
||||||
# return the system commands to the cmdset
|
# return the system commands to the cmdset
|
||||||
cmdset_c.add(sys_commands, allow_duplicates=True)
|
cmdset_c.add(sys_commands, allow_duplicates=True)
|
||||||
|
|
@ -670,5 +671,6 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
||||||
Hook method - this should be overloaded in the inheriting
|
Hook method - this should be overloaded in the inheriting
|
||||||
class, and should take care of populating the cmdset by use of
|
class, and should take care of populating the cmdset by use of
|
||||||
self.add().
|
self.add().
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ Since any number of CommandSets can be piled on top of each other, you
|
||||||
can then implement separate sets for different situations. For
|
can then implement separate sets for different situations. For
|
||||||
example, you can have a 'On a boat' set, onto which you then tack on
|
example, you can have a 'On a boat' set, onto which you then tack on
|
||||||
the 'Fishing' set. Fishing from a boat? No problem!
|
the 'Fishing' set. Fishing from a boat? No problem!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import sys
|
import sys
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
|
@ -168,7 +169,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
||||||
if "." in path:
|
if "." in path:
|
||||||
modpath, classname = python_path.rsplit(".", 1)
|
modpath, classname = python_path.rsplit(".", 1)
|
||||||
else:
|
else:
|
||||||
raise ImportError("The path '%s' is not on the form modulepath.ClassName" % path)
|
raise ImportError(f"The path '{path}' is not on the form modulepath.ClassName")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# first try to get from cache
|
# first try to get from cache
|
||||||
|
|
@ -312,6 +313,7 @@ class CmdSetHandler(object):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
Display current commands
|
Display current commands
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
strings = ["<CmdSetHandler> stack:"]
|
strings = ["<CmdSetHandler> stack:"]
|
||||||
|
|
@ -331,7 +333,7 @@ class CmdSetHandler(object):
|
||||||
|
|
||||||
if mergelist:
|
if mergelist:
|
||||||
# current is a result of mergers
|
# current is a result of mergers
|
||||||
mergelist="+".join(mergelist)
|
mergelist = "+".join(mergelist)
|
||||||
strings.append(f" <Merged {mergelist}>: {self.current}")
|
strings.append(f" <Merged {mergelist}>: {self.current}")
|
||||||
else:
|
else:
|
||||||
# current is a single cmdset
|
# current is a single cmdset
|
||||||
|
|
@ -421,7 +423,8 @@ class CmdSetHandler(object):
|
||||||
self.mergetype_stack.append(new_current.actual_mergetype)
|
self.mergetype_stack.append(new_current.actual_mergetype)
|
||||||
self.current = new_current
|
self.current = new_current
|
||||||
|
|
||||||
def add(self, cmdset, emit_to_obj=None, persistent=True, permanent=True, default_cmdset=False, **kwargs):
|
def add(self, cmdset, emit_to_obj=None, persistent=True, default_cmdset=False,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Add a cmdset to the handler, on top of the old ones, unless it
|
Add a cmdset to the handler, on top of the old ones, unless it
|
||||||
is set as the default one (it will then end up at the bottom of the stack)
|
is set as the default one (it will then end up at the bottom of the stack)
|
||||||
|
|
@ -431,8 +434,6 @@ class CmdSetHandler(object):
|
||||||
to such an object.
|
to such an object.
|
||||||
emit_to_obj (Object, optional): An object to receive error messages.
|
emit_to_obj (Object, optional): An object to receive error messages.
|
||||||
persistent (bool, optional): Let cmdset remain across server reload.
|
persistent (bool, optional): Let cmdset remain across server reload.
|
||||||
permanent (bool, optional): DEPRECATED. This has the same use as
|
|
||||||
`persistent`.
|
|
||||||
default_cmdset (Cmdset, optional): Insert this to replace the
|
default_cmdset (Cmdset, optional): Insert this to replace the
|
||||||
default cmdset position (there is only one such position,
|
default cmdset position (there is only one such position,
|
||||||
always at the bottom of the stack).
|
always at the bottom of the stack).
|
||||||
|
|
@ -450,10 +451,9 @@ class CmdSetHandler(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if "permanent" in kwargs:
|
if "permanent" in kwargs:
|
||||||
logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed to "
|
logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed name to "
|
||||||
"'persistent' and now defaults to True.")
|
"'persistent' and now defaults to True.")
|
||||||
|
persistent = kwargs['permanent'] if persistent is None else persistent
|
||||||
permanent = persistent or permanent
|
|
||||||
|
|
||||||
if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)):
|
if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)):
|
||||||
string = _("Only CmdSets can be added to the cmdsethandler!")
|
string = _("Only CmdSets can be added to the cmdsethandler!")
|
||||||
|
|
@ -465,8 +465,8 @@ class CmdSetHandler(object):
|
||||||
# this is (maybe) a python path. Try to import from cache.
|
# this is (maybe) a python path. Try to import from cache.
|
||||||
cmdset = self._import_cmdset(cmdset)
|
cmdset = self._import_cmdset(cmdset)
|
||||||
if cmdset and cmdset.key != "_CMDSET_ERROR":
|
if cmdset and cmdset.key != "_CMDSET_ERROR":
|
||||||
cmdset.permanent = permanent
|
cmdset.permanent = persistent # TODO change on cmdset too
|
||||||
if permanent and cmdset.key != "_CMDSET_ERROR":
|
if persistent and cmdset.key != "_CMDSET_ERROR":
|
||||||
# store the path permanently
|
# store the path permanently
|
||||||
storage = self.obj.cmdset_storage or [""]
|
storage = self.obj.cmdset_storage or [""]
|
||||||
if default_cmdset:
|
if default_cmdset:
|
||||||
|
|
@ -480,17 +480,21 @@ class CmdSetHandler(object):
|
||||||
self.cmdset_stack.append(cmdset)
|
self.cmdset_stack.append(cmdset)
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
def add_default(self, cmdset, emit_to_obj=None, permanent=True):
|
def add_default(self, cmdset, emit_to_obj=None, persistent=True, **kwargs):
|
||||||
"""
|
"""
|
||||||
Shortcut for adding a default cmdset.
|
Shortcut for adding a default cmdset.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
cmdset (Cmdset): The Cmdset to add.
|
cmdset (Cmdset): The Cmdset to add.
|
||||||
emit_to_obj (Object, optional): Gets error messages
|
emit_to_obj (Object, optional): Gets error messages
|
||||||
permanent (bool, optional): The new Cmdset should survive a server reboot.
|
persistent (bool, optional): The new Cmdset should survive a server reboot.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.add(cmdset, emit_to_obj=emit_to_obj, permanent=permanent, default_cmdset=True)
|
if "permanent" in kwargs:
|
||||||
|
logger.log_dep("obj.cmdset.add_default() kwarg 'permanent' has changed name to "
|
||||||
|
"'persistent'.")
|
||||||
|
persistent = kwargs['permanent'] if persistent is None else persistent
|
||||||
|
self.add(cmdset, emit_to_obj=emit_to_obj, persistent=persistent, default_cmdset=True)
|
||||||
|
|
||||||
def remove(self, cmdset=None, default_cmdset=False):
|
def remove(self, cmdset=None, default_cmdset=False):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -572,6 +572,7 @@ Command {self} has no defined `func()` - showing on-command variables:
|
||||||
border_left_char=border_left_char,
|
border_left_char=border_left_char,
|
||||||
border_right_char=border_right_char,
|
border_right_char=border_right_char,
|
||||||
border_top_char=border_top_char,
|
border_top_char=border_top_char,
|
||||||
|
border_bottom_char=border_bottom_char,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
return table
|
return table
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from django.urls import reverse
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
|
||||||
from evennia.typeclasses.models import TypeclassBase
|
from evennia.typeclasses.models import TypeclassBase
|
||||||
from evennia.comms.models import TempMsg, ChannelDB
|
from evennia.comms.models import ChannelDB
|
||||||
from evennia.comms.managers import ChannelManager
|
from evennia.comms.managers import ChannelManager
|
||||||
from evennia.utils import create, logger
|
from evennia.utils import create, logger
|
||||||
from evennia.utils.utils import make_iter
|
from evennia.utils.utils import make_iter
|
||||||
|
|
@ -49,7 +49,6 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
channel_msg_nick_pattern = r"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)"
|
channel_msg_nick_pattern = r"{alias}\s*?|{alias}\s+?(?P<arg1>.+?)"
|
||||||
channel_msg_nick_replacement = "channel {channelname} = $1"
|
channel_msg_nick_replacement = "channel {channelname} = $1"
|
||||||
|
|
||||||
|
|
||||||
def at_first_save(self):
|
def at_first_save(self):
|
||||||
"""
|
"""
|
||||||
Called by the typeclass system the very first time the channel
|
Called by the typeclass system the very first time the channel
|
||||||
|
|
@ -706,7 +705,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_detail_url(self):
|
def web_get_detail_url(self):
|
||||||
|
|
@ -721,8 +720,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
a named view of 'channel-detail' would be referenced by this method.
|
a named view of 'channel-detail' would be referenced by this method.
|
||||||
|
|
||||||
ex.
|
ex.
|
||||||
url(r'channels/(?P<slug>[\w\d\-]+)/$',
|
::
|
||||||
ChannelDetailView.as_view(), name='channel-detail')
|
|
||||||
|
url(r'channels/(?P<slug>[\w\d\-]+)/$',
|
||||||
|
ChannelDetailView.as_view(), name='channel-detail')
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an
|
If no View has been created and defined in urls.py, returns an
|
||||||
HTML anchor.
|
HTML anchor.
|
||||||
|
|
@ -740,7 +741,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
"%s-detail" % slugify(self._meta.verbose_name),
|
"%s-detail" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"slug": slugify(self.db_key)},
|
kwargs={"slug": slugify(self.db_key)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_update_url(self):
|
def web_get_update_url(self):
|
||||||
|
|
@ -755,8 +756,10 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
a named view of 'channel-update' would be referenced by this method.
|
a named view of 'channel-update' would be referenced by this method.
|
||||||
|
|
||||||
ex.
|
ex.
|
||||||
url(r'channels/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
|
::
|
||||||
ChannelUpdateView.as_view(), name='channel-update')
|
|
||||||
|
url(r'channels/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
|
||||||
|
ChannelUpdateView.as_view(), name='channel-update')
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an
|
If no View has been created and defined in urls.py, returns an
|
||||||
HTML anchor.
|
HTML anchor.
|
||||||
|
|
@ -774,7 +777,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
"%s-update" % slugify(self._meta.verbose_name),
|
"%s-update" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"slug": slugify(self.db_key)},
|
kwargs={"slug": slugify(self.db_key)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_delete_url(self):
|
def web_get_delete_url(self):
|
||||||
|
|
@ -807,7 +810,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
||||||
"%s-delete" % slugify(self._meta.verbose_name),
|
"%s-delete" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"slug": slugify(self.db_key)},
|
kwargs={"slug": slugify(self.db_key)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
# Used by Django Sites/Admin
|
# Used by Django Sites/Admin
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,6 @@ class MsgManager(TypedObjectManager):
|
||||||
else:
|
else:
|
||||||
raise CommError
|
raise CommError
|
||||||
|
|
||||||
|
|
||||||
def search_message(self, sender=None, receiver=None, freetext=None, dbref=None):
|
def search_message(self, sender=None, receiver=None, freetext=None, dbref=None):
|
||||||
"""
|
"""
|
||||||
Search the message database for particular messages. At least
|
Search the message database for particular messages. At least
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ database.
|
||||||
Channels are central objects that act as targets for Msgs. Accounts can
|
Channels are central objects that act as targets for Msgs. Accounts can
|
||||||
connect to channels by use of a ChannelConnect object (this object is
|
connect to channels by use of a ChannelConnect object (this object is
|
||||||
necessary to easily be able to delete connections on the fly).
|
necessary to easily be able to delete connections on the fly).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
@ -165,7 +166,8 @@ class Msg(SharedMemoryModel):
|
||||||
db_tags = models.ManyToManyField(
|
db_tags = models.ManyToManyField(
|
||||||
Tag,
|
Tag,
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="tags on this message. Tags are simple string markers to identify, group and alias messages.",
|
help_text="tags on this message. Tags are simple string markers to "
|
||||||
|
"identify, group and alias messages.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Database manager
|
# Database manager
|
||||||
|
|
@ -309,7 +311,6 @@ class Msg(SharedMemoryModel):
|
||||||
self.db_receiver_external = ""
|
self.db_receiver_external = ""
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
def remove_receiver(self, receivers):
|
def remove_receiver(self, receivers):
|
||||||
"""
|
"""
|
||||||
Remove a single receiver, a list of receivers, or a single extral receiver.
|
Remove a single receiver, a list of receivers, or a single extral receiver.
|
||||||
|
|
@ -337,8 +338,8 @@ class Msg(SharedMemoryModel):
|
||||||
elif clsname == "ScriptDB":
|
elif clsname == "ScriptDB":
|
||||||
self.db_receivers_scripts.remove(receiver)
|
self.db_receivers_scripts.remove(receiver)
|
||||||
|
|
||||||
|
@property
|
||||||
def __hide_from_get(self):
|
def hide_from(self):
|
||||||
"""
|
"""
|
||||||
Getter. Allows for value = self.hide_from.
|
Getter. Allows for value = self.hide_from.
|
||||||
Returns two lists of accounts and objects.
|
Returns two lists of accounts and objects.
|
||||||
|
|
@ -349,9 +350,12 @@ class Msg(SharedMemoryModel):
|
||||||
self.db_hide_from_objects.all(),
|
self.db_hide_from_objects.all(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# @hide_from_sender.setter
|
@hide_from.setter
|
||||||
def __hide_from_set(self, hiders):
|
def hide_from(self, hiders):
|
||||||
"Setter. Allows for self.hide_from = value. Will append to hiders"
|
"""
|
||||||
|
Setter. Allows for self.hide_from = value. Will append to hiders.
|
||||||
|
|
||||||
|
"""
|
||||||
for hider in make_iter(hiders):
|
for hider in make_iter(hiders):
|
||||||
if not hider:
|
if not hider:
|
||||||
continue
|
continue
|
||||||
|
|
@ -363,21 +367,25 @@ class Msg(SharedMemoryModel):
|
||||||
elif clsname == "ObjectDB":
|
elif clsname == "ObjectDB":
|
||||||
self.db_hide_from_objects.add(hider.__dbclass__)
|
self.db_hide_from_objects.add(hider.__dbclass__)
|
||||||
|
|
||||||
# @hide_from_sender.deleter
|
@hide_from.deleter
|
||||||
def __hide_from_del(self):
|
def hide_from(self):
|
||||||
"Deleter. Allows for del self.hide_from_senders"
|
"""
|
||||||
|
Deleter. Allows for del self.hide_from_senders
|
||||||
|
|
||||||
|
"""
|
||||||
self.db_hide_from_accounts.clear()
|
self.db_hide_from_accounts.clear()
|
||||||
self.db_hide_from_objects.clear()
|
self.db_hide_from_objects.clear()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
hide_from = property(__hide_from_get, __hide_from_set, __hide_from_del)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Msg class methods
|
# Msg class methods
|
||||||
#
|
#
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"This handles what is shown when e.g. printing the message"
|
"""
|
||||||
|
This handles what is shown when e.g. printing the message.
|
||||||
|
|
||||||
|
"""
|
||||||
senders = ",".join(getattr(obj, "key", str(obj)) for obj in self.senders)
|
senders = ",".join(getattr(obj, "key", str(obj)) for obj in self.senders)
|
||||||
receivers = ",".join(getattr(obj, "key", str(obj)) for obj in self.receivers)
|
receivers = ",".join(getattr(obj, "key", str(obj)) for obj in self.receivers)
|
||||||
return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40))
|
return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40))
|
||||||
|
|
@ -407,9 +415,8 @@ class Msg(SharedMemoryModel):
|
||||||
|
|
||||||
class TempMsg(object):
|
class TempMsg(object):
|
||||||
"""
|
"""
|
||||||
This is a non-persistent object for sending temporary messages
|
This is a non-persistent object for sending temporary messages that will not be stored. It
|
||||||
that will not be stored. It mimics the "real" Msg object, but
|
mimics the "real" Msg object, but doesn't require sender to be given.
|
||||||
doesn't require sender to be given.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -452,6 +459,7 @@ class TempMsg(object):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""
|
"""
|
||||||
This handles what is shown when e.g. printing the message.
|
This handles what is shown when e.g. printing the message.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
senders = ",".join(obj.key for obj in self.senders)
|
senders = ",".join(obj.key for obj in self.senders)
|
||||||
receivers = ",".join(obj.key for obj in self.receivers)
|
receivers = ",".join(obj.key for obj in self.receivers)
|
||||||
|
|
@ -477,6 +485,7 @@ class TempMsg(object):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
receiver (Object, Account, Script, str or list): Receivers to remove.
|
receiver (Object, Account, Script, str or list): Receivers to remove.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for o in make_iter(receiver):
|
for o in make_iter(receiver):
|
||||||
|
|
@ -513,6 +522,7 @@ class SubscriptionHandler(object):
|
||||||
This handler manages subscriptions to the
|
This handler manages subscriptions to the
|
||||||
channel and hides away which type of entity is
|
channel and hides away which type of entity is
|
||||||
subscribing (Account or Object)
|
subscribing (Account or Object)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
|
|
@ -634,7 +644,8 @@ class SubscriptionHandler(object):
|
||||||
if not obj.is_connected:
|
if not obj.is_connected:
|
||||||
continue
|
continue
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
# a subscribed object has already been deleted. Mark that we need a recache and ignore it
|
# a subscribed object has already been deleted. Mark that we need a recache and
|
||||||
|
# ignore it
|
||||||
recache_needed = True
|
recache_needed = True
|
||||||
continue
|
continue
|
||||||
subs.append(obj)
|
subs.append(obj)
|
||||||
|
|
@ -688,7 +699,7 @@ class ChannelDB(TypedObject):
|
||||||
__defaultclasspath__ = "evennia.comms.comms.DefaultChannel"
|
__defaultclasspath__ = "evennia.comms.comms.DefaultChannel"
|
||||||
__applabel__ = "comms"
|
__applabel__ = "comms"
|
||||||
|
|
||||||
class Meta(object):
|
class Meta:
|
||||||
"Define Django meta options"
|
"Define Django meta options"
|
||||||
verbose_name = "Channel"
|
verbose_name = "Channel"
|
||||||
verbose_name_plural = "Channels"
|
verbose_name_plural = "Channels"
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ class FileHelpStorageHandler:
|
||||||
|
|
||||||
Note that this is not meant to any searching/lookup - that is all handled
|
Note that this is not meant to any searching/lookup - that is all handled
|
||||||
by the help command.
|
by the help command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, help_file_modules=settings.FILE_HELP_ENTRY_MODULES):
|
def __init__(self, help_file_modules=settings.FILE_HELP_ENTRY_MODULES):
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
"""
|
"""
|
||||||
Custom manager for HelpEntry objects.
|
Custom manager for HelpEntry objects.
|
||||||
"""
|
"""
|
||||||
from django.db import models
|
|
||||||
from evennia.utils import logger, utils
|
from evennia.utils import logger, utils
|
||||||
from evennia.typeclasses.managers import TypedObjectManager
|
from evennia.typeclasses.managers import TypedObjectManager
|
||||||
|
|
||||||
|
|
@ -131,7 +130,7 @@ class HelpEntryManager(TypedObjectManager):
|
||||||
for topic in topics:
|
for topic in topics:
|
||||||
topic.help_category = default_category
|
topic.help_category = default_category
|
||||||
topic.save()
|
topic.save()
|
||||||
string = _("Help database moved to category {default_category}").format(
|
string = "Help database moved to category {default_category}".format(
|
||||||
default_category=default_category
|
default_category=default_category
|
||||||
)
|
)
|
||||||
logger.log_info(string)
|
logger.log_info(string)
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,9 @@ class HelpEntry(SharedMemoryModel):
|
||||||
a named view of 'character-create' would be referenced by this method.
|
a named view of 'character-create' would be referenced by this method.
|
||||||
|
|
||||||
ex.
|
ex.
|
||||||
url(r'characters/create/', ChargenView.as_view(), name='character-create')
|
::
|
||||||
|
|
||||||
|
url(r'characters/create/', ChargenView.as_view(), name='character-create')
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an
|
If no View has been created and defined in urls.py, returns an
|
||||||
HTML anchor.
|
HTML anchor.
|
||||||
|
|
@ -183,7 +185,7 @@ class HelpEntry(SharedMemoryModel):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_detail_url(self):
|
def web_get_detail_url(self):
|
||||||
|
|
@ -198,8 +200,9 @@ class HelpEntry(SharedMemoryModel):
|
||||||
a named view of 'character-detail' would be referenced by this method.
|
a named view of 'character-detail' would be referenced by this method.
|
||||||
|
|
||||||
ex.
|
ex.
|
||||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$',
|
::
|
||||||
CharDetailView.as_view(), name='character-detail')
|
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$',
|
||||||
|
CharDetailView.as_view(), name='character-detail')
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an
|
If no View has been created and defined in urls.py, returns an
|
||||||
HTML anchor.
|
HTML anchor.
|
||||||
|
|
@ -217,8 +220,7 @@ class HelpEntry(SharedMemoryModel):
|
||||||
"%s-detail" % slugify(self._meta.verbose_name),
|
"%s-detail" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
print(e)
|
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_update_url(self):
|
def web_get_update_url(self):
|
||||||
|
|
@ -233,8 +235,10 @@ class HelpEntry(SharedMemoryModel):
|
||||||
a named view of 'character-update' would be referenced by this method.
|
a named view of 'character-update' would be referenced by this method.
|
||||||
|
|
||||||
ex.
|
ex.
|
||||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
|
::
|
||||||
CharUpdateView.as_view(), name='character-update')
|
|
||||||
|
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
|
||||||
|
CharUpdateView.as_view(), name='character-update')
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an
|
If no View has been created and defined in urls.py, returns an
|
||||||
HTML anchor.
|
HTML anchor.
|
||||||
|
|
@ -252,7 +256,7 @@ class HelpEntry(SharedMemoryModel):
|
||||||
"%s-update" % slugify(self._meta.verbose_name),
|
"%s-update" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_delete_url(self):
|
def web_get_delete_url(self):
|
||||||
|
|
@ -266,8 +270,10 @@ class HelpEntry(SharedMemoryModel):
|
||||||
a named view of 'character-detail' would be referenced by this method.
|
a named view of 'character-detail' would be referenced by this method.
|
||||||
|
|
||||||
ex.
|
ex.
|
||||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$',
|
::
|
||||||
CharDeleteView.as_view(), name='character-delete')
|
|
||||||
|
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$',
|
||||||
|
CharDeleteView.as_view(), name='character-delete')
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an
|
If no View has been created and defined in urls.py, returns an
|
||||||
HTML anchor.
|
HTML anchor.
|
||||||
|
|
@ -285,7 +291,7 @@ class HelpEntry(SharedMemoryModel):
|
||||||
"%s-delete" % slugify(self._meta.verbose_name),
|
"%s-delete" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
# Used by Django Sites/Admin
|
# Used by Django Sites/Admin
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,6 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5, fields
|
||||||
custom_stop_words_filter = stop_word_filter.generate_stop_word_filter(stop_words)
|
custom_stop_words_filter = stop_word_filter.generate_stop_word_filter(stop_words)
|
||||||
_LUNR_BUILDER_PIPELINE = (trimmer, custom_stop_words_filter, stemmer)
|
_LUNR_BUILDER_PIPELINE = (trimmer, custom_stop_words_filter, stemmer)
|
||||||
|
|
||||||
|
|
||||||
indx = [cnd.search_index_entry for cnd in candidate_entries]
|
indx = [cnd.search_index_entry for cnd in candidate_entries]
|
||||||
mapping = {indx[ix]["key"]: cand for ix, cand in enumerate(candidate_entries)}
|
mapping = {indx[ix]["key"]: cand for ix, cand in enumerate(candidate_entries)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,81 +11,6 @@ Note that `accessing_obj` and `accessed_obj` can be any object type
|
||||||
with a lock variable/field, so be careful to not expect
|
with a lock variable/field, so be careful to not expect
|
||||||
a certain object type.
|
a certain object type.
|
||||||
|
|
||||||
|
|
||||||
**Appendix: MUX locks**
|
|
||||||
|
|
||||||
Below is a list nicked from the MUX help file on the locks available
|
|
||||||
in standard MUX. Most of these are not relevant to core Evennia since
|
|
||||||
locks in Evennia are considerably more flexible and can be implemented
|
|
||||||
on an individual command/typeclass basis rather than as globally
|
|
||||||
available like the MUX ones. So many of these are not available in
|
|
||||||
basic Evennia, but could all be implemented easily if needed for the
|
|
||||||
individual game.
|
|
||||||
|
|
||||||
```
|
|
||||||
MUX Name: Affects: Effect:
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
DefaultLock: Exits: controls who may traverse the exit to
|
|
||||||
its destination.
|
|
||||||
Evennia: "traverse:<lockfunc()>"
|
|
||||||
Rooms: controls whether the account sees the
|
|
||||||
SUCC or FAIL message for the room
|
|
||||||
following the room description when
|
|
||||||
looking at the room.
|
|
||||||
Evennia: Custom typeclass
|
|
||||||
Accounts/Things: controls who may GET the object.
|
|
||||||
Evennia: "get:<lockfunc()"
|
|
||||||
EnterLock: Accounts/Things: controls who may ENTER the object
|
|
||||||
Evennia:
|
|
||||||
GetFromLock: All but Exits: controls who may gets things from a
|
|
||||||
given location.
|
|
||||||
Evennia:
|
|
||||||
GiveLock: Accounts/Things: controls who may give the object.
|
|
||||||
Evennia:
|
|
||||||
LeaveLock: Accounts/Things: controls who may LEAVE the object.
|
|
||||||
Evennia:
|
|
||||||
LinkLock: All but Exits: controls who may link to the location
|
|
||||||
if the location is LINK_OK (for linking
|
|
||||||
exits or setting drop-tos) or ABODE (for
|
|
||||||
setting homes)
|
|
||||||
Evennia:
|
|
||||||
MailLock: Accounts: controls who may @mail the account.
|
|
||||||
Evennia:
|
|
||||||
OpenLock: All but Exits: controls who may open an exit.
|
|
||||||
Evennia:
|
|
||||||
PageLock: Accounts: controls who may page the account.
|
|
||||||
Evennia: "send:<lockfunc()>"
|
|
||||||
ParentLock: All: controls who may make @parent links to
|
|
||||||
the object.
|
|
||||||
Evennia: Typeclasses and
|
|
||||||
"puppet:<lockstring()>"
|
|
||||||
ReceiveLock: Accounts/Things: controls who may give things to the
|
|
||||||
object.
|
|
||||||
Evennia:
|
|
||||||
SpeechLock: All but Exits: controls who may speak in that location
|
|
||||||
Evennia:
|
|
||||||
TeloutLock: All but Exits: controls who may teleport out of the
|
|
||||||
location.
|
|
||||||
Evennia:
|
|
||||||
TportLock: Rooms/Things: controls who may teleport there
|
|
||||||
Evennia:
|
|
||||||
UseLock: All but Exits: controls who may USE the object, GIVE
|
|
||||||
the object money and have the PAY
|
|
||||||
attributes run, have their messages
|
|
||||||
heard and possibly acted on by LISTEN
|
|
||||||
and AxHEAR, and invoke $-commands
|
|
||||||
stored on the object.
|
|
||||||
Evennia: Commands and Cmdsets.
|
|
||||||
DropLock: All but rooms: controls who may drop that object.
|
|
||||||
Evennia:
|
|
||||||
VisibleLock: All: Controls object visibility when the
|
|
||||||
object is not dark and the looker
|
|
||||||
passes the lock. In DARK locations, the
|
|
||||||
object must also be set LIGHT and the
|
|
||||||
viewer must pass the VisibleLock.
|
|
||||||
Evennia: Room typeclass with
|
|
||||||
Dark/light script
|
|
||||||
```
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -112,16 +37,21 @@ def _to_account(accessing_obj):
|
||||||
|
|
||||||
|
|
||||||
def true(*args, **kwargs):
|
def true(*args, **kwargs):
|
||||||
"Always returns True."
|
"""
|
||||||
return True
|
Always returns True.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
def all(*args, **kwargs):
|
def all(*args, **kwargs):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def false(*args, **kwargs):
|
def false(*args, **kwargs):
|
||||||
"Always returns False"
|
"""
|
||||||
|
Always returns False
|
||||||
|
|
||||||
|
"""
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -129,6 +59,10 @@ def none(*args, **kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def superuser(*args, **kwargs):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def self(accessing_obj, accessed_obj, *args, **kwargs):
|
def self(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Check if accessing_obj is the same as accessed_obj
|
Check if accessing_obj is the same as accessed_obj
|
||||||
|
|
@ -167,7 +101,7 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
permission = args[0].lower()
|
permission = args[0].lower()
|
||||||
perms_object = accessing_obj.permissions.all()
|
perms_object = accessing_obj.permissions.all()
|
||||||
except (AttributeError, IndexError) as err:
|
except (AttributeError, IndexError):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
gtmode = kwargs.pop("_greater_than", False)
|
gtmode = kwargs.pop("_greater_than", False)
|
||||||
|
|
@ -644,17 +578,6 @@ def holds(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def superuser(*args, **kwargs):
|
|
||||||
"""
|
|
||||||
Only accepts an accesing_obj that is superuser (e.g. user #1)
|
|
||||||
|
|
||||||
Since a superuser would not ever reach this check (superusers
|
|
||||||
bypass the lock entirely), any user who gets this far cannot be a
|
|
||||||
superuser, hence we just return False. :)
|
|
||||||
"""
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def has_account(accessing_obj, accessed_obj, *args, **kwargs):
|
def has_account(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Only returns true if accessing_obj has_account is true, that is,
|
Only returns true if accessing_obj has_account is true, that is,
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ _LOCK_HANDLER = None
|
||||||
class LockException(Exception):
|
class LockException(Exception):
|
||||||
"""
|
"""
|
||||||
Raised during an error in a lock.
|
Raised during an error in a lock.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
@ -139,6 +140,7 @@ _LOCKFUNCS = {}
|
||||||
def _cache_lockfuncs():
|
def _cache_lockfuncs():
|
||||||
"""
|
"""
|
||||||
Updates the cache.
|
Updates the cache.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
global _LOCKFUNCS
|
global _LOCKFUNCS
|
||||||
_LOCKFUNCS = {}
|
_LOCKFUNCS = {}
|
||||||
|
|
@ -163,7 +165,7 @@ _RE_OK = re.compile(r"%s|and|or|not")
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class LockHandler(object):
|
class LockHandler:
|
||||||
"""
|
"""
|
||||||
This handler should be attached to all objects implementing
|
This handler should be attached to all objects implementing
|
||||||
permission checks, under the property 'lockhandler'.
|
permission checks, under the property 'lockhandler'.
|
||||||
|
|
@ -260,16 +262,13 @@ class LockHandler(object):
|
||||||
continue
|
continue
|
||||||
if access_type in locks:
|
if access_type in locks:
|
||||||
duplicates += 1
|
duplicates += 1
|
||||||
wlist.append(
|
wlist.append(_(
|
||||||
_(
|
"LockHandler on {obj}: access type '{access_type}' "
|
||||||
"LockHandler on %(obj)s: access type '%(access_type)s' changed from '%(source)s' to '%(goal)s' "
|
"changed from '{source}' to '{goal}' ".format(
|
||||||
% {
|
obj=self.obj,
|
||||||
"obj": self.obj,
|
access_type=access_type,
|
||||||
"access_type": access_type,
|
source=locks[access_type][2],
|
||||||
"source": locks[access_type][2],
|
goal=raw_lockstring))
|
||||||
"goal": raw_lockstring,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
||||||
if wlist and WARNING_LOG:
|
if wlist and WARNING_LOG:
|
||||||
|
|
@ -284,12 +283,14 @@ class LockHandler(object):
|
||||||
def _cache_locks(self, storage_lockstring):
|
def _cache_locks(self, storage_lockstring):
|
||||||
"""
|
"""
|
||||||
Store data
|
Store data
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.locks = self._parse_lockstring(storage_lockstring)
|
self.locks = self._parse_lockstring(storage_lockstring)
|
||||||
|
|
||||||
def _save_locks(self):
|
def _save_locks(self):
|
||||||
"""
|
"""
|
||||||
Store locks to obj
|
Store locks to obj
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()])
|
self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()])
|
||||||
|
|
||||||
|
|
@ -693,8 +694,7 @@ def check_lockstring(
|
||||||
access_type=access_type,
|
access_type=access_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
def check_perm(
|
def check_perm(obj, permission, no_superuser_bypass=False):
|
||||||
obj, permission, no_superuser_bypass=False):
|
|
||||||
"""
|
"""
|
||||||
Shortcut for checking if an object has the given `permission`. If the
|
Shortcut for checking if an object has the given `permission`. If the
|
||||||
permission is in `settings.PERMISSION_HIERARCHY`, the check passes
|
permission is in `settings.PERMISSION_HIERARCHY`, the check passes
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
Custom manager for Objects.
|
Custom manager for Objects.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from itertools import chain
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models.fields import exceptions
|
from django.db.models.fields import exceptions
|
||||||
|
|
@ -154,7 +153,8 @@ class ObjectDBManager(TypedObjectManager):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
attribute_name (str): Attribute key to search for.
|
attribute_name (str): Attribute key to search for.
|
||||||
attribute_value (any): Attribute value to search for. This can also be database objects.
|
attribute_value (any): Attribute value to search for. This can also be database
|
||||||
|
objects.
|
||||||
candidates (list, optional): Candidate objects to limit search to.
|
candidates (list, optional): Candidate objects to limit search to.
|
||||||
typeclasses (list, optional): Python pats to restrict matches with.
|
typeclasses (list, optional): Python pats to restrict matches with.
|
||||||
|
|
||||||
|
|
@ -591,6 +591,7 @@ class ObjectDBManager(TypedObjectManager):
|
||||||
"""
|
"""
|
||||||
Clear the db_sessid field of all objects having also the
|
Clear the db_sessid field of all objects having also the
|
||||||
db_account field set.
|
db_account field set.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.filter(db_sessid__isnull=False).update(db_sessid=None)
|
self.filter(db_sessid__isnull=False).update(db_sessid=None)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
Returns all exits from this object, i.e. all objects at this
|
Returns all exits from this object, i.e. all objects at this
|
||||||
location having the property destination != `None`.
|
location having the property destination != `None`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return [exi for exi in self.contents if exi.destination]
|
return [exi for exi in self.contents if exi.destination]
|
||||||
|
|
||||||
|
|
@ -345,6 +346,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
Returns:
|
Returns:
|
||||||
singular (str): The singular form to display.
|
singular (str): The singular form to display.
|
||||||
plural (str): The determined plural form of the key, including the count.
|
plural (str): The determined plural form of the key, including the count.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
plural_category = "plural_key"
|
plural_category = "plural_key"
|
||||||
key = kwargs.get("key", self.key)
|
key = kwargs.get("key", self.key)
|
||||||
|
|
@ -700,6 +702,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
Keyword arguments will be passed to the function for all objects.
|
Keyword arguments will be passed to the function for all objects.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
contents = self.contents
|
contents = self.contents
|
||||||
if exclude:
|
if exclude:
|
||||||
|
|
@ -947,6 +950,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
Destroys all of the exits and any exits pointing to this
|
Destroys all of the exits and any exits pointing to this
|
||||||
object as a destination.
|
object as a destination.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]:
|
for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]:
|
||||||
out_exit.delete()
|
out_exit.delete()
|
||||||
|
|
@ -957,6 +961,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
Moves all objects (accounts/things) to their home location or
|
Moves all objects (accounts/things) to their home location or
|
||||||
to default home.
|
to default home.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Gather up everything that thinks this is its location.
|
# Gather up everything that thinks this is its location.
|
||||||
default_home_id = int(settings.DEFAULT_HOME.lstrip("#"))
|
default_home_id = int(settings.DEFAULT_HOME.lstrip("#"))
|
||||||
|
|
@ -979,11 +984,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
# If for some reason it's still None...
|
# If for some reason it's still None...
|
||||||
if not home:
|
if not home:
|
||||||
string = "Missing default home, '%s(#%d)' "
|
|
||||||
string += "now has a null location."
|
|
||||||
obj.location = None
|
obj.location = None
|
||||||
obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin."))
|
obj.msg(_("Something went wrong! You are dumped into nowhere. Contact an admin."))
|
||||||
logger.log_err(string % (obj.name, obj.dbid))
|
logger.log_err("Missing default home - '{name}(#{dbid})' now "
|
||||||
|
"has a null location.".format(name=obj.name, dbid=obj.dbid))
|
||||||
return
|
return
|
||||||
|
|
||||||
if obj.has_account:
|
if obj.has_account:
|
||||||
|
|
@ -1539,7 +1543,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
if not source_location and self.location.has_account:
|
if not source_location and self.location.has_account:
|
||||||
# This was created from nowhere and added to an account's
|
# This was created from nowhere and added to an account's
|
||||||
# inventory; it's probably the result of a create command.
|
# inventory; it's probably the result of a create command.
|
||||||
string = "You now have %s in your possession." % self.get_display_name(self.location)
|
string = _("You now have {name} in your possession.").format(
|
||||||
|
name=self.get_display_name(self.location))
|
||||||
self.location.msg(string)
|
self.location.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -1547,9 +1552,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
if msg:
|
if msg:
|
||||||
string = msg
|
string = msg
|
||||||
else:
|
else:
|
||||||
string = "{object} arrives to {destination} from {origin}."
|
string = _("{object} arrives to {destination} from {origin}.")
|
||||||
else:
|
else:
|
||||||
string = "{object} arrives to {destination}."
|
string = _("{object} arrives to {destination}.")
|
||||||
|
|
||||||
origin = source_location
|
origin = source_location
|
||||||
destination = self.location
|
destination = self.location
|
||||||
|
|
@ -2157,7 +2162,7 @@ class DefaultCharacter(DefaultObject):
|
||||||
key = cls.normalize_name(key)
|
key = cls.normalize_name(key)
|
||||||
|
|
||||||
if not cls.validate_name(key):
|
if not cls.validate_name(key):
|
||||||
errors.append("Invalid character name.")
|
errors.append(_("Invalid character name."))
|
||||||
return obj, errors
|
return obj, errors
|
||||||
|
|
||||||
# Set the supplied key as the name of the intended object
|
# Set the supplied key as the name of the intended object
|
||||||
|
|
@ -2176,7 +2181,7 @@ class DefaultCharacter(DefaultObject):
|
||||||
# Check to make sure account does not have too many chars
|
# Check to make sure account does not have too many chars
|
||||||
if account:
|
if account:
|
||||||
if len(account.characters) >= settings.MAX_NR_CHARACTERS:
|
if len(account.characters) >= settings.MAX_NR_CHARACTERS:
|
||||||
errors.append("There are too many characters associated with this account.")
|
errors.append(_("There are too many characters associated with this account."))
|
||||||
return obj, errors
|
return obj, errors
|
||||||
|
|
||||||
# Create the Character
|
# Create the Character
|
||||||
|
|
@ -2202,10 +2207,10 @@ class DefaultCharacter(DefaultObject):
|
||||||
|
|
||||||
# If no description is set, set a default description
|
# If no description is set, set a default description
|
||||||
if description or not obj.db.desc:
|
if description or not obj.db.desc:
|
||||||
obj.db.desc = description if description else "This is a character."
|
obj.db.desc = description if description else _("This is a character.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append("An error occurred while creating this '%s' object." % key)
|
errors.append(f"An error occurred while creating object '{key} object.")
|
||||||
logger.log_err(e)
|
logger.log_err(e)
|
||||||
|
|
||||||
return obj, errors
|
return obj, errors
|
||||||
|
|
@ -2274,6 +2279,7 @@ class DefaultCharacter(DefaultObject):
|
||||||
Args:
|
Args:
|
||||||
account (Account): This is the connecting account.
|
account (Account): This is the connecting account.
|
||||||
session (Session): Session controlling the connection.
|
session (Session): Session controlling the connection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if (
|
if (
|
||||||
self.location is None
|
self.location is None
|
||||||
|
|
@ -2287,7 +2293,8 @@ class DefaultCharacter(DefaultObject):
|
||||||
self.db.prelogout_location = self.location # save location again to be sure.
|
self.db.prelogout_location = self.location # save location again to be sure.
|
||||||
else:
|
else:
|
||||||
account.msg(
|
account.msg(
|
||||||
"|r%s has no location and no home is set.|n" % self, session=session
|
_("|r{obj} has no location and no home is set.|n").format(obj=self),
|
||||||
|
session=session
|
||||||
) # Note to set home.
|
) # Note to set home.
|
||||||
|
|
||||||
def at_post_puppet(self, **kwargs):
|
def at_post_puppet(self, **kwargs):
|
||||||
|
|
@ -2305,11 +2312,12 @@ class DefaultCharacter(DefaultObject):
|
||||||
puppeting this Object.
|
puppeting this Object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.msg("\nYou become |c%s|n.\n" % self.name)
|
self.msg(_("\nYou become |c{name}|n.\n").format(name=self.key))
|
||||||
self.msg((self.at_look(self.location), {"type": "look"}), options=None)
|
self.msg((self.at_look(self.location), {"type": "look"}), options=None)
|
||||||
|
|
||||||
def message(obj, from_obj):
|
def message(obj, from_obj):
|
||||||
obj.msg("%s has entered the game." % self.get_display_name(obj), from_obj=from_obj)
|
obj.msg(_("{name} has entered the game.").format(name=self.get_display_name(obj)),
|
||||||
|
from_obj=from_obj)
|
||||||
|
|
||||||
self.location.for_contents(message, exclude=[self], from_obj=self)
|
self.location.for_contents(message, exclude=[self], from_obj=self)
|
||||||
|
|
||||||
|
|
@ -2332,7 +2340,8 @@ class DefaultCharacter(DefaultObject):
|
||||||
if self.location:
|
if self.location:
|
||||||
|
|
||||||
def message(obj, from_obj):
|
def message(obj, from_obj):
|
||||||
obj.msg("%s has left the game." % self.get_display_name(obj), from_obj=from_obj)
|
obj.msg(_("{name} has left the game.").format(name=self.get_display_name(obj)),
|
||||||
|
from_obj=from_obj)
|
||||||
|
|
||||||
self.location.for_contents(message, exclude=[self], from_obj=self)
|
self.location.for_contents(message, exclude=[self], from_obj=self)
|
||||||
self.db.prelogout_location = self.location
|
self.db.prelogout_location = self.location
|
||||||
|
|
@ -2343,6 +2352,7 @@ class DefaultCharacter(DefaultObject):
|
||||||
"""
|
"""
|
||||||
Returns the idle time of the least idle session in seconds. If
|
Returns the idle time of the least idle session in seconds. If
|
||||||
no sessions are connected it returns nothing.
|
no sessions are connected it returns nothing.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
idle = [session.cmd_last_visible for session in self.sessions.all()]
|
idle = [session.cmd_last_visible for session in self.sessions.all()]
|
||||||
if idle:
|
if idle:
|
||||||
|
|
@ -2354,6 +2364,7 @@ class DefaultCharacter(DefaultObject):
|
||||||
"""
|
"""
|
||||||
Returns the maximum connection time of all connected sessions
|
Returns the maximum connection time of all connected sessions
|
||||||
in seconds. Returns nothing if there are no sessions.
|
in seconds. Returns nothing if there are no sessions.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
conn = [session.conn_time for session in self.sessions.all()]
|
conn = [session.conn_time for session in self.sessions.all()]
|
||||||
if conn:
|
if conn:
|
||||||
|
|
@ -2447,7 +2458,7 @@ class DefaultRoom(DefaultObject):
|
||||||
|
|
||||||
# If no description is set, set a default description
|
# If no description is set, set a default description
|
||||||
if description or not obj.db.desc:
|
if description or not obj.db.desc:
|
||||||
obj.db.desc = description if description else "This is a room."
|
obj.db.desc = description if description else _("This is a room.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append("An error occurred while creating this '%s' object." % key)
|
errors.append("An error occurred while creating this '%s' object." % key)
|
||||||
|
|
@ -2653,7 +2664,7 @@ class DefaultExit(DefaultObject):
|
||||||
|
|
||||||
# If no description is set, set a default description
|
# If no description is set, set a default description
|
||||||
if description or not obj.db.desc:
|
if description or not obj.db.desc:
|
||||||
obj.db.desc = description if description else "This is an exit."
|
obj.db.desc = description if description else _("This is an exit.")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errors.append("An error occurred while creating this '%s' object." % key)
|
errors.append("An error occurred while creating this '%s' object." % key)
|
||||||
|
|
@ -2750,4 +2761,4 @@ class DefaultExit(DefaultObject):
|
||||||
read for an error string instead.
|
read for an error string instead.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
traversing_object.msg("You cannot go there.")
|
traversing_object.msg(_("You cannot go there."))
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,6 @@ def protfunc_callable_protkey(*args, **kwargs):
|
||||||
return prot_value
|
return prot_value
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# this is picked up by FuncParser
|
# this is picked up by FuncParser
|
||||||
FUNCPARSER_CALLABLES = {
|
FUNCPARSER_CALLABLES = {
|
||||||
"protkey": protfunc_callable_protkey,
|
"protkey": protfunc_callable_protkey,
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
from ast import literal_eval
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q, Subquery
|
from django.db.models import Q
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from evennia.scripts.scripts import DefaultScript
|
from evennia.scripts.scripts import DefaultScript
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.typeclasses.attributes import Attribute
|
from evennia.typeclasses.attributes import Attribute
|
||||||
|
|
@ -22,10 +22,6 @@ from evennia.utils.utils import (
|
||||||
make_iter,
|
make_iter,
|
||||||
is_iter,
|
is_iter,
|
||||||
dbid_to_obj,
|
dbid_to_obj,
|
||||||
callables_from_module,
|
|
||||||
get_all_typeclasses,
|
|
||||||
to_str,
|
|
||||||
dbref,
|
|
||||||
justify,
|
justify,
|
||||||
class_from_module,
|
class_from_module,
|
||||||
)
|
)
|
||||||
|
|
@ -84,7 +80,6 @@ def homogenize_prototype(prototype, custom_keys=None):
|
||||||
"""
|
"""
|
||||||
Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form.
|
Homogenize the more free-form prototype supported pre Evennia 0.7 into the stricter form.
|
||||||
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
prototype (dict): Prototype.
|
prototype (dict): Prototype.
|
||||||
custom_keys (list, optional): Custom keys which should not be interpreted as attrs, beyond
|
custom_keys (list, optional): Custom keys which should not be interpreted as attrs, beyond
|
||||||
|
|
@ -211,6 +206,7 @@ def load_module_prototypes():
|
||||||
class DbPrototype(DefaultScript):
|
class DbPrototype(DefaultScript):
|
||||||
"""
|
"""
|
||||||
This stores a single prototype, in an Attribute `prototype`.
|
This stores a single prototype, in an Attribute `prototype`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def at_script_creation(self):
|
def at_script_creation(self):
|
||||||
|
|
@ -262,7 +258,7 @@ def save_prototype(prototype):
|
||||||
|
|
||||||
prototype_key = in_prototype.get("prototype_key")
|
prototype_key = in_prototype.get("prototype_key")
|
||||||
if not prototype_key:
|
if not prototype_key:
|
||||||
raise ValidationError("Prototype requires a prototype_key")
|
raise ValidationError(_("Prototype requires a prototype_key"))
|
||||||
|
|
||||||
prototype_key = str(prototype_key).lower()
|
prototype_key = str(prototype_key).lower()
|
||||||
|
|
||||||
|
|
@ -270,7 +266,8 @@ def save_prototype(prototype):
|
||||||
if prototype_key in _MODULE_PROTOTYPES:
|
if prototype_key in _MODULE_PROTOTYPES:
|
||||||
mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A")
|
mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A")
|
||||||
raise PermissionError(
|
raise PermissionError(
|
||||||
"{} is a read-only prototype " "(defined as code in {}).".format(prototype_key, mod)
|
_("{protkey} is a read-only prototype " "(defined as code in {module}).").format(
|
||||||
|
protkey=prototype_key, module=mod)
|
||||||
)
|
)
|
||||||
|
|
||||||
# make sure meta properties are included with defaults
|
# make sure meta properties are included with defaults
|
||||||
|
|
@ -337,20 +334,21 @@ def delete_prototype(prototype_key, caller=None):
|
||||||
if prototype_key in _MODULE_PROTOTYPES:
|
if prototype_key in _MODULE_PROTOTYPES:
|
||||||
mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key.lower(), "N/A")
|
mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key.lower(), "N/A")
|
||||||
raise PermissionError(
|
raise PermissionError(
|
||||||
"{} is a read-only prototype " "(defined as code in {}).".format(prototype_key, mod)
|
_("{protkey} is a read-only prototype " "(defined as code in {module}).").format(
|
||||||
|
protkey=prototype_key, module=mod)
|
||||||
)
|
)
|
||||||
|
|
||||||
stored_prototype = DbPrototype.objects.filter(db_key__iexact=prototype_key)
|
stored_prototype = DbPrototype.objects.filter(db_key__iexact=prototype_key)
|
||||||
|
|
||||||
if not stored_prototype:
|
if not stored_prototype:
|
||||||
raise PermissionError("Prototype {} was not found.".format(prototype_key))
|
raise PermissionError(_("Prototype {} was not found.").format(prototype_key))
|
||||||
|
|
||||||
stored_prototype = stored_prototype[0]
|
stored_prototype = stored_prototype[0]
|
||||||
if caller:
|
if caller:
|
||||||
if not stored_prototype.access(caller, "edit"):
|
if not stored_prototype.access(caller, "edit"):
|
||||||
raise PermissionError(
|
raise PermissionError(
|
||||||
"{} needs explicit 'edit' permissions to "
|
_("{} needs explicit 'edit' permissions to "
|
||||||
"delete prototype {}.".format(caller, prototype_key)
|
"delete prototype {}.").format(caller, prototype_key)
|
||||||
)
|
)
|
||||||
stored_prototype.delete()
|
stored_prototype.delete()
|
||||||
return True
|
return True
|
||||||
|
|
@ -449,7 +447,11 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
||||||
nmodules = len(module_prototypes)
|
nmodules = len(module_prototypes)
|
||||||
ndbprots = db_matches.count()
|
ndbprots = db_matches.count()
|
||||||
if nmodules + ndbprots != 1:
|
if nmodules + ndbprots != 1:
|
||||||
raise KeyError(f"Found {nmodules + ndbprots} matching prototypes {module_prototypes}.")
|
raise KeyError(_(
|
||||||
|
"Found {num} matching prototypes {module_prototypes}.").format(
|
||||||
|
num=nmodules + ndbprots,
|
||||||
|
module_prototypes=module_prototypes)
|
||||||
|
)
|
||||||
|
|
||||||
if return_iterators:
|
if return_iterators:
|
||||||
# trying to get the entire set of prototypes - we must paginate
|
# trying to get the entire set of prototypes - we must paginate
|
||||||
|
|
@ -479,10 +481,14 @@ class PrototypeEvMore(EvMore):
|
||||||
Listing 1000+ prototypes can be very slow. So we customize EvMore to
|
Listing 1000+ prototypes can be very slow. So we customize EvMore to
|
||||||
display an EvTable per paginated page rather than to try creating an
|
display an EvTable per paginated page rather than to try creating an
|
||||||
EvTable for the entire dataset and then paginate it.
|
EvTable for the entire dataset and then paginate it.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, caller, *args, session=None, **kwargs):
|
def __init__(self, caller, *args, session=None, **kwargs):
|
||||||
"""Store some extra properties on the EvMore class"""
|
"""
|
||||||
|
Store some extra properties on the EvMore class
|
||||||
|
|
||||||
|
"""
|
||||||
self.show_non_use = kwargs.pop("show_non_use", False)
|
self.show_non_use = kwargs.pop("show_non_use", False)
|
||||||
self.show_non_edit = kwargs.pop("show_non_edit", False)
|
self.show_non_edit = kwargs.pop("show_non_edit", False)
|
||||||
super().__init__(caller, *args, session=session, **kwargs)
|
super().__init__(caller, *args, session=session, **kwargs)
|
||||||
|
|
@ -493,6 +499,7 @@ class PrototypeEvMore(EvMore):
|
||||||
and we must handle these separately since they cannot be paginated in the same
|
and we must handle these separately since they cannot be paginated in the same
|
||||||
way. We will build the prototypes so that the db-prototypes come first (they
|
way. We will build the prototypes so that the db-prototypes come first (they
|
||||||
are likely the most volatile), followed by the mod-prototypes.
|
are likely the most volatile), followed by the mod-prototypes.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dbprot_query, modprot_list = inp
|
dbprot_query, modprot_list = inp
|
||||||
# set the number of entries per page to half the reported height of the screen
|
# set the number of entries per page to half the reported height of the screen
|
||||||
|
|
@ -514,6 +521,7 @@ class PrototypeEvMore(EvMore):
|
||||||
"""
|
"""
|
||||||
The listing is separated in db/mod prototypes, so we need to figure out which
|
The listing is separated in db/mod prototypes, so we need to figure out which
|
||||||
one to pick based on the page number. Also, pageno starts from 0.
|
one to pick based on the page number. Also, pageno starts from 0.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dbprot_pages, modprot_list = self._data
|
dbprot_pages, modprot_list = self._data
|
||||||
|
|
||||||
|
|
@ -522,15 +530,16 @@ class PrototypeEvMore(EvMore):
|
||||||
else:
|
else:
|
||||||
# get the correct slice, adjusted for the db-prototypes
|
# get the correct slice, adjusted for the db-prototypes
|
||||||
pageno = max(0, pageno - self._npages_db)
|
pageno = max(0, pageno - self._npages_db)
|
||||||
return modprot_list[pageno * self.height : pageno * self.height + self.height]
|
return modprot_list[pageno * self.height: pageno * self.height + self.height]
|
||||||
|
|
||||||
def page_formatter(self, page):
|
def page_formatter(self, page):
|
||||||
"""Input is a queryset page from django.Paginator"""
|
"""
|
||||||
|
Input is a queryset page from django.Paginator
|
||||||
|
|
||||||
|
"""
|
||||||
caller = self._caller
|
caller = self._caller
|
||||||
|
|
||||||
# get use-permissions of readonly attributes (edit is always False)
|
# get use-permissions of readonly attributes (edit is always False)
|
||||||
display_tuples = []
|
|
||||||
|
|
||||||
table = EvTable(
|
table = EvTable(
|
||||||
"|wKey|n",
|
"|wKey|n",
|
||||||
"|wSpawn/Edit|n",
|
"|wSpawn/Edit|n",
|
||||||
|
|
@ -599,7 +608,7 @@ def list_prototypes(
|
||||||
dbprot_query, modprot_list = search_prototype(key, tags, return_iterators=True)
|
dbprot_query, modprot_list = search_prototype(key, tags, return_iterators=True)
|
||||||
|
|
||||||
if not dbprot_query and not modprot_list:
|
if not dbprot_query and not modprot_list:
|
||||||
caller.msg("No prototypes found.", session=session)
|
caller.msg(_("No prototypes found."), session=session)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# get specific prototype (one value or exception)
|
# get specific prototype (one value or exception)
|
||||||
|
|
@ -650,7 +659,7 @@ def validate_prototype(
|
||||||
protkey = protkey and protkey.lower() or prototype.get("prototype_key", None)
|
protkey = protkey and protkey.lower() or prototype.get("prototype_key", None)
|
||||||
|
|
||||||
if strict and not bool(protkey):
|
if strict and not bool(protkey):
|
||||||
_flags["errors"].append("Prototype lacks a `prototype_key`.")
|
_flags["errors"].append(_("Prototype lacks a `prototype_key`."))
|
||||||
protkey = "[UNSET]"
|
protkey = "[UNSET]"
|
||||||
|
|
||||||
typeclass = prototype.get("typeclass")
|
typeclass = prototype.get("typeclass")
|
||||||
|
|
@ -659,12 +668,13 @@ def validate_prototype(
|
||||||
if strict and not (typeclass or prototype_parent):
|
if strict and not (typeclass or prototype_parent):
|
||||||
if is_prototype_base:
|
if is_prototype_base:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
"Prototype {} requires `typeclass` " "or 'prototype_parent'.".format(protkey)
|
_("Prototype {protkey} requires `typeclass` " "or 'prototype_parent'.").format(
|
||||||
|
protkey=protkey)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_flags["warnings"].append(
|
_flags["warnings"].append(
|
||||||
"Prototype {} can only be used as a mixin since it lacks "
|
_("Prototype {protkey} can only be used as a mixin since it lacks "
|
||||||
"a typeclass or a prototype_parent.".format(protkey)
|
"a typeclass or a prototype_parent.").format(protkey=protkey)
|
||||||
)
|
)
|
||||||
|
|
||||||
if strict and typeclass:
|
if strict and typeclass:
|
||||||
|
|
@ -672,9 +682,9 @@ def validate_prototype(
|
||||||
class_from_module(typeclass)
|
class_from_module(typeclass)
|
||||||
except ImportError as err:
|
except ImportError as err:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
"{}: Prototype {} is based on typeclass {}, which could not be imported!".format(
|
_("{err}: Prototype {protkey} is based on typeclass {typeclass}, "
|
||||||
err, protkey, typeclass
|
"which could not be imported!").format(
|
||||||
)
|
err=err, protkey=protkey, typeclass=typeclass)
|
||||||
)
|
)
|
||||||
|
|
||||||
# recursively traverese prototype_parent chain
|
# recursively traverese prototype_parent chain
|
||||||
|
|
@ -682,19 +692,22 @@ def validate_prototype(
|
||||||
for protstring in make_iter(prototype_parent):
|
for protstring in make_iter(prototype_parent):
|
||||||
protstring = protstring.lower()
|
protstring = protstring.lower()
|
||||||
if protkey is not None and protstring == protkey:
|
if protkey is not None and protstring == protkey:
|
||||||
_flags["errors"].append("Prototype {} tries to parent itself.".format(protkey))
|
_flags["errors"].append(_("Prototype {protkey} tries to parent itself.").format(
|
||||||
|
protkey=protkey))
|
||||||
protparent = protparents.get(protstring)
|
protparent = protparents.get(protstring)
|
||||||
if not protparent:
|
if not protparent:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
"Prototype {}'s prototype_parent '{}' was not found.".format(protkey, protstring)
|
_("Prototype {protkey}'s prototype_parent '{parent}' was not found.").format(
|
||||||
|
protkey=protkey, parent=protstring)
|
||||||
)
|
)
|
||||||
if id(prototype) in _flags["visited"]:
|
if id(prototype) in _flags["visited"]:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
"{} has infinite nesting of prototypes.".format(protkey or prototype)
|
_("{protkey} has infinite nesting of prototypes.").format(
|
||||||
|
protkey=protkey or prototype)
|
||||||
)
|
)
|
||||||
|
|
||||||
if _flags["errors"]:
|
if _flags["errors"]:
|
||||||
raise RuntimeError("Error: " + "\nError: ".join(_flags["errors"]))
|
raise RuntimeError(_("Error: ") + _("\nError: ").join(_flags["errors"]))
|
||||||
_flags["visited"].append(id(prototype))
|
_flags["visited"].append(id(prototype))
|
||||||
_flags["depth"] += 1
|
_flags["depth"] += 1
|
||||||
validate_prototype(
|
validate_prototype(
|
||||||
|
|
@ -709,16 +722,16 @@ def validate_prototype(
|
||||||
# if we get back to the current level without a typeclass it's an error.
|
# if we get back to the current level without a typeclass it's an error.
|
||||||
if strict and is_prototype_base and _flags["depth"] <= 0 and not _flags["typeclass"]:
|
if strict and is_prototype_base and _flags["depth"] <= 0 and not _flags["typeclass"]:
|
||||||
_flags["errors"].append(
|
_flags["errors"].append(
|
||||||
"Prototype {} has no `typeclass` defined anywhere in its parent\n "
|
_("Prototype {protkey} has no `typeclass` defined anywhere in its parent\n "
|
||||||
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
||||||
"prototype with a typeclass.".format(protkey)
|
"prototype with a typeclass.").format(protkey=protkey)
|
||||||
)
|
)
|
||||||
|
|
||||||
if _flags["depth"] <= 0:
|
if _flags["depth"] <= 0:
|
||||||
if _flags["errors"]:
|
if _flags["errors"]:
|
||||||
raise RuntimeError("Error: " + "\nError: ".join(_flags["errors"]))
|
raise RuntimeError(_("Error: " + "\nError: ").join(_flags["errors"]))
|
||||||
if _flags["warnings"]:
|
if _flags["warnings"]:
|
||||||
raise RuntimeWarning("Warning: " + "\nWarning: ".join(_flags["warnings"]))
|
raise RuntimeWarning(_("Warning: " + "\nWarning: ").join(_flags["warnings"]))
|
||||||
|
|
||||||
# make sure prototype_locks are set to defaults
|
# make sure prototype_locks are set to defaults
|
||||||
prototype_locks = [
|
prototype_locks = [
|
||||||
|
|
@ -831,10 +844,10 @@ def prototype_to_str(prototype):
|
||||||
category=category if category else "|wNone|n"
|
category=category if category else "|wNone|n"
|
||||||
)
|
)
|
||||||
out.append(
|
out.append(
|
||||||
"{attrkey}{cat_locks} |c=|n {value}".format(
|
"{attrkey}{cat_locks}{locks} |c=|n {value}".format(
|
||||||
attrkey=attrkey,
|
attrkey=attrkey,
|
||||||
cat_locks=cat_locks,
|
cat_locks=cat_locks,
|
||||||
locks=locks if locks else "|wNone|n",
|
locks=" |w(locks:|n {locks})".format(locks=locks) if locks else "",
|
||||||
value=value,
|
value=value,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -40,8 +40,8 @@ Possible keywords are:
|
||||||
supported are 'edit' and 'use'.
|
supported are 'edit' and 'use'.
|
||||||
prototype_tags(list, optional): List of tags or tuples (tag, category) used to group prototype
|
prototype_tags(list, optional): List of tags or tuples (tag, category) used to group prototype
|
||||||
in listings
|
in listings
|
||||||
prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent prototype, or
|
prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent
|
||||||
a list of parents, for multiple left-to-right inheritance.
|
prototype, or a list of parents, for multiple left-to-right inheritance.
|
||||||
prototype: Deprecated. Same meaning as 'parent'.
|
prototype: Deprecated. Same meaning as 'parent'.
|
||||||
|
|
||||||
typeclass (str or callable, optional): if not set, will use typeclass of parent prototype or use
|
typeclass (str or callable, optional): if not set, will use typeclass of parent prototype or use
|
||||||
|
|
@ -138,6 +138,7 @@ import hashlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
import evennia
|
import evennia
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
|
|
@ -355,8 +356,8 @@ def prototype_diff(prototype1, prototype2, maxdepth=2, homogenize=False, implici
|
||||||
This is most useful for displaying.
|
This is most useful for displaying.
|
||||||
implicit_keep (bool, optional): If set, the resulting diff will assume KEEP unless the new
|
implicit_keep (bool, optional): If set, the resulting diff will assume KEEP unless the new
|
||||||
prototype explicitly change them. That is, if a key exists in `prototype1` and
|
prototype explicitly change them. That is, if a key exists in `prototype1` and
|
||||||
not in `prototype2`, it will not be REMOVEd but set to KEEP instead. This is particularly
|
not in `prototype2`, it will not be REMOVEd but set to KEEP instead. This is
|
||||||
useful for auto-generated prototypes when updating objects.
|
particularly useful for auto-generated prototypes when updating objects.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
diff (dict): A structure detailing how to convert prototype1 to prototype2. All
|
diff (dict): A structure detailing how to convert prototype1 to prototype2. All
|
||||||
|
|
@ -469,8 +470,8 @@ def flatten_diff(diff):
|
||||||
out.extend(_get_all_nested_diff_instructions(val))
|
out.extend(_get_all_nested_diff_instructions(val))
|
||||||
else:
|
else:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Diff contains non-dicts that are not on the "
|
_("Diff contains non-dicts that are not on the "
|
||||||
"form (old, new, inst): {}".format(diffpart)
|
"form (old, new, inst): {diffpart}").format(diffpart)
|
||||||
)
|
)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
@ -693,11 +694,13 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None,
|
||||||
elif key == "permissions":
|
elif key == "permissions":
|
||||||
if directive == "REPLACE":
|
if directive == "REPLACE":
|
||||||
obj.permissions.clear()
|
obj.permissions.clear()
|
||||||
obj.permissions.batch_add(*(init_spawn_value(perm, str, caller=caller) for perm in val))
|
obj.permissions.batch_add(*(init_spawn_value(perm, str, caller=caller)
|
||||||
|
for perm in val))
|
||||||
elif key == "aliases":
|
elif key == "aliases":
|
||||||
if directive == "REPLACE":
|
if directive == "REPLACE":
|
||||||
obj.aliases.clear()
|
obj.aliases.clear()
|
||||||
obj.aliases.batch_add(*(init_spawn_value(alias, str, caller=caller) for alias in val))
|
obj.aliases.batch_add(*(init_spawn_value(alias, str, caller=caller)
|
||||||
|
for alias in val))
|
||||||
elif key == "tags":
|
elif key == "tags":
|
||||||
if directive == "REPLACE":
|
if directive == "REPLACE":
|
||||||
obj.tags.clear()
|
obj.tags.clear()
|
||||||
|
|
@ -923,7 +926,8 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, caller=caller)
|
create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, caller=caller)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
create_kwargs["db_home"] = init_spawn_value(settings.DEFAULT_HOME, value_to_obj, caller=caller)
|
create_kwargs["db_home"] = init_spawn_value(
|
||||||
|
settings.DEFAULT_HOME, value_to_obj, caller=caller)
|
||||||
except ObjectDB.DoesNotExist:
|
except ObjectDB.DoesNotExist:
|
||||||
# settings.DEFAULT_HOME not existing is common for unittests
|
# settings.DEFAULT_HOME not existing is common for unittests
|
||||||
pass
|
pass
|
||||||
|
|
@ -945,7 +949,8 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
val = prot.pop("tags", [])
|
val = prot.pop("tags", [])
|
||||||
tags = []
|
tags = []
|
||||||
for (tag, category, *data) in val:
|
for (tag, category, *data) in val:
|
||||||
tags.append((init_spawn_value(tag, str, caller=caller), category, data[0] if data else None))
|
tags.append((init_spawn_value(tag, str, caller=caller), category, data[0]
|
||||||
|
if data else None))
|
||||||
|
|
||||||
prototype_key = prototype.get("prototype_key", None)
|
prototype_key = prototype.get("prototype_key", None)
|
||||||
if prototype_key:
|
if prototype_key:
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,13 @@ class MonitorHandler(object):
|
||||||
"""
|
"""
|
||||||
This is a resource singleton that allows for registering
|
This is a resource singleton that allows for registering
|
||||||
callbacks for when a field or Attribute is updated (saved).
|
callbacks for when a field or Attribute is updated (saved).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
Initialize the handler.
|
Initialize the handler.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.savekey = "_monitorhandler_save"
|
self.savekey = "_monitorhandler_save"
|
||||||
self.monitors = defaultdict(lambda: defaultdict(dict))
|
self.monitors = defaultdict(lambda: defaultdict(dict))
|
||||||
|
|
|
||||||
|
|
@ -62,22 +62,28 @@ class TaskHandlerTask:
|
||||||
return TASK_HANDLER.get_deferred(self.task_id)
|
return TASK_HANDLER.get_deferred(self.task_id)
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
"""Pause the callback of a task.
|
"""
|
||||||
To resume use TaskHandlerTask.unpause
|
Pause the callback of a task.
|
||||||
|
To resume use `TaskHandlerTask.unpause`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
d = self.deferred
|
d = self.deferred
|
||||||
if d:
|
if d:
|
||||||
d.pause()
|
d.pause()
|
||||||
|
|
||||||
def unpause(self):
|
def unpause(self):
|
||||||
"""Unpause a task, run the task if it has passed delay time."""
|
"""
|
||||||
|
Unpause a task, run the task if it has passed delay time.
|
||||||
|
|
||||||
|
"""
|
||||||
d = self.deferred
|
d = self.deferred
|
||||||
if d:
|
if d:
|
||||||
d.unpause()
|
d.unpause()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def paused(self):
|
def paused(self):
|
||||||
"""A task attribute to check if the deferred instance of a task has been paused.
|
"""
|
||||||
|
A task attribute to check if the deferred instance of a task has been paused.
|
||||||
|
|
||||||
This exists to mock usage of a twisted deferred object.
|
This exists to mock usage of a twisted deferred object.
|
||||||
|
|
||||||
|
|
@ -93,7 +99,8 @@ class TaskHandlerTask:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def do_task(self):
|
def do_task(self):
|
||||||
"""Execute the task (call its callback).
|
"""
|
||||||
|
Execute the task (call its callback).
|
||||||
If calling before timedelay, cancel the deferred instance affliated to this task.
|
If calling before timedelay, cancel the deferred instance affliated to this task.
|
||||||
Remove the task from the dictionary of current tasks on a successful
|
Remove the task from the dictionary of current tasks on a successful
|
||||||
callback.
|
callback.
|
||||||
|
|
@ -106,7 +113,8 @@ class TaskHandlerTask:
|
||||||
return TASK_HANDLER.do_task(self.task_id)
|
return TASK_HANDLER.do_task(self.task_id)
|
||||||
|
|
||||||
def call(self):
|
def call(self):
|
||||||
"""Call the callback of a task.
|
"""
|
||||||
|
Call the callback of a task.
|
||||||
Leave the task unaffected otherwise.
|
Leave the task unaffected otherwise.
|
||||||
This does not use the task's deferred instance.
|
This does not use the task's deferred instance.
|
||||||
The only requirement is that the task exist in task handler.
|
The only requirement is that the task exist in task handler.
|
||||||
|
|
@ -173,7 +181,8 @@ class TaskHandlerTask:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def exists(self):
|
def exists(self):
|
||||||
"""Check if a task exists.
|
"""
|
||||||
|
Check if a task exists.
|
||||||
Most task handler methods check for existence for you.
|
Most task handler methods check for existence for you.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -183,7 +192,8 @@ class TaskHandlerTask:
|
||||||
return TASK_HANDLER.exists(self.task_id)
|
return TASK_HANDLER.exists(self.task_id)
|
||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
""" Returns the global id for this task. For use with
|
"""
|
||||||
|
Returns the global id for this task. For use with
|
||||||
`evennia.scripts.taskhandler.TASK_HANDLER`.
|
`evennia.scripts.taskhandler.TASK_HANDLER`.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -215,7 +225,7 @@ class TaskHandler(object):
|
||||||
self.clock = reactor
|
self.clock = reactor
|
||||||
# number of seconds before an uncalled canceled task is removed from TaskHandler
|
# number of seconds before an uncalled canceled task is removed from TaskHandler
|
||||||
self.stale_timeout = 60
|
self.stale_timeout = 60
|
||||||
self._now = False # used in unit testing to manually set now time
|
self._now = False # used in unit testing to manually set now time
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
"""Load from the ServerConfig.
|
"""Load from the ServerConfig.
|
||||||
|
|
@ -271,7 +281,10 @@ class TaskHandler(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
"""Save the tasks in ServerConfig."""
|
"""
|
||||||
|
Save the tasks in ServerConfig.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items():
|
for task_id, (date, callback, args, kwargs, persistent, _) in self.tasks.items():
|
||||||
if task_id in self.to_save:
|
if task_id in self.to_save:
|
||||||
|
|
@ -286,14 +299,12 @@ class TaskHandler(object):
|
||||||
callback = (obj, name)
|
callback = (obj, name)
|
||||||
|
|
||||||
# Check if callback can be pickled. args and kwargs have been checked
|
# Check if callback can be pickled. args and kwargs have been checked
|
||||||
safe_callback = None
|
|
||||||
|
|
||||||
|
|
||||||
self.to_save[task_id] = dbserialize((date, callback, args, kwargs))
|
self.to_save[task_id] = dbserialize((date, callback, args, kwargs))
|
||||||
ServerConfig.objects.conf("delayed_tasks", self.to_save)
|
ServerConfig.objects.conf("delayed_tasks", self.to_save)
|
||||||
|
|
||||||
def add(self, timedelay, callback, *args, **kwargs):
|
def add(self, timedelay, callback, *args, **kwargs):
|
||||||
"""Add a new task.
|
"""
|
||||||
|
Add a new task.
|
||||||
|
|
||||||
If the persistent kwarg is truthy:
|
If the persistent kwarg is truthy:
|
||||||
The callback, args and values for kwarg will be serialized. Type
|
The callback, args and values for kwarg will be serialized. Type
|
||||||
|
|
@ -399,7 +410,8 @@ class TaskHandler(object):
|
||||||
return TaskHandlerTask(task_id)
|
return TaskHandlerTask(task_id)
|
||||||
|
|
||||||
def exists(self, task_id):
|
def exists(self, task_id):
|
||||||
"""Check if a task exists.
|
"""
|
||||||
|
Check if a task exists.
|
||||||
Most task handler methods check for existence for you.
|
Most task handler methods check for existence for you.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -415,7 +427,8 @@ class TaskHandler(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def active(self, task_id):
|
def active(self, task_id):
|
||||||
"""Check if a task is active (has not been called yet).
|
"""
|
||||||
|
Check if a task is active (has not been called yet).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
task_id (int): an existing task ID.
|
task_id (int): an existing task ID.
|
||||||
|
|
@ -433,7 +446,8 @@ class TaskHandler(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def cancel(self, task_id):
|
def cancel(self, task_id):
|
||||||
"""Stop a task from automatically executing.
|
"""
|
||||||
|
Stop a task from automatically executing.
|
||||||
This will not remove the task.
|
This will not remove the task.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -459,7 +473,8 @@ class TaskHandler(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def remove(self, task_id):
|
def remove(self, task_id):
|
||||||
"""Remove a task without executing it.
|
"""
|
||||||
|
Remove a task without executing it.
|
||||||
Deletes the instance of the task's deferred.
|
Deletes the instance of the task's deferred.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -485,8 +500,8 @@ class TaskHandler(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def clear(self, save=True, cancel=True):
|
def clear(self, save=True, cancel=True):
|
||||||
"""clear all tasks.
|
"""
|
||||||
By default tasks are canceled and removed from the database also.
|
Clear all tasks. By default tasks are canceled and removed from the database as well.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
save=True (bool): Should changes to persistent tasks be saved to database.
|
save=True (bool): Should changes to persistent tasks be saved to database.
|
||||||
|
|
@ -508,7 +523,8 @@ class TaskHandler(object):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def call_task(self, task_id):
|
def call_task(self, task_id):
|
||||||
"""Call the callback of a task.
|
"""
|
||||||
|
Call the callback of a task.
|
||||||
Leave the task unaffected otherwise.
|
Leave the task unaffected otherwise.
|
||||||
This does not use the task's deferred instance.
|
This does not use the task's deferred instance.
|
||||||
The only requirement is that the task exist in task handler.
|
The only requirement is that the task exist in task handler.
|
||||||
|
|
@ -528,7 +544,8 @@ class TaskHandler(object):
|
||||||
return callback(*args, **kwargs)
|
return callback(*args, **kwargs)
|
||||||
|
|
||||||
def do_task(self, task_id):
|
def do_task(self, task_id):
|
||||||
"""Execute the task (call its callback).
|
"""
|
||||||
|
Execute the task (call its callback).
|
||||||
If calling before timedelay cancel the deferred instance affliated to this task.
|
If calling before timedelay cancel the deferred instance affliated to this task.
|
||||||
Remove the task from the dictionary of current tasks on a successful
|
Remove the task from the dictionary of current tasks on a successful
|
||||||
callback.
|
callback.
|
||||||
|
|
@ -573,7 +590,8 @@ class TaskHandler(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_delays(self):
|
def create_delays(self):
|
||||||
"""Create the delayed tasks for the persistent tasks.
|
"""
|
||||||
|
Create the delayed tasks for the persistent tasks.
|
||||||
This method should be automatically called when Evennia starts.
|
This method should be automatically called when Evennia starts.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ manager's conf() method.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
from evennia.utils.idmapper.models import WeakSharedMemoryModel
|
from evennia.utils.idmapper.models import WeakSharedMemoryModel
|
||||||
from evennia.utils import logger, utils
|
from evennia.utils import logger, utils
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,10 @@ def loads(data):
|
||||||
|
|
||||||
|
|
||||||
def _get_logger():
|
def _get_logger():
|
||||||
"Delay import of logger until absolutely necessary"
|
"""
|
||||||
|
Delay import of logger until absolutely necessary
|
||||||
|
|
||||||
|
"""
|
||||||
global _LOGGER
|
global _LOGGER
|
||||||
if not _LOGGER:
|
if not _LOGGER:
|
||||||
from evennia.utils import logger as _LOGGER
|
from evennia.utils import logger as _LOGGER
|
||||||
|
|
@ -102,7 +105,10 @@ def _get_logger():
|
||||||
|
|
||||||
@wraps
|
@wraps
|
||||||
def catch_traceback(func):
|
def catch_traceback(func):
|
||||||
"Helper decorator"
|
"""
|
||||||
|
Helper decorator
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def decorator(*args, **kwargs):
|
def decorator(*args, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
|
@ -353,6 +359,7 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
||||||
def dataReceived(self, data):
|
def dataReceived(self, data):
|
||||||
"""
|
"""
|
||||||
Handle non-AMP messages, such as HTTP communication.
|
Handle non-AMP messages, such as HTTP communication.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# print("dataReceived: {}".format(data))
|
# print("dataReceived: {}".format(data))
|
||||||
if data[:1] == NUL:
|
if data[:1] == NUL:
|
||||||
|
|
@ -413,6 +420,7 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
||||||
that is irrelevant. If a true connection error happens, the
|
that is irrelevant. If a true connection error happens, the
|
||||||
portal will continuously try to reconnect, showing the problem
|
portal will continuously try to reconnect, showing the problem
|
||||||
that way.
|
that way.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# print("ConnectionLost: {}: {}".format(self, reason))
|
# print("ConnectionLost: {}: {}".format(self, reason))
|
||||||
try:
|
try:
|
||||||
|
|
@ -422,20 +430,20 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
|
|
||||||
def errback(self, e, info):
|
def errback(self, err, info):
|
||||||
"""
|
"""
|
||||||
Error callback.
|
Error callback.
|
||||||
Handles errors to avoid dropping connections on server tracebacks.
|
Handles errors to avoid dropping connections on server tracebacks.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
e (Failure): Deferred error instance.
|
err (Failure): Deferred error instance.
|
||||||
info (str): Error string.
|
info (str): Error string.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
e.trap(Exception)
|
err.trap(Exception)
|
||||||
_get_logger().log_err(
|
_get_logger().log_err(
|
||||||
"AMP Error from {info}: {trcbck} {err}".format(
|
"AMP Error from {info}: {trcbck} {err}".format(
|
||||||
info=info, trcbck=e.getTraceback(), err=e.getErrorMessage()
|
info=info, trcbck=err.getTraceback(), err=err.getErrorMessage()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,10 @@ class AMPServerFactory(protocol.ServerFactory):
|
||||||
noisy = False
|
noisy = False
|
||||||
|
|
||||||
def logPrefix(self):
|
def logPrefix(self):
|
||||||
"How this is named in logs"
|
"""
|
||||||
|
How this is named in logs
|
||||||
|
|
||||||
|
"""
|
||||||
return "AMP"
|
return "AMP"
|
||||||
|
|
||||||
def __init__(self, portal):
|
def __init__(self, portal):
|
||||||
|
|
|
||||||
|
|
@ -335,7 +335,7 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
||||||
# incoming broadcast from network
|
# incoming broadcast from network
|
||||||
payload = data["payload"]
|
payload = data["payload"]
|
||||||
|
|
||||||
print("channels/broadcast:", payload["channel"], self.channel)
|
# print("channels/broadcast:", payload["channel"], self.channel)
|
||||||
if str(payload["channel"]) != self.channel:
|
if str(payload["channel"]) != self.channel:
|
||||||
# only echo from channels this particular bot actually listens to
|
# only echo from channels this particular bot actually listens to
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,7 @@ class Portal(object):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
server_twistd_cmd (list): An instruction for starting the server, to pass to Popen.
|
server_twistd_cmd (list): An instruction for starting the server, to pass to Popen.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
server_twistd_cmd = [
|
server_twistd_cmd = [
|
||||||
"twistd",
|
"twistd",
|
||||||
|
|
@ -196,7 +197,10 @@ class Portal(object):
|
||||||
return server_twistd_cmd
|
return server_twistd_cmd
|
||||||
|
|
||||||
def get_info_dict(self):
|
def get_info_dict(self):
|
||||||
"Return the Portal info, for display."
|
"""
|
||||||
|
Return the Portal info, for display.
|
||||||
|
|
||||||
|
"""
|
||||||
return INFO_DICT
|
return INFO_DICT
|
||||||
|
|
||||||
def shutdown(self, _reactor_stopping=False, _stop_server=False):
|
def shutdown(self, _reactor_stopping=False, _stop_server=False):
|
||||||
|
|
@ -354,7 +358,8 @@ if SSH_ENABLED:
|
||||||
for port in SSH_PORTS:
|
for port in SSH_PORTS:
|
||||||
pstring = "%s:%s" % (ifacestr, port)
|
pstring = "%s:%s" % (ifacestr, port)
|
||||||
factory = ssh.makeFactory(
|
factory = ssh.makeFactory(
|
||||||
{"protocolFactory": _ssh_protocol, "protocolArgs": (), "sessions": PORTAL_SESSIONS,}
|
{"protocolFactory": _ssh_protocol,
|
||||||
|
"protocolArgs": (), "sessions": PORTAL_SESSIONS}
|
||||||
)
|
)
|
||||||
factory.noisy = False
|
factory.noisy = False
|
||||||
ssh_service = internet.TCPServer(port, factory, interface=interface)
|
ssh_service = internet.TCPServer(port, factory, interface=interface)
|
||||||
|
|
@ -390,7 +395,7 @@ if WEBSERVER_ENABLED:
|
||||||
if WEBSOCKET_CLIENT_ENABLED and not websocket_started:
|
if WEBSOCKET_CLIENT_ENABLED and not websocket_started:
|
||||||
# start websocket client port for the webclient
|
# start websocket client port for the webclient
|
||||||
# we only support one websocket client
|
# we only support one websocket client
|
||||||
from evennia.server.portal import webclient
|
from evennia.server.portal import webclient # noqa
|
||||||
from autobahn.twisted.websocket import WebSocketServerFactory
|
from autobahn.twisted.websocket import WebSocketServerFactory
|
||||||
|
|
||||||
w_interface = WEBSOCKET_CLIENT_INTERFACE
|
w_interface = WEBSOCKET_CLIENT_INTERFACE
|
||||||
|
|
@ -417,10 +422,13 @@ if WEBSERVER_ENABLED:
|
||||||
if WEB_PLUGINS_MODULE:
|
if WEB_PLUGINS_MODULE:
|
||||||
try:
|
try:
|
||||||
web_root = WEB_PLUGINS_MODULE.at_webproxy_root_creation(web_root)
|
web_root = WEB_PLUGINS_MODULE.at_webproxy_root_creation(web_root)
|
||||||
except Exception as e: # Legacy user has not added an at_webproxy_root_creation function in existing web plugins file
|
except Exception:
|
||||||
|
# Legacy user has not added an at_webproxy_root_creation function in existing
|
||||||
|
# web plugins file
|
||||||
INFO_DICT["errors"] = (
|
INFO_DICT["errors"] = (
|
||||||
"WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() not found - "
|
"WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() "
|
||||||
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf."
|
"not found copy 'evennia/game_template/server/conf/web_plugins.py to "
|
||||||
|
"mygame/server/conf."
|
||||||
)
|
)
|
||||||
web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE)
|
web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE)
|
||||||
web_root.is_portal = True
|
web_root.is_portal = True
|
||||||
|
|
@ -435,4 +443,3 @@ for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
|
||||||
# external plugin services to start
|
# external plugin services to start
|
||||||
if plugin_module:
|
if plugin_module:
|
||||||
plugin_module.start_plugin_services(PORTAL)
|
plugin_module.start_plugin_services(PORTAL)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""
|
"""
|
||||||
Sessionhandler for portal sessions
|
Sessionhandler for portal sessions.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -11,6 +12,7 @@ from evennia.server.sessionhandler import SessionHandler
|
||||||
from evennia.server.portal.amp import PCONN, PDISCONN, PCONNSYNC, PDISCONNALL
|
from evennia.server.portal.amp import PCONN, PDISCONN, PCONNSYNC, PDISCONNALL
|
||||||
from evennia.utils.logger import log_trace
|
from evennia.utils.logger import log_trace
|
||||||
from evennia.utils.utils import class_from_module
|
from evennia.utils.utils import class_from_module
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
# module import
|
# module import
|
||||||
_MOD_IMPORT = None
|
_MOD_IMPORT = None
|
||||||
|
|
@ -35,6 +37,8 @@ DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0)
|
||||||
# Portal-SessionHandler class
|
# Portal-SessionHandler class
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
|
DOS_PROTECTION_MSG = _("{servername} DoS protection is active. You are queued to connect in {num} seconds ...")
|
||||||
|
|
||||||
|
|
||||||
class PortalSessionHandler(SessionHandler):
|
class PortalSessionHandler(SessionHandler):
|
||||||
"""
|
"""
|
||||||
|
|
@ -111,16 +115,12 @@ class PortalSessionHandler(SessionHandler):
|
||||||
_CONNECTION_QUEUE.appendleft(session)
|
_CONNECTION_QUEUE.appendleft(session)
|
||||||
if len(_CONNECTION_QUEUE) > 1:
|
if len(_CONNECTION_QUEUE) > 1:
|
||||||
session.data_out(
|
session.data_out(
|
||||||
text=[
|
text=(
|
||||||
[
|
(DOS_PROTECTION_MSG.format(
|
||||||
"%s DoS protection is active. You are queued to connect in %g seconds ..."
|
servername=settings.SERVERNAME,
|
||||||
% (
|
num=len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS),),
|
||||||
settings.SERVERNAME,
|
|
||||||
len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
{},
|
{},
|
||||||
]
|
)
|
||||||
)
|
)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if (
|
if (
|
||||||
|
|
@ -220,6 +220,7 @@ class PortalSessionHandler(SessionHandler):
|
||||||
def disconnect_all(self):
|
def disconnect_all(self):
|
||||||
"""
|
"""
|
||||||
Disconnect all sessions, informing the Server.
|
Disconnect all sessions, informing the Server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _callback(result, sessionhandler):
|
def _callback(result, sessionhandler):
|
||||||
|
|
@ -478,5 +479,4 @@ class PortalSessionHandler(SessionHandler):
|
||||||
|
|
||||||
|
|
||||||
_PORTAL_SESSION_HANDLER_CLASS = class_from_module(settings.PORTAL_SESSION_HANDLER_CLASS)
|
_PORTAL_SESSION_HANDLER_CLASS = class_from_module(settings.PORTAL_SESSION_HANDLER_CLASS)
|
||||||
|
|
||||||
PORTAL_SESSIONS = _PORTAL_SESSION_HANDLER_CLASS()
|
PORTAL_SESSIONS = _PORTAL_SESSION_HANDLER_CLASS()
|
||||||
|
|
|
||||||
|
|
@ -145,6 +145,7 @@ class RSSBotFactory(object):
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
"""
|
||||||
Called by portalsessionhandler. Starts the bot.
|
Called by portalsessionhandler. Starts the bot.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def errback(fail):
|
def errback(fail):
|
||||||
|
|
|
||||||
|
|
@ -61,24 +61,25 @@ CTRL_D = "\x04"
|
||||||
CTRL_BACKSLASH = "\x1c"
|
CTRL_BACKSLASH = "\x1c"
|
||||||
CTRL_L = "\x0c"
|
CTRL_L = "\x0c"
|
||||||
|
|
||||||
_NO_AUTOGEN = """
|
_NO_AUTOGEN = f"""
|
||||||
Evennia could not generate SSH private- and public keys ({{err}})
|
Evennia could not generate SSH private- and public keys ({{err}})
|
||||||
Using conch default keys instead.
|
Using conch default keys instead.
|
||||||
|
|
||||||
If this error persists, create the keys manually (using the tools for your OS)
|
If this error persists, create the keys manually (using the tools for your OS)
|
||||||
and put them here:
|
and put them here:
|
||||||
{}
|
{_PRIVATE_KEY_FILE}
|
||||||
{}
|
{_PUBLIC_KEY_FILE}
|
||||||
""".format(
|
"""
|
||||||
_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
|
|
||||||
)
|
|
||||||
|
|
||||||
_BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
_BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
||||||
|
|
||||||
|
|
||||||
# not used atm
|
# not used atm
|
||||||
class SSHServerFactory(protocol.ServerFactory):
|
class SSHServerFactory(protocol.ServerFactory):
|
||||||
"This is only to name this better in logs"
|
"""
|
||||||
|
This is only to name this better in logs
|
||||||
|
|
||||||
|
"""
|
||||||
noisy = False
|
noisy = False
|
||||||
|
|
||||||
def logPrefix(self):
|
def logPrefix(self):
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ class SSLProtocol(_TELNET_PROTOCOL_CLASS):
|
||||||
"""
|
"""
|
||||||
Communication is the same as telnet, except data transfer
|
Communication is the same as telnet, except data transfer
|
||||||
is done with encryption.
|
is done with encryption.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -62,6 +63,7 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
||||||
This function looks for RSA key and certificate in the current
|
This function looks for RSA key and certificate in the current
|
||||||
directory. If files ssl.key and ssl.cert does not exist, they
|
directory. If files ssl.key and ssl.cert does not exist, they
|
||||||
are created.
|
are created.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not (os.path.exists(keyfile) and os.path.exists(certfile)):
|
if not (os.path.exists(keyfile) and os.path.exists(certfile)):
|
||||||
|
|
@ -74,10 +76,11 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# create the RSA key and store it.
|
# create the RSA key and store it.
|
||||||
KEY_LENGTH = 1024
|
KEY_LENGTH = 2048
|
||||||
rsaKey = Key(RSA.generate(KEY_LENGTH))
|
rsa_key = Key(RSA.generate(KEY_LENGTH))
|
||||||
keyString = rsaKey.toString(type="OPENSSH")
|
key_string = rsa_key.toString(type="OPENSSH")
|
||||||
file(keyfile, "w+b").write(keyString)
|
with open(keyfile, "w+b") as fil:
|
||||||
|
fil.write(key_string)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(NO_AUTOGEN.format(err=err, keyfile=keyfile))
|
print(NO_AUTOGEN.format(err=err, keyfile=keyfile))
|
||||||
sys.exit(5)
|
sys.exit(5)
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,10 @@ _BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
||||||
|
|
||||||
|
|
||||||
class TelnetServerFactory(protocol.ServerFactory):
|
class TelnetServerFactory(protocol.ServerFactory):
|
||||||
"This is only to name this better in logs"
|
"""
|
||||||
|
This exists only to name this better in logs.
|
||||||
|
|
||||||
|
"""
|
||||||
noisy = False
|
noisy = False
|
||||||
|
|
||||||
def logPrefix(self):
|
def logPrefix(self):
|
||||||
|
|
@ -71,6 +74,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
Each player connecting over telnet (ie using most traditional mud
|
Each player connecting over telnet (ie using most traditional mud
|
||||||
clients) gets a telnet protocol instance assigned to them. All
|
clients) gets a telnet protocol instance assigned to them. All
|
||||||
communication between game and player goes through here.
|
communication between game and player goes through here.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
@ -81,6 +85,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
"""
|
"""
|
||||||
Unused by default, but a good place to put debug printouts
|
Unused by default, but a good place to put debug printouts
|
||||||
of incoming data.
|
of incoming data.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# print(f"telnet dataReceived: {data}")
|
# print(f"telnet dataReceived: {data}")
|
||||||
try:
|
try:
|
||||||
|
|
@ -145,11 +150,15 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
Client refuses do(linemode). This is common for MUD-specific
|
Client refuses do(linemode). This is common for MUD-specific
|
||||||
clients, but we must ask for the sake of raw telnet. We ignore
|
clients, but we must ask for the sake of raw telnet. We ignore
|
||||||
this error.
|
this error.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _send_nop_keepalive(self):
|
def _send_nop_keepalive(self):
|
||||||
"""Send NOP keepalive unless flag is set"""
|
"""
|
||||||
|
Send NOP keepalive unless flag is set
|
||||||
|
|
||||||
|
"""
|
||||||
if self.protocol_flags.get("NOPKEEPALIVE"):
|
if self.protocol_flags.get("NOPKEEPALIVE"):
|
||||||
self._write(IAC + NOP)
|
self._write(IAC + NOP)
|
||||||
|
|
||||||
|
|
@ -158,7 +167,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
Allow to toggle the NOP keepalive for those sad clients that
|
Allow to toggle the NOP keepalive for those sad clients that
|
||||||
can't even handle a NOP instruction. This is turned off by the
|
can't even handle a NOP instruction. This is turned off by the
|
||||||
protocol_flag NOPKEEPALIVE (settable e.g. by the default
|
protocol_flag NOPKEEPALIVE (settable e.g. by the default
|
||||||
`@option` command).
|
`option` command).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.nop_keep_alive and self.nop_keep_alive.running:
|
if self.nop_keep_alive and self.nop_keep_alive.running:
|
||||||
self.nop_keep_alive.stop()
|
self.nop_keep_alive.stop()
|
||||||
|
|
@ -172,6 +182,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
When all have reported, a sync with the server is performed.
|
When all have reported, a sync with the server is performed.
|
||||||
The system will force-call this sync after a small time to handle
|
The system will force-call this sync after a small time to handle
|
||||||
clients that don't reply to handshakes at all.
|
clients that don't reply to handshakes at all.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if timeout:
|
if timeout:
|
||||||
if self.handshakes > 0:
|
if self.handshakes > 0:
|
||||||
|
|
@ -186,6 +197,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
def at_login(self):
|
def at_login(self):
|
||||||
"""
|
"""
|
||||||
Called when this session gets authenticated by the server.
|
Called when this session gets authenticated by the server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
@ -321,7 +333,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
self.data_in(text=dat + b"\n")
|
self.data_in(text=dat + b"\n")
|
||||||
|
|
||||||
def _write(self, data):
|
def _write(self, data):
|
||||||
"""hook overloading the one used in plain telnet"""
|
"""
|
||||||
|
Hook overloading the one used in plain telnet
|
||||||
|
|
||||||
|
"""
|
||||||
data = data.replace(b"\n", b"\r\n").replace(b"\r\r\n", b"\r\n")
|
data = data.replace(b"\n", b"\r\n").replace(b"\r\r\n", b"\r\n")
|
||||||
super()._write(mccp_compress(self, data))
|
super()._write(mccp_compress(self, data))
|
||||||
|
|
||||||
|
|
@ -347,7 +362,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
|
|
||||||
def disconnect(self, reason=""):
|
def disconnect(self, reason=""):
|
||||||
"""
|
"""
|
||||||
generic hook for the engine to call in order to
|
Generic hook for the engine to call in order to
|
||||||
disconnect this protocol.
|
disconnect this protocol.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -376,6 +391,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
kwargs (any): Options to the protocol
|
kwargs (any): Options to the protocol
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.sessionhandler.data_out(self, **kwargs)
|
self.sessionhandler.data_out(self, **kwargs)
|
||||||
|
|
||||||
|
|
@ -442,7 +458,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
prompt = mxp_parse(prompt)
|
prompt = mxp_parse(prompt)
|
||||||
prompt = to_bytes(prompt, self)
|
prompt = to_bytes(prompt, self)
|
||||||
prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n")
|
prompt = prompt.replace(IAC, IAC + IAC).replace(b"\n", b"\r\n")
|
||||||
if not self.protocol_flags.get("NOPROMPTGOAHEAD",
|
if not self.protocol_flags.get("NOPROMPTGOAHEAD",
|
||||||
self.protocol_flags.get("NOGOAHEAD", True)):
|
self.protocol_flags.get("NOGOAHEAD", True)):
|
||||||
prompt += IAC + GA
|
prompt += IAC + GA
|
||||||
self.transport.write(mccp_compress(self, prompt))
|
self.transport.write(mccp_compress(self, prompt))
|
||||||
|
|
@ -488,6 +504,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
def send_default(self, cmdname, *args, **kwargs):
|
def send_default(self, cmdname, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Send other oob data
|
Send other oob data
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not cmdname == "options":
|
if not cmdname == "options":
|
||||||
self.oob.data_out(cmdname, *args, **kwargs)
|
self.oob.data_out(cmdname, *args, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -46,32 +46,29 @@ _CERTIFICATE_ISSUER = {
|
||||||
|
|
||||||
# messages
|
# messages
|
||||||
|
|
||||||
NO_AUTOGEN = """
|
NO_AUTOGEN = f"""
|
||||||
Evennia could not auto-generate the SSL private- and public keys ({{err}}).
|
Evennia could not auto-generate the SSL private- and public keys ({{err}}).
|
||||||
If this error persists, create them manually (using the tools for your OS). The files
|
If this error persists, create them manually (using the tools for your OS). The files
|
||||||
should be placed and named like this:
|
should be placed and named like this:
|
||||||
{}
|
{_PRIVATE_KEY_FILE}
|
||||||
{}
|
{_PUBLIC_KEY_FILE}
|
||||||
""".format(
|
"""
|
||||||
_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
|
|
||||||
)
|
|
||||||
|
|
||||||
NO_AUTOCERT = """
|
NO_AUTOCERT = """
|
||||||
Evennia's could not auto-generate the SSL certificate ({{err}}).
|
Evennia's could not auto-generate the SSL certificate ({{err}}).
|
||||||
The private key already exists here:
|
The private key already exists here:
|
||||||
{}
|
{_PRIVATE_KEY_FILE}
|
||||||
If this error persists, create the certificate manually (using the private key and
|
If this error persists, create the certificate manually (using the private key and
|
||||||
the tools for your OS). The file should be placed and named like this:
|
the tools for your OS). The file should be placed and named like this:
|
||||||
{}
|
{_CERTIFICATE_FILE}
|
||||||
""".format(
|
"""
|
||||||
_PRIVATE_KEY_FILE, _CERTIFICATE_FILE
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SSLProtocol(TelnetProtocol):
|
class SSLProtocol(TelnetProtocol):
|
||||||
"""
|
"""
|
||||||
Communication is the same as telnet, except data transfer
|
Communication is the same as telnet, except data transfer
|
||||||
is done with encryption set up by the portal at start time.
|
is done with encryption set up by the portal at start time.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ _BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
||||||
class WebSocketClient(WebSocketServerProtocol, _BASE_SESSION_CLASS):
|
class WebSocketClient(WebSocketServerProtocol, _BASE_SESSION_CLASS):
|
||||||
"""
|
"""
|
||||||
Implements the server-side of the Websocket connection.
|
Implements the server-side of the Websocket connection.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# nonce value, used to prevent the webclient from erasing the
|
# nonce value, used to prevent the webclient from erasing the
|
||||||
|
|
@ -155,7 +156,7 @@ class WebSocketClient(WebSocketServerProtocol, _BASE_SESSION_CLASS):
|
||||||
# in case anyone wants to expose this functionality later.
|
# in case anyone wants to expose this functionality later.
|
||||||
#
|
#
|
||||||
# sendClose() under autobahn/websocket/interfaces.py
|
# sendClose() under autobahn/websocket/interfaces.py
|
||||||
ret = self.sendClose(CLOSE_NORMAL, reason)
|
self.sendClose(CLOSE_NORMAL, reason)
|
||||||
|
|
||||||
def onClose(self, wasClean, code=None, reason=None):
|
def onClose(self, wasClean, code=None, reason=None):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ http://localhost:4001/webclient.)
|
||||||
The WebClient resource in this module will
|
The WebClient resource in this module will
|
||||||
handle these requests and act as a gateway
|
handle these requests and act as a gateway
|
||||||
to sessions connected over the webclient.
|
to sessions connected over the webclient.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
@ -27,7 +28,7 @@ from django.utils.functional import Promise
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils.ansi import parse_ansi
|
from evennia.utils.ansi import parse_ansi
|
||||||
from evennia.utils import utils
|
from evennia.utils import utils
|
||||||
from evennia.utils.utils import to_bytes, to_str
|
from evennia.utils.utils import to_bytes
|
||||||
from evennia.utils.text2html import parse_html
|
from evennia.utils.text2html import parse_html
|
||||||
from evennia.server import session
|
from evennia.server import session
|
||||||
|
|
||||||
|
|
@ -223,10 +224,13 @@ class AjaxWebClient(resource.Resource):
|
||||||
return jsonify({"msg": host_string, "csessid": csessid})
|
return jsonify({"msg": host_string, "csessid": csessid})
|
||||||
|
|
||||||
def mode_keepalive(self, request):
|
def mode_keepalive(self, request):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This is called by render_POST when the
|
This is called by render_POST when the
|
||||||
client is replying to the keepalive.
|
client is replying to the keepalive.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request (Request): Incoming request.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
csessid = self.get_client_sessid(request)
|
csessid = self.get_client_sessid(request)
|
||||||
self.last_alive[csessid] = (time.time(), False)
|
self.last_alive[csessid] = (time.time(), False)
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ class Evennia:
|
||||||
The main Evennia server handler. This object sets up the database and
|
The main Evennia server handler. This object sets up the database and
|
||||||
tracks and interlinks all the twisted network services that make up
|
tracks and interlinks all the twisted network services that make up
|
||||||
evennia.
|
evennia.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, application):
|
def __init__(self, application):
|
||||||
|
|
@ -243,6 +244,7 @@ class Evennia:
|
||||||
This allows for changing default cmdset locations and default
|
This allows for changing default cmdset locations and default
|
||||||
typeclasses in the settings file and have them auto-update all
|
typeclasses in the settings file and have them auto-update all
|
||||||
already existing objects.
|
already existing objects.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
global INFO_DICT
|
global INFO_DICT
|
||||||
|
|
||||||
|
|
@ -471,7 +473,10 @@ class Evennia:
|
||||||
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime())
|
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime())
|
||||||
|
|
||||||
def get_info_dict(self):
|
def get_info_dict(self):
|
||||||
"Return the server info, for display."
|
"""
|
||||||
|
Return the server info, for display.
|
||||||
|
|
||||||
|
"""
|
||||||
return INFO_DICT
|
return INFO_DICT
|
||||||
|
|
||||||
# server start/stop hooks
|
# server start/stop hooks
|
||||||
|
|
@ -480,6 +485,7 @@ class Evennia:
|
||||||
"""
|
"""
|
||||||
This is called every time the server starts up, regardless of
|
This is called every time the server starts up, regardless of
|
||||||
how it was shut down.
|
how it was shut down.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if SERVER_STARTSTOP_MODULE:
|
if SERVER_STARTSTOP_MODULE:
|
||||||
SERVER_STARTSTOP_MODULE.at_server_start()
|
SERVER_STARTSTOP_MODULE.at_server_start()
|
||||||
|
|
@ -488,6 +494,7 @@ class Evennia:
|
||||||
"""
|
"""
|
||||||
This is called just before a server is shut down, regardless
|
This is called just before a server is shut down, regardless
|
||||||
of it is fore a reload, reset or shutdown.
|
of it is fore a reload, reset or shutdown.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if SERVER_STARTSTOP_MODULE:
|
if SERVER_STARTSTOP_MODULE:
|
||||||
SERVER_STARTSTOP_MODULE.at_server_stop()
|
SERVER_STARTSTOP_MODULE.at_server_stop()
|
||||||
|
|
@ -495,6 +502,7 @@ class Evennia:
|
||||||
def at_server_reload_start(self):
|
def at_server_reload_start(self):
|
||||||
"""
|
"""
|
||||||
This is called only when server starts back up after a reload.
|
This is called only when server starts back up after a reload.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if SERVER_STARTSTOP_MODULE:
|
if SERVER_STARTSTOP_MODULE:
|
||||||
SERVER_STARTSTOP_MODULE.at_server_reload_start()
|
SERVER_STARTSTOP_MODULE.at_server_reload_start()
|
||||||
|
|
@ -505,7 +513,7 @@ class Evennia:
|
||||||
after reconnecting.
|
after reconnecting.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mode (str): One of reload, reset or shutdown.
|
mode (str): One of 'reload', 'reset' or 'shutdown'.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -555,6 +563,7 @@ class Evennia:
|
||||||
def at_server_reload_stop(self):
|
def at_server_reload_stop(self):
|
||||||
"""
|
"""
|
||||||
This is called only time the server stops before a reload.
|
This is called only time the server stops before a reload.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if SERVER_STARTSTOP_MODULE:
|
if SERVER_STARTSTOP_MODULE:
|
||||||
SERVER_STARTSTOP_MODULE.at_server_reload_stop()
|
SERVER_STARTSTOP_MODULE.at_server_reload_stop()
|
||||||
|
|
@ -563,6 +572,7 @@ class Evennia:
|
||||||
"""
|
"""
|
||||||
This is called only when the server starts "cold", i.e. after a
|
This is called only when the server starts "cold", i.e. after a
|
||||||
shutdown or a reset.
|
shutdown or a reset.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# We need to do this just in case the server was killed in a way where
|
# We need to do this just in case the server was killed in a way where
|
||||||
# the normal cleanup operations did not have time to run.
|
# the normal cleanup operations did not have time to run.
|
||||||
|
|
@ -590,6 +600,7 @@ class Evennia:
|
||||||
def at_server_cold_stop(self):
|
def at_server_cold_stop(self):
|
||||||
"""
|
"""
|
||||||
This is called only when the server goes down due to a shutdown or reset.
|
This is called only when the server goes down due to a shutdown or reset.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if SERVER_STARTSTOP_MODULE:
|
if SERVER_STARTSTOP_MODULE:
|
||||||
SERVER_STARTSTOP_MODULE.at_server_cold_stop()
|
SERVER_STARTSTOP_MODULE.at_server_cold_stop()
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ from evennia.comms.models import ChannelDB
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.utils.utils import make_iter, lazy_property, class_from_module
|
from evennia.utils.utils import make_iter, lazy_property, class_from_module
|
||||||
from evennia.commands.cmdsethandler import CmdSetHandler
|
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||||
from evennia.server.session import Session
|
|
||||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||||
from evennia.typeclasses.attributes import AttributeHandler, InMemoryAttributeBackend, DbHolder
|
from evennia.typeclasses.attributes import AttributeHandler, InMemoryAttributeBackend, DbHolder
|
||||||
|
|
||||||
|
|
@ -22,9 +21,6 @@ _SA = object.__setattr__
|
||||||
_ObjectDB = None
|
_ObjectDB = None
|
||||||
_ANSI = None
|
_ANSI = None
|
||||||
|
|
||||||
# i18n
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
|
|
||||||
_BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
_BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -45,7 +41,10 @@ class ServerSession(_BASE_SESSION_CLASS):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Initiate to avoid AttributeErrors down the line"""
|
"""
|
||||||
|
Initiate to avoid AttributeErrors down the line
|
||||||
|
|
||||||
|
"""
|
||||||
self.puppet = None
|
self.puppet = None
|
||||||
self.account = None
|
self.account = None
|
||||||
self.cmdset_storage_string = ""
|
self.cmdset_storage_string = ""
|
||||||
|
|
@ -321,7 +320,10 @@ class ServerSession(_BASE_SESSION_CLASS):
|
||||||
self.sessionhandler.data_in(session or self, **kwargs)
|
self.sessionhandler.data_in(session or self, **kwargs)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
"""Handle session comparisons"""
|
"""
|
||||||
|
Handle session comparisons
|
||||||
|
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return self.address == other.address
|
return self.address == other.address
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
|
@ -368,6 +370,7 @@ class ServerSession(_BASE_SESSION_CLASS):
|
||||||
def at_cmdset_get(self, **kwargs):
|
def at_cmdset_get(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
A dummy hook all objects with cmdsets need to have
|
A dummy hook all objects with cmdsets need to have
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
@ -414,7 +417,10 @@ class ServerSession(_BASE_SESSION_CLASS):
|
||||||
|
|
||||||
# @ndb.deleter
|
# @ndb.deleter
|
||||||
def ndb_del(self):
|
def ndb_del(self):
|
||||||
"""Stop accidental deletion."""
|
"""
|
||||||
|
Stop accidental deletion.
|
||||||
|
|
||||||
|
"""
|
||||||
raise Exception("Cannot delete the ndb object!")
|
raise Exception("Cannot delete the ndb object!")
|
||||||
|
|
||||||
ndb = property(ndb_get, ndb_set, ndb_del)
|
ndb = property(ndb_get, ndb_set, ndb_del)
|
||||||
|
|
@ -423,5 +429,8 @@ class ServerSession(_BASE_SESSION_CLASS):
|
||||||
# Mock access method for the session (there is no lock info
|
# Mock access method for the session (there is no lock info
|
||||||
# at this stage, so we just present a uniform API)
|
# at this stage, so we just present a uniform API)
|
||||||
def access(self, *args, **kwargs):
|
def access(self, *args, **kwargs):
|
||||||
"""Dummy method to mimic the logged-in API."""
|
"""
|
||||||
|
Dummy method to mimic the logged-in API.
|
||||||
|
|
||||||
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ from django.conf import settings
|
||||||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||||
from evennia.utils.logger import log_trace
|
from evennia.utils.logger import log_trace
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import (
|
||||||
variable_from_module, class_from_module,
|
|
||||||
is_iter,
|
is_iter,
|
||||||
make_iter,
|
make_iter,
|
||||||
delay,
|
delay,
|
||||||
|
|
@ -29,6 +28,7 @@ from evennia.server.portal import amp
|
||||||
from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT
|
from evennia.server.signals import SIGNAL_ACCOUNT_POST_LOGIN, SIGNAL_ACCOUNT_POST_LOGOUT
|
||||||
from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT
|
from evennia.server.signals import SIGNAL_ACCOUNT_POST_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT
|
||||||
from codecs import decode as codecs_decode
|
from codecs import decode as codecs_decode
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
_FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED
|
_FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED = settings.FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ _ServerConfig = None
|
||||||
_ScriptDB = None
|
_ScriptDB = None
|
||||||
_OOB_HANDLER = None
|
_OOB_HANDLER = None
|
||||||
|
|
||||||
_ERR_BAD_UTF8 = "Your client sent an incorrect UTF-8 sequence."
|
_ERR_BAD_UTF8 = _("Your client sent an incorrect UTF-8 sequence.")
|
||||||
|
|
||||||
|
|
||||||
class DummySession(object):
|
class DummySession(object):
|
||||||
|
|
@ -48,9 +48,6 @@ class DummySession(object):
|
||||||
|
|
||||||
DUMMYSESSION = DummySession()
|
DUMMYSESSION = DummySession()
|
||||||
|
|
||||||
# i18n
|
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
|
|
||||||
_SERVERNAME = settings.SERVERNAME
|
_SERVERNAME = settings.SERVERNAME
|
||||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||||
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||||
|
|
@ -61,7 +58,6 @@ _MODEL_MAP = None
|
||||||
_FUNCPARSER = None
|
_FUNCPARSER = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# input handlers
|
# input handlers
|
||||||
|
|
||||||
_INPUT_FUNCS = {}
|
_INPUT_FUNCS = {}
|
||||||
|
|
@ -103,24 +99,36 @@ class SessionHandler(dict):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
"Clean out None-sessions automatically."
|
"""
|
||||||
|
Clean out None-sessions automatically.
|
||||||
|
|
||||||
|
"""
|
||||||
if None in self:
|
if None in self:
|
||||||
del self[None]
|
del self[None]
|
||||||
return super().__getitem__(key)
|
return super().__getitem__(key)
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
"Clean out None-sessions automatically."
|
"""
|
||||||
|
Clean out None-sessions automatically.
|
||||||
|
|
||||||
|
"""
|
||||||
if None in self:
|
if None in self:
|
||||||
del self[None]
|
del self[None]
|
||||||
return super().get(key, default)
|
return super().get(key, default)
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
"Don't assign None sessions"
|
"""
|
||||||
|
Don't assign None sessions"
|
||||||
|
|
||||||
|
"""
|
||||||
if key is not None:
|
if key is not None:
|
||||||
super().__setitem__(key, value)
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
def __contains__(self, key):
|
def __contains__(self, key):
|
||||||
"None-keys are not accepted."
|
"""
|
||||||
|
None-keys are not accepted.
|
||||||
|
|
||||||
|
"""
|
||||||
return False if key is None else super().__contains__(key)
|
return False if key is None else super().__contains__(key)
|
||||||
|
|
||||||
def get_sessions(self, include_unloggedin=False):
|
def get_sessions(self, include_unloggedin=False):
|
||||||
|
|
@ -158,9 +166,8 @@ class SessionHandler(dict):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (Session): The relevant session instance.
|
session (Session): The relevant session instance.
|
||||||
kwargs (dict) Each keyword represents a send-instruction, with the keyword itself being the name
|
kwargs (dict) Each keyword represents a send-instruction, with the keyword itself being
|
||||||
of the instruction (like "text"). Suitable values for each
|
the name of the instruction (like "text"). Suitable values for each keyword are:
|
||||||
keyword are:
|
|
||||||
- arg -> [[arg], {}]
|
- arg -> [[arg], {}]
|
||||||
- [args] -> [[args], {}]
|
- [args] -> [[args], {}]
|
||||||
- {kwargs} -> [[], {kwargs}]
|
- {kwargs} -> [[], {kwargs}]
|
||||||
|
|
@ -177,7 +184,8 @@ class SessionHandler(dict):
|
||||||
global _FUNCPARSER
|
global _FUNCPARSER
|
||||||
if not _FUNCPARSER:
|
if not _FUNCPARSER:
|
||||||
from evennia.utils.funcparser import FuncParser
|
from evennia.utils.funcparser import FuncParser
|
||||||
_FUNCPARSER = FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES, raise_errors=True)
|
_FUNCPARSER = FuncParser(settings.FUNCPARSER_OUTGOING_MESSAGES_MODULES,
|
||||||
|
raise_errors=True)
|
||||||
|
|
||||||
options = kwargs.pop("options", None) or {}
|
options = kwargs.pop("options", None) or {}
|
||||||
raw = options.get("raw", False)
|
raw = options.get("raw", False)
|
||||||
|
|
@ -199,7 +207,10 @@ class SessionHandler(dict):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _validate(data):
|
def _validate(data):
|
||||||
"Helper function to convert data to AMP-safe (picketable) values"
|
"""
|
||||||
|
Helper function to convert data to AMP-safe (picketable) values"
|
||||||
|
|
||||||
|
"""
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
newdict = {}
|
newdict = {}
|
||||||
for key, part in data.items():
|
for key, part in data.items():
|
||||||
|
|
@ -210,7 +221,8 @@ class SessionHandler(dict):
|
||||||
elif isinstance(data, (str, bytes)):
|
elif isinstance(data, (str, bytes)):
|
||||||
data = _utf8(data)
|
data = _utf8(data)
|
||||||
|
|
||||||
if _FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED and not raw and isinstance(self, ServerSessionHandler):
|
if (_FUNCPARSER_PARSE_OUTGOING_MESSAGES_ENABLED
|
||||||
|
and not raw and isinstance(self, ServerSessionHandler)):
|
||||||
# only apply funcparser on the outgoing path (sessionhandler->)
|
# only apply funcparser on the outgoing path (sessionhandler->)
|
||||||
# data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)
|
# data = parse_inlinefunc(data, strip=strip_inlinefunc, session=session)
|
||||||
data = _FUNCPARSER.parse(data, strip=strip_inlinefunc, session=session)
|
data = _FUNCPARSER.parse(data, strip=strip_inlinefunc, session=session)
|
||||||
|
|
@ -261,14 +273,11 @@ class SessionHandler(dict):
|
||||||
|
|
||||||
class ServerSessionHandler(SessionHandler):
|
class ServerSessionHandler(SessionHandler):
|
||||||
"""
|
"""
|
||||||
This object holds the stack of sessions active in the game at
|
This object holds the stack of sessions active in the game at any time.
|
||||||
any time.
|
|
||||||
|
|
||||||
A session register with the handler in two steps, first by
|
A session register with the handler in two steps, first by registering itself with the connect()
|
||||||
registering itself with the connect() method. This indicates an
|
method. This indicates an non-authenticated session. Whenever the session is authenticated the
|
||||||
non-authenticated session. Whenever the session is authenticated
|
session together with the related account is sent to the login() method.
|
||||||
the session together with the related account is sent to the login()
|
|
||||||
method.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -468,9 +477,8 @@ class ServerSessionHandler(SessionHandler):
|
||||||
|
|
||||||
def login(self, session, account, force=False, testmode=False):
|
def login(self, session, account, force=False, testmode=False):
|
||||||
"""
|
"""
|
||||||
Log in the previously unloggedin session and the account we by
|
Log in the previously unloggedin session and the account we by now should know is connected
|
||||||
now should know is connected to it. After this point we assume
|
to it. After this point we assume the session to be logged in one way or another.
|
||||||
the session to be logged in one way or another.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (Session): The Session to authenticate.
|
session (Session): The Session to authenticate.
|
||||||
|
|
@ -627,7 +635,8 @@ class ServerSessionHandler(SessionHandler):
|
||||||
# mean connecting from the same host would not catch duplicates
|
# mean connecting from the same host would not catch duplicates
|
||||||
sid = id(curr_session)
|
sid = id(curr_session)
|
||||||
doublet_sessions = [
|
doublet_sessions = [
|
||||||
sess for sess in self.values() if sess.logged_in and sess.uid == uid and id(sess) != sid
|
sess for sess in self.values()
|
||||||
|
if sess.logged_in and sess.uid == uid and id(sess) != sid
|
||||||
]
|
]
|
||||||
|
|
||||||
for session in doublet_sessions:
|
for session in doublet_sessions:
|
||||||
|
|
@ -737,8 +746,8 @@ class ServerSessionHandler(SessionHandler):
|
||||||
puppet (Object): Object puppeted
|
puppet (Object): Object puppeted
|
||||||
|
|
||||||
Returns.
|
Returns.
|
||||||
sessions (Session or list): Can be more than one of Object is controlled by
|
sessions (Session or list): Can be more than one of Object is controlled by more than
|
||||||
more than one Session (MULTISESSION_MODE > 1).
|
one Session (MULTISESSION_MODE > 1).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
sessions = puppet.sessid.get()
|
sessions = puppet.sessid.get()
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ from django.core.cache import caches
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
import time
|
import time
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
|
|
||||||
class Throttle(object):
|
class Throttle:
|
||||||
"""
|
"""
|
||||||
Keeps a running count of failed actions per IP address.
|
Keeps a running count of failed actions per IP address.
|
||||||
|
|
||||||
|
|
@ -17,7 +18,7 @@ class Throttle(object):
|
||||||
caches for automatic key eviction and persistence configurability.
|
caches for automatic key eviction and persistence configurability.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
error_msg = "Too many failed attempts; you must wait a few minutes before trying again."
|
error_msg = _("Too many failed attempts; you must wait a few minutes before trying again.")
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -34,7 +35,7 @@ class Throttle(object):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.storage = caches['throttle']
|
self.storage = caches['throttle']
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logger.log_trace("Throttle: Errors encountered; using default cache.")
|
logger.log_trace("Throttle: Errors encountered; using default cache.")
|
||||||
self.storage = caches['default']
|
self.storage = caches['default']
|
||||||
|
|
||||||
|
|
@ -123,7 +124,10 @@ class Throttle(object):
|
||||||
|
|
||||||
# If this makes it engage, log a single activation event
|
# If this makes it engage, log a single activation event
|
||||||
if not previously_throttled and currently_throttled:
|
if not previously_throttled and currently_throttled:
|
||||||
logger.log_sec(f"Throttle Activated: {failmsg} (IP: {ip}, {self.limit} hits in {self.timeout} seconds.)")
|
logger.log_sec(
|
||||||
|
f"Throttle Activated: {failmsg} (IP: {ip}, "
|
||||||
|
f"{self.limit} hits in {self.timeout} seconds.)"
|
||||||
|
)
|
||||||
|
|
||||||
self.record_ip(ip)
|
self.record_ip(ip)
|
||||||
|
|
||||||
|
|
@ -136,14 +140,15 @@ class Throttle(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
exists = self.get(ip)
|
exists = self.get(ip)
|
||||||
if not exists: return False
|
if not exists:
|
||||||
|
return False
|
||||||
|
|
||||||
cache_key = self.get_cache_key(ip)
|
cache_key = self.get_cache_key(ip)
|
||||||
self.storage.delete(cache_key)
|
self.storage.delete(cache_key)
|
||||||
self.unrecord_ip(ip)
|
self.unrecord_ip(ip)
|
||||||
|
|
||||||
# Return True if NOT exists
|
# Return True if NOT exists
|
||||||
return ~bool(self.get(ip))
|
return not bool(self.get(ip))
|
||||||
|
|
||||||
def record_ip(self, ip, *args, **kwargs):
|
def record_ip(self, ip, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ which is a non-db version of Attributes.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
import fnmatch
|
import fnmatch
|
||||||
import weakref
|
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
@ -117,6 +116,7 @@ class IAttribute:
|
||||||
class InMemoryAttribute(IAttribute):
|
class InMemoryAttribute(IAttribute):
|
||||||
"""
|
"""
|
||||||
This Attribute is used purely for NAttributes/NAttributeHandler. It has no database backend.
|
This Attribute is used purely for NAttributes/NAttributeHandler. It has no database backend.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Primary Key has no meaning for an InMemoryAttribute. This merely serves to satisfy other code.
|
# Primary Key has no meaning for an InMemoryAttribute. This merely serves to satisfy other code.
|
||||||
|
|
@ -126,11 +126,12 @@ class InMemoryAttribute(IAttribute):
|
||||||
Create an Attribute that exists only in Memory.
|
Create an Attribute that exists only in Memory.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pk (int): This is a fake 'primary key' / id-field. It doesn't actually have to be unique, but is fed an
|
pk (int): This is a fake 'primary key' / id-field. It doesn't actually have to be
|
||||||
incrementing number from the InMemoryBackend by default. This is needed only so Attributes can be
|
unique, but is fed an incrementing number from the InMemoryBackend by default. This
|
||||||
sorted. Some parts of the API also see the lack of a .pk field as a sign that the Attribute was
|
is needed only so Attributes can be sorted. Some parts of the API also see the lack
|
||||||
deleted.
|
of a .pk field as a sign that the Attribute was deleted.
|
||||||
**kwargs: Other keyword arguments are used to construct the actual Attribute.
|
**kwargs: Other keyword arguments are used to construct the actual Attribute.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.id = pk
|
self.id = pk
|
||||||
self.pk = pk
|
self.pk = pk
|
||||||
|
|
@ -245,8 +246,8 @@ class Attribute(IAttribute, SharedMemoryModel):
|
||||||
lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del)
|
lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del)
|
||||||
|
|
||||||
# value property (wraps db_value)
|
# value property (wraps db_value)
|
||||||
# @property
|
@property
|
||||||
def __value_get(self):
|
def value(self):
|
||||||
"""
|
"""
|
||||||
Getter. Allows for `value = self.value`.
|
Getter. Allows for `value = self.value`.
|
||||||
We cannot cache here since it makes certain cases (such
|
We cannot cache here since it makes certain cases (such
|
||||||
|
|
@ -255,8 +256,8 @@ class Attribute(IAttribute, SharedMemoryModel):
|
||||||
"""
|
"""
|
||||||
return from_pickle(self.db_value, db_obj=self)
|
return from_pickle(self.db_value, db_obj=self)
|
||||||
|
|
||||||
# @value.setter
|
@value.setter
|
||||||
def __value_set(self, new_value):
|
def value(self, new_value):
|
||||||
"""
|
"""
|
||||||
Setter. Allows for self.value = value. We cannot cache here,
|
Setter. Allows for self.value = value. We cannot cache here,
|
||||||
see self.__value_get.
|
see self.__value_get.
|
||||||
|
|
@ -264,14 +265,11 @@ class Attribute(IAttribute, SharedMemoryModel):
|
||||||
self.db_value = to_pickle(new_value)
|
self.db_value = to_pickle(new_value)
|
||||||
self.save(update_fields=["db_value"])
|
self.save(update_fields=["db_value"])
|
||||||
|
|
||||||
# @value.deleter
|
@value.deleter
|
||||||
def __value_del(self):
|
def value(self):
|
||||||
"""Deleter. Allows for del attr.value. This removes the entire attribute."""
|
"""Deleter. Allows for del attr.value. This removes the entire attribute."""
|
||||||
self.delete()
|
self.delete()
|
||||||
|
|
||||||
value = property(__value_get, __value_set, __value_del)
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Handlers making use of the Attribute model
|
# Handlers making use of the Attribute model
|
||||||
#
|
#
|
||||||
|
|
@ -348,7 +346,7 @@ class IAttributeBackend:
|
||||||
|
|
||||||
def _get_cache_key(self, key, category):
|
def _get_cache_key(self, key, category):
|
||||||
"""
|
"""
|
||||||
|
Fetch cache key.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key (str): The key of the Attribute being searched for.
|
key (str): The key of the Attribute being searched for.
|
||||||
|
|
@ -529,7 +527,8 @@ class IAttributeBackend:
|
||||||
|
|
||||||
def create_attribute(self, key, category, lockstring, value, strvalue=False, cache=True):
|
def create_attribute(self, key, category, lockstring, value, strvalue=False, cache=True):
|
||||||
"""
|
"""
|
||||||
Creates Attribute (using the class specified for the backend), (optionally) caches it, and returns it.
|
Creates Attribute (using the class specified for the backend), (optionally) caches it, and
|
||||||
|
returns it.
|
||||||
|
|
||||||
This MUST actively save the Attribute to whatever database backend is used, AND
|
This MUST actively save the Attribute to whatever database backend is used, AND
|
||||||
call self.set_cache(key, category, new_attrobj)
|
call self.set_cache(key, category, new_attrobj)
|
||||||
|
|
@ -714,7 +713,8 @@ class IAttributeBackend:
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# have to cast the results to a list or we'll get a RuntimeError for removing from the dict we're iterating
|
# have to cast the results to a list or we'll get a RuntimeError for removing from the
|
||||||
|
# dict we're iterating
|
||||||
self.do_batch_delete(list(attrs))
|
self.do_batch_delete(list(attrs))
|
||||||
self.reset_cache()
|
self.reset_cache()
|
||||||
|
|
||||||
|
|
@ -735,10 +735,10 @@ class IAttributeBackend:
|
||||||
|
|
||||||
class InMemoryAttributeBackend(IAttributeBackend):
|
class InMemoryAttributeBackend(IAttributeBackend):
|
||||||
"""
|
"""
|
||||||
This Backend for Attributes stores NOTHING in the database. Everything is kept in memory, and normally lost
|
This Backend for Attributes stores NOTHING in the database. Everything is kept in memory, and
|
||||||
on a crash, reload, shared memory flush, etc. It generates IDs for the Attributes it manages, but these are
|
normally lost on a crash, reload, shared memory flush, etc. It generates IDs for the Attributes
|
||||||
of little importance beyond sorting and satisfying the caching logic to know an Attribute hasn't been
|
it manages, but these are of little importance beyond sorting and satisfying the caching logic
|
||||||
deleted out from under the cache's nose.
|
to know an Attribute hasn't been deleted out from under the cache's nose.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -810,7 +810,8 @@ class InMemoryAttributeBackend(IAttributeBackend):
|
||||||
|
|
||||||
def do_delete_attribute(self, attr):
|
def do_delete_attribute(self, attr):
|
||||||
"""
|
"""
|
||||||
Removes the Attribute from local storage. Once it's out of the cache, garbage collection will handle the rest.
|
Removes the Attribute from local storage. Once it's out of the cache, garbage collection
|
||||||
|
will handle the rest.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
attr (IAttribute): The attribute to delete.
|
attr (IAttribute): The attribute to delete.
|
||||||
|
|
@ -929,8 +930,9 @@ class AttributeHandler:
|
||||||
Setup the AttributeHandler.
|
Setup the AttributeHandler.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
obj (TypedObject): An Account, Object, Channel, ServerSession (not technically a typed object), etc.
|
obj (TypedObject): An Account, Object, Channel, ServerSession (not technically a typed
|
||||||
backend_class (IAttributeBackend class): The class of the backend to use.
|
object), etc. backend_class (IAttributeBackend class): The class of the backend to
|
||||||
|
use.
|
||||||
"""
|
"""
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
self.backend = backend_class(self, self._attrtype)
|
self.backend = backend_class(self, self._attrtype)
|
||||||
|
|
@ -1263,6 +1265,7 @@ class DbHolder:
|
||||||
all = property(get_all)
|
all = property(get_all)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
# Nick templating
|
# Nick templating
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
@ -1282,7 +1285,7 @@ This happens in two steps:
|
||||||
|
|
||||||
This will be converted to the following regex:
|
This will be converted to the following regex:
|
||||||
|
|
||||||
\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+)
|
\@desc (?P<1>\w+) (?P<2>\w+) $(?P<3>\w+)
|
||||||
|
|
||||||
Supported template markers (through fnmatch)
|
Supported template markers (through fnmatch)
|
||||||
* matches anything (non-greedy) -> .*?
|
* matches anything (non-greedy) -> .*?
|
||||||
|
|
@ -1345,7 +1348,7 @@ def initialize_nick_templates(pattern, replacement, pattern_is_regex=False):
|
||||||
# groups. we need to split out any | - separated parts so we can
|
# groups. we need to split out any | - separated parts so we can
|
||||||
# attach the line-break/ending extras all regexes require.
|
# attach the line-break/ending extras all regexes require.
|
||||||
pattern_regex_string = r"|".join(
|
pattern_regex_string = r"|".join(
|
||||||
or_part + r"(?:[\n\r]*?)\Z"
|
or_part + r"(?:[\n\r]*?)\Z"
|
||||||
for or_part in _RE_OR.split(pattern))
|
for or_part in _RE_OR.split(pattern))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
|
|
@ -595,13 +595,21 @@ class TypeclassManager(TypedObjectManager):
|
||||||
Search by supplying a string with optional extra search criteria to aid the query.
|
Search by supplying a string with optional extra search criteria to aid the query.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
query (str): A search criteria that accepts extra search criteria on the
|
query (str): A search criteria that accepts extra search criteria on the following
|
||||||
|
forms:
|
||||||
|
|
||||||
|
[key|alias|#dbref...]
|
||||||
|
[tag==<tagstr>[:category]...]
|
||||||
|
[attr==<key>:<value>:category...]
|
||||||
|
|
||||||
|
All three can be combined in the same query, separated by spaces.
|
||||||
|
|
||||||
following forms: [key|alias|#dbref...] [tag==<tagstr>[:category]...] [attr==<key>:<value>:category...]
|
|
||||||
" != " != "
|
|
||||||
Returns:
|
Returns:
|
||||||
matches (queryset): A queryset result matching all queries exactly. If wanting to use spaces or
|
matches (queryset): A queryset result matching all queries exactly. If wanting to use
|
||||||
==, != in tags or attributes, enclose them in quotes.
|
spaces or ==, != in tags or attributes, enclose them in quotes.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
house = smart_search("key=foo alias=bar tag=house:building tag=magic attr=color:red")
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
The flexibility of this method is limited by the input line format. Tag/attribute
|
The flexibility of this method is limited by the input line format. Tag/attribute
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ _SA = object.__setattr__
|
||||||
def call_at_first_save(sender, instance, created, **kwargs):
|
def call_at_first_save(sender, instance, created, **kwargs):
|
||||||
"""
|
"""
|
||||||
Receives a signal just after the object is saved.
|
Receives a signal just after the object is saved.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if created:
|
if created:
|
||||||
instance.at_first_save()
|
instance.at_first_save()
|
||||||
|
|
@ -77,6 +78,7 @@ def call_at_first_save(sender, instance, created, **kwargs):
|
||||||
def remove_attributes_on_delete(sender, instance, **kwargs):
|
def remove_attributes_on_delete(sender, instance, **kwargs):
|
||||||
"""
|
"""
|
||||||
Wipe object's Attributes when it's deleted
|
Wipe object's Attributes when it's deleted
|
||||||
|
|
||||||
"""
|
"""
|
||||||
instance.db_attributes.all().delete()
|
instance.db_attributes.all().delete()
|
||||||
|
|
||||||
|
|
@ -98,6 +100,7 @@ class TypeclassBase(SharedMemoryModelBase):
|
||||||
Metaclass which should be set for the root of model proxies
|
Metaclass which should be set for the root of model proxies
|
||||||
that don't define any new fields, like Object, Script etc. This
|
that don't define any new fields, like Object, Script etc. This
|
||||||
is the basis for the typeclassing system.
|
is the basis for the typeclassing system.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(cls, name, bases, attrs):
|
def __new__(cls, name, bases, attrs):
|
||||||
|
|
@ -208,7 +211,8 @@ class TypedObject(SharedMemoryModel):
|
||||||
"typeclass",
|
"typeclass",
|
||||||
max_length=255,
|
max_length=255,
|
||||||
null=True,
|
null=True,
|
||||||
help_text="this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.",
|
help_text="this defines what 'type' of entity this is. This variable holds "
|
||||||
|
"a Python path to a module with a valid Evennia Typeclass.",
|
||||||
db_index=True,
|
db_index=True,
|
||||||
)
|
)
|
||||||
# Creation date. This is not changed once the object is created.
|
# Creation date. This is not changed once the object is created.
|
||||||
|
|
@ -217,16 +221,20 @@ class TypedObject(SharedMemoryModel):
|
||||||
db_lock_storage = models.TextField(
|
db_lock_storage = models.TextField(
|
||||||
"locks",
|
"locks",
|
||||||
blank=True,
|
blank=True,
|
||||||
help_text="locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.",
|
help_text="locks limit access to an entity. A lock is defined as a 'lock string' "
|
||||||
|
"on the form 'type:lockfunctions', defining what functionality is locked and "
|
||||||
|
"how to determine access. Not defining a lock means no access is granted.",
|
||||||
)
|
)
|
||||||
# many2many relationships
|
# many2many relationships
|
||||||
db_attributes = models.ManyToManyField(
|
db_attributes = models.ManyToManyField(
|
||||||
Attribute,
|
Attribute,
|
||||||
help_text="attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).",
|
help_text="attributes on this object. An attribute can hold any pickle-able "
|
||||||
|
"python object (see docs for special cases).",
|
||||||
)
|
)
|
||||||
db_tags = models.ManyToManyField(
|
db_tags = models.ManyToManyField(
|
||||||
Tag,
|
Tag,
|
||||||
help_text="tags on this object. Tags are simple string markers to identify, group and alias objects.",
|
help_text="tags on this object. Tags are simple string markers to identify, "
|
||||||
|
"group and alias objects.",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Database manager
|
# Database manager
|
||||||
|
|
@ -701,8 +709,8 @@ class TypedObject(SharedMemoryModel):
|
||||||
# Attribute storage
|
# Attribute storage
|
||||||
#
|
#
|
||||||
|
|
||||||
# @property db
|
@property
|
||||||
def __db_get(self):
|
def db(self):
|
||||||
"""
|
"""
|
||||||
Attribute handler wrapper. Allows for the syntax
|
Attribute handler wrapper. Allows for the syntax
|
||||||
|
|
||||||
|
|
@ -725,26 +733,24 @@ class TypedObject(SharedMemoryModel):
|
||||||
self._db_holder = DbHolder(self, "attributes")
|
self._db_holder = DbHolder(self, "attributes")
|
||||||
return self._db_holder
|
return self._db_holder
|
||||||
|
|
||||||
# @db.setter
|
@db.setter
|
||||||
def __db_set(self, value):
|
def db(self, value):
|
||||||
"Stop accidentally replacing the db object"
|
"Stop accidentally replacing the db object"
|
||||||
string = "Cannot assign directly to db object! "
|
string = "Cannot assign directly to db object! "
|
||||||
string += "Use db.attr=value instead."
|
string += "Use db.attr=value instead."
|
||||||
raise Exception(string)
|
raise Exception(string)
|
||||||
|
|
||||||
# @db.deleter
|
@db.deleter
|
||||||
def __db_del(self):
|
def db(self):
|
||||||
"Stop accidental deletion."
|
"Stop accidental deletion."
|
||||||
raise Exception("Cannot delete the db object!")
|
raise Exception("Cannot delete the db object!")
|
||||||
|
|
||||||
db = property(__db_get, __db_set, __db_del)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Non-persistent (ndb) storage
|
# Non-persistent (ndb) storage
|
||||||
#
|
#
|
||||||
|
|
||||||
# @property ndb
|
@property
|
||||||
def __ndb_get(self):
|
def ndb(self):
|
||||||
"""
|
"""
|
||||||
A non-attr_obj store (ndb: NonDataBase). Everything stored
|
A non-attr_obj store (ndb: NonDataBase). Everything stored
|
||||||
to this is guaranteed to be cleared when a server is shutdown.
|
to this is guaranteed to be cleared when a server is shutdown.
|
||||||
|
|
@ -757,20 +763,18 @@ class TypedObject(SharedMemoryModel):
|
||||||
self._ndb_holder = DbHolder(self, "nattrhandler", manager_name="nattributes")
|
self._ndb_holder = DbHolder(self, "nattrhandler", manager_name="nattributes")
|
||||||
return self._ndb_holder
|
return self._ndb_holder
|
||||||
|
|
||||||
# @db.setter
|
@ndb.setter
|
||||||
def __ndb_set(self, value):
|
def ndb(self, value):
|
||||||
"Stop accidentally replacing the ndb object"
|
"Stop accidentally replacing the ndb object"
|
||||||
string = "Cannot assign directly to ndb object! "
|
string = "Cannot assign directly to ndb object! "
|
||||||
string += "Use ndb.attr=value instead."
|
string += "Use ndb.attr=value instead."
|
||||||
raise Exception(string)
|
raise Exception(string)
|
||||||
|
|
||||||
# @db.deleter
|
@ndb.deleter
|
||||||
def __ndb_del(self):
|
def ndb(self):
|
||||||
"Stop accidental deletion."
|
"Stop accidental deletion."
|
||||||
raise Exception("Cannot delete the ndb object!")
|
raise Exception("Cannot delete the ndb object!")
|
||||||
|
|
||||||
ndb = property(__ndb_get, __ndb_set, __ndb_del)
|
|
||||||
|
|
||||||
def get_display_name(self, looker, **kwargs):
|
def get_display_name(self, looker, **kwargs):
|
||||||
"""
|
"""
|
||||||
Displays the name of the object in a viewer-aware manner.
|
Displays the name of the object in a viewer-aware manner.
|
||||||
|
|
@ -879,7 +883,7 @@ class TypedObject(SharedMemoryModel):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_detail_url(self):
|
def web_get_detail_url(self):
|
||||||
|
|
@ -919,7 +923,7 @@ class TypedObject(SharedMemoryModel):
|
||||||
"%s-detail" % slugify(self._meta.verbose_name),
|
"%s-detail" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_puppet_url(self):
|
def web_get_puppet_url(self):
|
||||||
|
|
@ -931,19 +935,17 @@ class TypedObject(SharedMemoryModel):
|
||||||
str: URI path to object puppet page, if defined.
|
str: URI path to object puppet page, if defined.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
::
|
||||||
|
|
||||||
```python
|
|
||||||
Oscar (Character) = '/characters/oscar/1/puppet/'
|
Oscar (Character) = '/characters/oscar/1/puppet/'
|
||||||
```
|
|
||||||
|
|
||||||
For this to work, the developer must have defined a named view somewhere
|
For this to work, the developer must have defined a named view somewhere
|
||||||
in urls.py that follows the format 'modelname-action', so in this case
|
in urls.py that follows the format 'modelname-action', so in this case
|
||||||
a named view of 'character-puppet' would be referenced by this method.
|
a named view of 'character-puppet' would be referenced by this method.
|
||||||
|
::
|
||||||
|
|
||||||
```python
|
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/puppet/$',
|
||||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/puppet/$',
|
CharPuppetView.as_view(), name='character-puppet')
|
||||||
CharPuppetView.as_view(), name='character-puppet')
|
|
||||||
```
|
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an
|
If no View has been created and defined in urls.py, returns an
|
||||||
HTML anchor.
|
HTML anchor.
|
||||||
|
|
@ -959,7 +961,7 @@ class TypedObject(SharedMemoryModel):
|
||||||
"%s-puppet" % slugify(self._meta.verbose_name),
|
"%s-puppet" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_update_url(self):
|
def web_get_update_url(self):
|
||||||
|
|
@ -979,11 +981,10 @@ class TypedObject(SharedMemoryModel):
|
||||||
For this to work, the developer must have defined a named view somewhere
|
For this to work, the developer must have defined a named view somewhere
|
||||||
in urls.py that follows the format 'modelname-action', so in this case
|
in urls.py that follows the format 'modelname-action', so in this case
|
||||||
a named view of 'character-update' would be referenced by this method.
|
a named view of 'character-update' would be referenced by this method.
|
||||||
|
::
|
||||||
|
|
||||||
```python
|
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
|
||||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
|
|
||||||
CharUpdateView.as_view(), name='character-update')
|
CharUpdateView.as_view(), name='character-update')
|
||||||
```
|
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an
|
If no View has been created and defined in urls.py, returns an
|
||||||
HTML anchor.
|
HTML anchor.
|
||||||
|
|
@ -999,7 +1000,7 @@ class TypedObject(SharedMemoryModel):
|
||||||
"%s-update" % slugify(self._meta.verbose_name),
|
"%s-update" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
def web_get_delete_url(self):
|
def web_get_delete_url(self):
|
||||||
|
|
@ -1019,11 +1020,10 @@ class TypedObject(SharedMemoryModel):
|
||||||
somewhere in urls.py that follows the format 'modelname-action', so
|
somewhere in urls.py that follows the format 'modelname-action', so
|
||||||
in this case a named view of 'character-detail' would be referenced
|
in this case a named view of 'character-detail' would be referenced
|
||||||
by this method.
|
by this method.
|
||||||
|
::
|
||||||
|
|
||||||
```python
|
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$',
|
||||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$',
|
|
||||||
CharDeleteView.as_view(), name='character-delete')
|
CharDeleteView.as_view(), name='character-delete')
|
||||||
```
|
|
||||||
|
|
||||||
If no View has been created and defined in urls.py, returns an HTML
|
If no View has been created and defined in urls.py, returns an HTML
|
||||||
anchor.
|
anchor.
|
||||||
|
|
@ -1039,7 +1039,7 @@ class TypedObject(SharedMemoryModel):
|
||||||
"%s-delete" % slugify(self._meta.verbose_name),
|
"%s-delete" % slugify(self._meta.verbose_name),
|
||||||
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
||||||
)
|
)
|
||||||
except:
|
except Exception:
|
||||||
return "#"
|
return "#"
|
||||||
|
|
||||||
# Used by Django Sites/Admin
|
# Used by Django Sites/Admin
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,10 @@ class TagHandler(object):
|
||||||
self._cache_complete = False
|
self._cache_complete = False
|
||||||
|
|
||||||
def _query_all(self):
|
def _query_all(self):
|
||||||
"Get all tags for this objects"
|
"""
|
||||||
|
Get all tags for this object.
|
||||||
|
|
||||||
|
"""
|
||||||
query = {
|
query = {
|
||||||
"%s__id" % self._model: self._objid,
|
"%s__id" % self._model: self._objid,
|
||||||
"tag__db_model": self._model,
|
"tag__db_model": self._model,
|
||||||
|
|
@ -137,7 +140,10 @@ class TagHandler(object):
|
||||||
]
|
]
|
||||||
|
|
||||||
def _fullcache(self):
|
def _fullcache(self):
|
||||||
"Cache all tags of this object"
|
"""
|
||||||
|
Cache all tags of this object.
|
||||||
|
|
||||||
|
"""
|
||||||
if not _TYPECLASS_AGGRESSIVE_CACHE:
|
if not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||||
return
|
return
|
||||||
tags = self._query_all()
|
tags = self._query_all()
|
||||||
|
|
@ -277,6 +283,7 @@ class TagHandler(object):
|
||||||
def reset_cache(self):
|
def reset_cache(self):
|
||||||
"""
|
"""
|
||||||
Reset the cache from the outside.
|
Reset the cache from the outside.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._cache_complete = False
|
self._cache_complete = False
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
|
|
@ -483,8 +490,9 @@ class TagHandler(object):
|
||||||
Batch-add tags from a list of tuples.
|
Batch-add tags from a list of tuples.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*args (tuple or str): Each argument should be a `tagstr` keys or tuple `(keystr, category)` or
|
*args (tuple or str): Each argument should be a `tagstr` keys or tuple
|
||||||
`(keystr, category, data)`. It's possible to mix input types.
|
`(keystr, category)` or `(keystr, category, data)`. It's possible to mix input
|
||||||
|
types.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
This will generate a mimimal number of self.add calls,
|
This will generate a mimimal number of self.add calls,
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ Supported standards:
|
||||||
|
|
||||||
## Markup
|
## Markup
|
||||||
|
|
||||||
ANSI colors: `r` ed, `g` reen, `y` ellow, `b` lue, `m` agenta, `c` yan, `n` ormal (no color). Capital
|
ANSI colors: `r` ed, `g` reen, `y` ellow, `b` lue, `m` agenta, `c` yan, `n` ormal (no color).
|
||||||
letters indicate the 'dark' variant.
|
Capital letters indicate the 'dark' variant.
|
||||||
|
|
||||||
- `|r` fg bright red
|
- `|r` fg bright red
|
||||||
- `|R` fg dark red
|
- `|R` fg dark red
|
||||||
|
|
@ -337,8 +337,9 @@ class ANSIParser(object):
|
||||||
colval = 16 + (red * 36) + (green * 6) + blue
|
colval = 16 + (red * 36) + (green * 6) + blue
|
||||||
|
|
||||||
return "\033[%s8;5;%sm" % (3 + int(background), colval)
|
return "\033[%s8;5;%sm" % (3 + int(background), colval)
|
||||||
# replaced since some clients (like Potato) does not accept codes with leading zeroes, see issue #1024.
|
# replaced since some clients (like Potato) does not accept codes with leading zeroes,
|
||||||
# return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10)
|
# see issue #1024.
|
||||||
|
# return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10) # noqa
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# xterm256 not supported, convert the rgb value to ansi instead
|
# xterm256 not supported, convert the rgb value to ansi instead
|
||||||
|
|
@ -729,7 +730,8 @@ class ANSIString(str, metaclass=ANSIMeta):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# A compiled Regex for the format mini-language: https://docs.python.org/3/library/string.html#formatspec
|
# A compiled Regex for the format mini-language:
|
||||||
|
# https://docs.python.org/3/library/string.html#formatspec
|
||||||
re_format = re.compile(
|
re_format = re.compile(
|
||||||
r"(?i)(?P<just>(?P<fill>.)?(?P<align>\<|\>|\=|\^))?(?P<sign>\+|\-| )?(?P<alt>\#)?"
|
r"(?i)(?P<just>(?P<fill>.)?(?P<align>\<|\>|\=|\^))?(?P<sign>\+|\-| )?(?P<alt>\#)?"
|
||||||
r"(?P<zero>0)?(?P<width>\d+)?(?P<grouping>\_|\,)?(?:\.(?P<precision>\d+))?"
|
r"(?P<zero>0)?(?P<width>\d+)?(?P<grouping>\_|\,)?(?:\.(?P<precision>\d+))?"
|
||||||
|
|
@ -802,12 +804,14 @@ class ANSIString(str, metaclass=ANSIMeta):
|
||||||
Current features supported: fill, align, width.
|
Current features supported: fill, align, width.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
format_spec (str): The format specification passed by f-string or str.format(). This is a string such as
|
format_spec (str): The format specification passed by f-string or str.format(). This is
|
||||||
"0<30" which would mean "left justify to 30, filling with zeros". The full specification can be found
|
a string such as "0<30" which would mean "left justify to 30, filling with zeros".
|
||||||
at https://docs.python.org/3/library/string.html#formatspec
|
The full specification can be found at
|
||||||
|
https://docs.python.org/3/library/string.html#formatspec
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ansi_str (str): The formatted ANSIString's .raw() form, for display.
|
ansi_str (str): The formatted ANSIString's .raw() form, for display.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# This calls the compiled regex stored on ANSIString's class to analyze the format spec.
|
# This calls the compiled regex stored on ANSIString's class to analyze the format spec.
|
||||||
# It returns a dictionary.
|
# It returns a dictionary.
|
||||||
|
|
@ -1067,7 +1071,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
||||||
current_index = 0
|
current_index = 0
|
||||||
result = tuple()
|
result = tuple()
|
||||||
for section in parent_result:
|
for section in parent_result:
|
||||||
result += (self[current_index : current_index + len(section)],)
|
result += (self[current_index: current_index + len(section)],)
|
||||||
current_index += len(section)
|
current_index += len(section)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -1187,7 +1191,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
||||||
start = next + bylen
|
start = next + bylen
|
||||||
maxsplit -= 1 # NB. if it's already < 0, it stays < 0
|
maxsplit -= 1 # NB. if it's already < 0, it stays < 0
|
||||||
|
|
||||||
res.append(self[start : len(self)])
|
res.append(self[start: len(self)])
|
||||||
if drop_spaces:
|
if drop_spaces:
|
||||||
return [part for part in res if part != ""]
|
return [part for part in res if part != ""]
|
||||||
return res
|
return res
|
||||||
|
|
@ -1230,7 +1234,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
||||||
if next < 0:
|
if next < 0:
|
||||||
break
|
break
|
||||||
# Get character codes after the index as well.
|
# Get character codes after the index as well.
|
||||||
res.append(self[next + bylen : end])
|
res.append(self[next + bylen: end])
|
||||||
end = next
|
end = next
|
||||||
maxsplit -= 1 # NB. if it's already < 0, it stays < 0
|
maxsplit -= 1 # NB. if it's already < 0, it stays < 0
|
||||||
|
|
||||||
|
|
@ -1284,7 +1288,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
||||||
ic -= 1
|
ic -= 1
|
||||||
ir2 -= 1
|
ir2 -= 1
|
||||||
rstripped = rstripped[::-1]
|
rstripped = rstripped[::-1]
|
||||||
return ANSIString(lstripped + raw[ir1 : ir2 + 1] + rstripped)
|
return ANSIString(lstripped + raw[ir1: ir2 + 1] + rstripped)
|
||||||
|
|
||||||
def lstrip(self, chars=None):
|
def lstrip(self, chars=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -1403,7 +1407,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
||||||
start = None
|
start = None
|
||||||
end = char._char_indexes[0]
|
end = char._char_indexes[0]
|
||||||
prefix = char._raw_string[start:end]
|
prefix = char._raw_string[start:end]
|
||||||
postfix = char._raw_string[end + 1 :]
|
postfix = char._raw_string[end + 1:]
|
||||||
line = char._clean_string * amount
|
line = char._clean_string * amount
|
||||||
code_indexes = [i for i in range(0, len(prefix))]
|
code_indexes = [i for i in range(0, len(prefix))]
|
||||||
length = len(prefix) + len(line)
|
length = len(prefix) + len(line)
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,7 @@ class BatchCommandProcessor(object):
|
||||||
try:
|
try:
|
||||||
path = match.group(1)
|
path = match.group(1)
|
||||||
return "\n#\n".join(self.parse_file(path))
|
return "\n#\n".join(self.parse_file(path))
|
||||||
except IOError as err:
|
except IOError:
|
||||||
raise IOError("#INSERT {} failed.".format(path))
|
raise IOError("#INSERT {} failed.".format(path))
|
||||||
|
|
||||||
text = _RE_INSERT.sub(replace_insert, text)
|
text = _RE_INSERT.sub(replace_insert, text)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from evennia.utils import logger
|
||||||
SCRIPTDB = None
|
SCRIPTDB = None
|
||||||
|
|
||||||
|
|
||||||
class Container(object):
|
class Container:
|
||||||
"""
|
"""
|
||||||
Base container class. A container is simply a storage object whose
|
Base container class. A container is simply a storage object whose
|
||||||
properties can be acquired as a property on it. This is generally
|
properties can be acquired as a property on it. This is generally
|
||||||
|
|
@ -156,9 +156,8 @@ class GlobalScriptContainer(Container):
|
||||||
return new_script
|
return new_script
|
||||||
|
|
||||||
if ((found.interval != interval)
|
if ((found.interval != interval)
|
||||||
or (found.start_delay != start_delay)
|
or (found.start_delay != start_delay)
|
||||||
or (found.repeats != repeats)
|
or (found.repeats != repeats)):
|
||||||
):
|
|
||||||
# the setup changed
|
# the setup changed
|
||||||
found.start(interval=interval, start_delay=start_delay, repeats=repeats)
|
found.start(interval=interval, start_delay=start_delay, repeats=repeats)
|
||||||
if found.desc != desc:
|
if found.desc != desc:
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ except ImportError:
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.utils.safestring import SafeString
|
from django.utils.safestring import SafeString
|
||||||
from evennia.utils.utils import uses_database, is_iter, to_str, to_bytes
|
from evennia.utils.utils import uses_database, is_iter, to_bytes
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
||||||
__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle", "dbserialize", "dbunserialize")
|
__all__ = ("to_pickle", "from_pickle", "do_pickle", "do_unpickle", "dbserialize", "dbunserialize")
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,11 @@ survive a reload. See the `EvEditor` class for more details.
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia import Command, CmdSet
|
from evennia import CmdSet
|
||||||
from evennia.utils import is_iter, fill, dedent, logger, justify, to_str, utils
|
from evennia.utils import is_iter, fill, dedent, logger, justify, to_str, utils
|
||||||
from evennia.utils.ansi import raw
|
from evennia.utils.ansi import raw
|
||||||
from evennia.commands import cmdhandler
|
from evennia.commands import cmdhandler
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
# we use cmdhandler instead of evennia.syscmdkeys to
|
# we use cmdhandler instead of evennia.syscmdkeys to
|
||||||
# avoid some cases of loading before evennia init'd
|
# avoid some cases of loading before evennia init'd
|
||||||
|
|
@ -63,7 +64,7 @@ _DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||||
#
|
#
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
|
|
||||||
_HELP_TEXT = """
|
_HELP_TEXT = _("""
|
||||||
<txt> - any non-command is appended to the end of the buffer.
|
<txt> - any non-command is appended to the end of the buffer.
|
||||||
: <l> - view buffer or only line(s) <l>
|
: <l> - view buffer or only line(s) <l>
|
||||||
:: <l> - raw-view buffer or only line(s) <l>
|
:: <l> - raw-view buffer or only line(s) <l>
|
||||||
|
|
@ -99,66 +100,66 @@ _HELP_TEXT = """
|
||||||
:fd <l> - de-indent entire buffer or line <l>
|
:fd <l> - de-indent entire buffer or line <l>
|
||||||
|
|
||||||
:echo - turn echoing of the input on/off (helpful for some clients)
|
:echo - turn echoing of the input on/off (helpful for some clients)
|
||||||
"""
|
""")
|
||||||
|
|
||||||
_HELP_LEGEND = """
|
_HELP_LEGEND = _("""
|
||||||
Legend:
|
Legend:
|
||||||
<l> - line number, like '5' or range, like '3:7'.
|
<l> - line number, like '5' or range, like '3:7'.
|
||||||
<w> - a single word, or multiple words with quotes around them.
|
<w> - a single word, or multiple words with quotes around them.
|
||||||
<txt> - longer string, usually not needing quotes.
|
<txt> - longer string, usually not needing quotes.
|
||||||
"""
|
""")
|
||||||
|
|
||||||
_HELP_CODE = """
|
_HELP_CODE = _("""
|
||||||
:! - Execute code buffer without saving
|
:! - Execute code buffer without saving
|
||||||
:< - Decrease the level of automatic indentation for the next lines
|
:< - Decrease the level of automatic indentation for the next lines
|
||||||
:> - Increase the level of automatic indentation for the next lines
|
:> - Increase the level of automatic indentation for the next lines
|
||||||
:= - Switch automatic indentation on/off
|
:= - Switch automatic indentation on/off
|
||||||
""".lstrip(
|
""".lstrip(
|
||||||
"\n"
|
"\n"
|
||||||
)
|
))
|
||||||
|
|
||||||
_ERROR_LOADFUNC = """
|
_ERROR_LOADFUNC = _("""
|
||||||
{error}
|
{error}
|
||||||
|
|
||||||
|rBuffer load function error. Could not load initial data.|n
|
|rBuffer load function error. Could not load initial data.|n
|
||||||
"""
|
""")
|
||||||
|
|
||||||
_ERROR_SAVEFUNC = """
|
_ERROR_SAVEFUNC = _("""
|
||||||
{error}
|
{error}
|
||||||
|
|
||||||
|rSave function returned an error. Buffer not saved.|n
|
|rSave function returned an error. Buffer not saved.|n
|
||||||
"""
|
""")
|
||||||
|
|
||||||
_ERROR_NO_SAVEFUNC = "|rNo save function defined. Buffer cannot be saved.|n"
|
_ERROR_NO_SAVEFUNC = _("|rNo save function defined. Buffer cannot be saved.|n")
|
||||||
|
|
||||||
_MSG_SAVE_NO_CHANGE = "No changes need saving"
|
_MSG_SAVE_NO_CHANGE = _("No changes need saving")
|
||||||
_DEFAULT_NO_QUITFUNC = "Exited editor."
|
_DEFAULT_NO_QUITFUNC = _("Exited editor.")
|
||||||
|
|
||||||
_ERROR_QUITFUNC = """
|
_ERROR_QUITFUNC = _("""
|
||||||
{error}
|
{error}
|
||||||
|
|
||||||
|rQuit function gave an error. Skipping.|n
|
|rQuit function gave an error. Skipping.|n
|
||||||
"""
|
""")
|
||||||
|
|
||||||
_ERROR_PERSISTENT_SAVING = """
|
_ERROR_PERSISTENT_SAVING = _("""
|
||||||
{error}
|
{error}
|
||||||
|
|
||||||
|rThe editor state could not be saved for persistent mode. Switching
|
|rThe editor state could not be saved for persistent mode. Switching
|
||||||
to non-persistent mode (which means the editor session won't survive
|
to non-persistent mode (which means the editor session won't survive
|
||||||
an eventual server reload - so save often!)|n
|
an eventual server reload - so save often!)|n
|
||||||
"""
|
""")
|
||||||
|
|
||||||
_TRACE_PERSISTENT_SAVING = (
|
_TRACE_PERSISTENT_SAVING = _(
|
||||||
"EvEditor persistent-mode error. Commonly, this is because one or "
|
"EvEditor persistent-mode error. Commonly, this is because one or "
|
||||||
"more of the EvEditor callbacks could not be pickled, for example "
|
"more of the EvEditor callbacks could not be pickled, for example "
|
||||||
"because it's a class method or is defined inside another function."
|
"because it's a class method or is defined inside another function."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_MSG_NO_UNDO = "Nothing to undo."
|
_MSG_NO_UNDO = _("Nothing to undo.")
|
||||||
_MSG_NO_REDO = "Nothing to redo."
|
_MSG_NO_REDO = _("Nothing to redo.")
|
||||||
_MSG_UNDO = "Undid one step."
|
_MSG_UNDO = _("Undid one step.")
|
||||||
_MSG_REDO = "Redid one step."
|
_MSG_REDO = _("Redid one step.")
|
||||||
|
|
||||||
# -------------------------------------------------------------
|
# -------------------------------------------------------------
|
||||||
#
|
#
|
||||||
|
|
@ -180,7 +181,10 @@ class CmdSaveYesNo(_COMMAND_DEFAULT_CLASS):
|
||||||
help_cateogory = "LineEditor"
|
help_cateogory = "LineEditor"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"""Implement the yes/no choice."""
|
"""
|
||||||
|
Implement the yes/no choice.
|
||||||
|
|
||||||
|
"""
|
||||||
# this is only called from inside the lineeditor
|
# this is only called from inside the lineeditor
|
||||||
# so caller.ndb._lineditor must be set.
|
# so caller.ndb._lineditor must be set.
|
||||||
|
|
||||||
|
|
@ -195,7 +199,10 @@ class CmdSaveYesNo(_COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
|
|
||||||
class SaveYesNoCmdSet(CmdSet):
|
class SaveYesNoCmdSet(CmdSet):
|
||||||
"""Stores the yesno question"""
|
"""
|
||||||
|
Stores the yesno question
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
key = "quitsave_yesno"
|
key = "quitsave_yesno"
|
||||||
priority = 150 # override other cmdsets.
|
priority = 150 # override other cmdsets.
|
||||||
|
|
@ -331,6 +338,7 @@ class CmdEditorBase(_COMMAND_DEFAULT_CLASS):
|
||||||
def _load_editor(caller):
|
def _load_editor(caller):
|
||||||
"""
|
"""
|
||||||
Load persistent editor from storage.
|
Load persistent editor from storage.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
saved_options = caller.attributes.get("_eveditor_saved")
|
saved_options = caller.attributes.get("_eveditor_saved")
|
||||||
saved_buffer, saved_undo = caller.attributes.get("_eveditor_buffer_temp", (None, None))
|
saved_buffer, saved_undo = caller.attributes.get("_eveditor_buffer_temp", (None, None))
|
||||||
|
|
@ -356,6 +364,7 @@ def _load_editor(caller):
|
||||||
class CmdLineInput(CmdEditorBase):
|
class CmdLineInput(CmdEditorBase):
|
||||||
"""
|
"""
|
||||||
No command match - Inputs line of text into buffer.
|
No command match - Inputs line of text into buffer.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
key = _CMD_NOMATCH
|
key = _CMD_NOMATCH
|
||||||
|
|
@ -440,6 +449,7 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
This command handles all the in-editor :-style commands. Since
|
This command handles all the in-editor :-style commands. Since
|
||||||
each command is small and very limited, this makes for a more
|
each command is small and very limited, this makes for a more
|
||||||
efficient presentation.
|
efficient presentation.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
editor = caller.ndb._eveditor
|
editor = caller.ndb._eveditor
|
||||||
|
|
@ -467,7 +477,7 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
# Insert single colon alone on a line
|
# Insert single colon alone on a line
|
||||||
editor.update_buffer([":"] if lstart == 0 else linebuffer + [":"])
|
editor.update_buffer([":"] if lstart == 0 else linebuffer + [":"])
|
||||||
if echo_mode:
|
if echo_mode:
|
||||||
caller.msg("Single ':' added to buffer.")
|
caller.msg(_("Single ':' added to buffer."))
|
||||||
elif cmd == ":h":
|
elif cmd == ":h":
|
||||||
# help entry
|
# help entry
|
||||||
editor.display_help()
|
editor.display_help()
|
||||||
|
|
@ -482,7 +492,7 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
# quit. If not saved, will ask
|
# quit. If not saved, will ask
|
||||||
if self.editor._unsaved:
|
if self.editor._unsaved:
|
||||||
caller.cmdset.add(SaveYesNoCmdSet)
|
caller.cmdset.add(SaveYesNoCmdSet)
|
||||||
caller.msg("Save before quitting? |lcyes|lt[Y]|le/|lcno|ltN|le")
|
caller.msg(_("Save before quitting?") + " |lcyes|lt[Y]|le/|lcno|ltN|le")
|
||||||
else:
|
else:
|
||||||
editor.quit()
|
editor.quit()
|
||||||
elif cmd == ":q!":
|
elif cmd == ":q!":
|
||||||
|
|
@ -497,24 +507,24 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
elif cmd == ":UU":
|
elif cmd == ":UU":
|
||||||
# reset buffer
|
# reset buffer
|
||||||
editor.update_buffer(editor._pristine_buffer)
|
editor.update_buffer(editor._pristine_buffer)
|
||||||
caller.msg("Reverted all changes to the buffer back to original state.")
|
caller.msg(_("Reverted all changes to the buffer back to original state."))
|
||||||
elif cmd == ":dd":
|
elif cmd == ":dd":
|
||||||
# :dd <l> - delete line <l>
|
# :dd <l> - delete line <l>
|
||||||
buf = linebuffer[:lstart] + linebuffer[lend:]
|
buf = linebuffer[:lstart] + linebuffer[lend:]
|
||||||
editor.update_buffer(buf)
|
editor.update_buffer(buf)
|
||||||
caller.msg("Deleted %s." % self.lstr)
|
caller.msg(_("Deleted {string}.").format(string= self.lstr))
|
||||||
elif cmd == ":dw":
|
elif cmd == ":dw":
|
||||||
# :dw <w> - delete word in entire buffer
|
# :dw <w> - delete word in entire buffer
|
||||||
# :dw <l> <w> delete word only on line(s) <l>
|
# :dw <l> <w> delete word only on line(s) <l>
|
||||||
if not self.arg1:
|
if not self.arg1:
|
||||||
caller.msg("You must give a search word to delete.")
|
caller.msg(_("You must give a search word to delete."))
|
||||||
else:
|
else:
|
||||||
if not self.linerange:
|
if not self.linerange:
|
||||||
lstart = 0
|
lstart = 0
|
||||||
lend = self.cline + 1
|
lend = self.cline + 1
|
||||||
caller.msg("Removed %s for lines %i-%i." % (self.arg1, lstart + 1, lend + 1))
|
caller.msg(_("Removed %s for lines %i-%i.") % (self.arg1, lstart + 1, lend + 1))
|
||||||
else:
|
else:
|
||||||
caller.msg("Removed %s for %s." % (self.arg1, self.lstr))
|
caller.msg(_("Removed %s for %s.") % (self.arg1, self.lstr))
|
||||||
sarea = "\n".join(linebuffer[lstart:lend])
|
sarea = "\n".join(linebuffer[lstart:lend])
|
||||||
sarea = re.sub(r"%s" % self.arg1.strip("'").strip('"'), "", sarea, re.MULTILINE)
|
sarea = re.sub(r"%s" % self.arg1.strip("'").strip('"'), "", sarea, re.MULTILINE)
|
||||||
buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:]
|
buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:]
|
||||||
|
|
@ -529,49 +539,49 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
editor._indent = 0
|
editor._indent = 0
|
||||||
if editor._persistent:
|
if editor._persistent:
|
||||||
caller.attributes.add("_eveditor_indent", 0)
|
caller.attributes.add("_eveditor_indent", 0)
|
||||||
caller.msg("Cleared %i lines from buffer." % self.nlines)
|
caller.msg(_("Cleared %i lines from buffer.") % self.nlines)
|
||||||
elif cmd == ":y":
|
elif cmd == ":y":
|
||||||
# :y <l> - yank line(s) to copy buffer
|
# :y <l> - yank line(s) to copy buffer
|
||||||
cbuf = linebuffer[lstart:lend]
|
cbuf = linebuffer[lstart:lend]
|
||||||
editor._copy_buffer = cbuf
|
editor._copy_buffer = cbuf
|
||||||
caller.msg("%s, %s yanked." % (self.lstr.capitalize(), cbuf))
|
caller.msg(_("%s, %s yanked.") % (self.lstr.capitalize(), cbuf))
|
||||||
elif cmd == ":x":
|
elif cmd == ":x":
|
||||||
# :x <l> - cut line to copy buffer
|
# :x <l> - cut line to copy buffer
|
||||||
cbuf = linebuffer[lstart:lend]
|
cbuf = linebuffer[lstart:lend]
|
||||||
editor._copy_buffer = cbuf
|
editor._copy_buffer = cbuf
|
||||||
buf = linebuffer[:lstart] + linebuffer[lend:]
|
buf = linebuffer[:lstart] + linebuffer[lend:]
|
||||||
editor.update_buffer(buf)
|
editor.update_buffer(buf)
|
||||||
caller.msg("%s, %s cut." % (self.lstr.capitalize(), cbuf))
|
caller.msg(_("%s, %s cut.") % (self.lstr.capitalize(), cbuf))
|
||||||
elif cmd == ":p":
|
elif cmd == ":p":
|
||||||
# :p <l> paste line(s) from copy buffer
|
# :p <l> paste line(s) from copy buffer
|
||||||
if not editor._copy_buffer:
|
if not editor._copy_buffer:
|
||||||
caller.msg("Copy buffer is empty.")
|
caller.msg(_("Copy buffer is empty."))
|
||||||
else:
|
else:
|
||||||
buf = linebuffer[:lstart] + editor._copy_buffer + linebuffer[lstart:]
|
buf = linebuffer[:lstart] + editor._copy_buffer + linebuffer[lstart:]
|
||||||
editor.update_buffer(buf)
|
editor.update_buffer(buf)
|
||||||
caller.msg("Pasted buffer %s to %s." % (editor._copy_buffer, self.lstr))
|
caller.msg(_("Pasted buffer %s to %s.") % (editor._copy_buffer, self.lstr))
|
||||||
elif cmd == ":i":
|
elif cmd == ":i":
|
||||||
# :i <l> <txt> - insert new line
|
# :i <l> <txt> - insert new line
|
||||||
new_lines = self.args.split("\n")
|
new_lines = self.args.split("\n")
|
||||||
if not new_lines:
|
if not new_lines:
|
||||||
caller.msg("You need to enter a new line and where to insert it.")
|
caller.msg(_("You need to enter a new line and where to insert it."))
|
||||||
else:
|
else:
|
||||||
buf = linebuffer[:lstart] + new_lines + linebuffer[lstart:]
|
buf = linebuffer[:lstart] + new_lines + linebuffer[lstart:]
|
||||||
editor.update_buffer(buf)
|
editor.update_buffer(buf)
|
||||||
caller.msg("Inserted %i new line(s) at %s." % (len(new_lines), self.lstr))
|
caller.msg(_("Inserted %i new line(s) at %s.") % (len(new_lines), self.lstr))
|
||||||
elif cmd == ":r":
|
elif cmd == ":r":
|
||||||
# :r <l> <txt> - replace lines
|
# :r <l> <txt> - replace lines
|
||||||
new_lines = self.args.split("\n")
|
new_lines = self.args.split("\n")
|
||||||
if not new_lines:
|
if not new_lines:
|
||||||
caller.msg("You need to enter a replacement string.")
|
caller.msg(_("You need to enter a replacement string."))
|
||||||
else:
|
else:
|
||||||
buf = linebuffer[:lstart] + new_lines + linebuffer[lend:]
|
buf = linebuffer[:lstart] + new_lines + linebuffer[lend:]
|
||||||
editor.update_buffer(buf)
|
editor.update_buffer(buf)
|
||||||
caller.msg("Replaced %i line(s) at %s." % (len(new_lines), self.lstr))
|
caller.msg(_("Replaced %i line(s) at %s.") % (len(new_lines), self.lstr))
|
||||||
elif cmd == ":I":
|
elif cmd == ":I":
|
||||||
# :I <l> <txt> - insert text at beginning of line(s) <l>
|
# :I <l> <txt> - insert text at beginning of line(s) <l>
|
||||||
if not self.raw_string and not editor._codefunc:
|
if not self.raw_string and not editor._codefunc:
|
||||||
caller.msg("You need to enter text to insert.")
|
caller.msg(_("You need to enter text to insert."))
|
||||||
else:
|
else:
|
||||||
buf = (
|
buf = (
|
||||||
linebuffer[:lstart]
|
linebuffer[:lstart]
|
||||||
|
|
@ -579,11 +589,11 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
+ linebuffer[lend:]
|
+ linebuffer[lend:]
|
||||||
)
|
)
|
||||||
editor.update_buffer(buf)
|
editor.update_buffer(buf)
|
||||||
caller.msg("Inserted text at beginning of %s." % self.lstr)
|
caller.msg(_("Inserted text at beginning of %s.") % self.lstr)
|
||||||
elif cmd == ":A":
|
elif cmd == ":A":
|
||||||
# :A <l> <txt> - append text after end of line(s)
|
# :A <l> <txt> - append text after end of line(s)
|
||||||
if not self.args:
|
if not self.args:
|
||||||
caller.msg("You need to enter text to append.")
|
caller.msg(_("You need to enter text to append."))
|
||||||
else:
|
else:
|
||||||
buf = (
|
buf = (
|
||||||
linebuffer[:lstart]
|
linebuffer[:lstart]
|
||||||
|
|
@ -591,23 +601,23 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
+ linebuffer[lend:]
|
+ linebuffer[lend:]
|
||||||
)
|
)
|
||||||
editor.update_buffer(buf)
|
editor.update_buffer(buf)
|
||||||
caller.msg("Appended text to end of %s." % self.lstr)
|
caller.msg(_("Appended text to end of %s.") % self.lstr)
|
||||||
elif cmd == ":s":
|
elif cmd == ":s":
|
||||||
# :s <li> <w> <txt> - search and replace words
|
# :s <li> <w> <txt> - search and replace words
|
||||||
# in entire buffer or on certain lines
|
# in entire buffer or on certain lines
|
||||||
if not self.arg1 or not self.arg2:
|
if not self.arg1 or not self.arg2:
|
||||||
caller.msg("You must give a search word and something to replace it with.")
|
caller.msg(_("You must give a search word and something to replace it with."))
|
||||||
else:
|
else:
|
||||||
if not self.linerange:
|
if not self.linerange:
|
||||||
lstart = 0
|
lstart = 0
|
||||||
lend = self.cline + 1
|
lend = self.cline + 1
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"Search-replaced %s -> %s for lines %i-%i."
|
_("Search-replaced %s -> %s for lines %i-%i.")
|
||||||
% (self.arg1, self.arg2, lstart + 1, lend)
|
% (self.arg1, self.arg2, lstart + 1, lend)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"Search-replaced %s -> %s for %s." % (self.arg1, self.arg2, self.lstr)
|
_("Search-replaced %s -> %s for %s.") % (self.arg1, self.arg2, self.lstr)
|
||||||
)
|
)
|
||||||
sarea = "\n".join(linebuffer[lstart:lend])
|
sarea = "\n".join(linebuffer[lstart:lend])
|
||||||
|
|
||||||
|
|
@ -629,9 +639,9 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
if not self.linerange:
|
if not self.linerange:
|
||||||
lstart = 0
|
lstart = 0
|
||||||
lend = self.cline + 1
|
lend = self.cline + 1
|
||||||
caller.msg("Flood filled lines %i-%i." % (lstart + 1, lend))
|
caller.msg(_("Flood filled lines %i-%i.") % (lstart + 1, lend))
|
||||||
else:
|
else:
|
||||||
caller.msg("Flood filled %s." % self.lstr)
|
caller.msg(_("Flood filled %s.") % self.lstr)
|
||||||
fbuf = "\n".join(linebuffer[lstart:lend])
|
fbuf = "\n".join(linebuffer[lstart:lend])
|
||||||
fbuf = fill(fbuf, width=width)
|
fbuf = fill(fbuf, width=width)
|
||||||
buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:]
|
buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:]
|
||||||
|
|
@ -653,16 +663,17 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
width = _DEFAULT_WIDTH
|
width = _DEFAULT_WIDTH
|
||||||
if self.arg1 and self.arg1.lower() not in align_map:
|
if self.arg1 and self.arg1.lower() not in align_map:
|
||||||
self.caller.msg(
|
self.caller.msg(
|
||||||
"Valid justifications are [f]ull (default), [c]enter, [r]right or [l]eft"
|
_("Valid justifications are")
|
||||||
|
+ " [f]ull (default), [c]enter, [r]right or [l]eft"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
align = align_map[self.arg1.lower()] if self.arg1 else "f"
|
align = align_map[self.arg1.lower()] if self.arg1 else "f"
|
||||||
if not self.linerange:
|
if not self.linerange:
|
||||||
lstart = 0
|
lstart = 0
|
||||||
lend = self.cline + 1
|
lend = self.cline + 1
|
||||||
self.caller.msg("%s-justified lines %i-%i." % (align_name[align], lstart + 1, lend))
|
self.caller.msg(_("%s-justified lines %i-%i.") % (align_name[align], lstart + 1, lend))
|
||||||
else:
|
else:
|
||||||
self.caller.msg("%s-justified %s." % (align_name[align], self.lstr))
|
self.caller.msg(_("%s-justified %s.") % (align_name[align], self.lstr))
|
||||||
jbuf = "\n".join(linebuffer[lstart:lend])
|
jbuf = "\n".join(linebuffer[lstart:lend])
|
||||||
jbuf = justify(jbuf, width=width, align=align)
|
jbuf = justify(jbuf, width=width, align=align)
|
||||||
buf = linebuffer[:lstart] + jbuf.split("\n") + linebuffer[lend:]
|
buf = linebuffer[:lstart] + jbuf.split("\n") + linebuffer[lend:]
|
||||||
|
|
@ -673,9 +684,9 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
if not self.linerange:
|
if not self.linerange:
|
||||||
lstart = 0
|
lstart = 0
|
||||||
lend = self.cline + 1
|
lend = self.cline + 1
|
||||||
caller.msg("Indented lines %i-%i." % (lstart + 1, lend))
|
caller.msg(_("Indented lines %i-%i.") % (lstart + 1, lend))
|
||||||
else:
|
else:
|
||||||
caller.msg("Indented %s." % self.lstr)
|
caller.msg(_("Indented %s.") % self.lstr)
|
||||||
fbuf = [indent + line for line in linebuffer[lstart:lend]]
|
fbuf = [indent + line for line in linebuffer[lstart:lend]]
|
||||||
buf = linebuffer[:lstart] + fbuf + linebuffer[lend:]
|
buf = linebuffer[:lstart] + fbuf + linebuffer[lend:]
|
||||||
editor.update_buffer(buf)
|
editor.update_buffer(buf)
|
||||||
|
|
@ -684,9 +695,9 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
if not self.linerange:
|
if not self.linerange:
|
||||||
lstart = 0
|
lstart = 0
|
||||||
lend = self.cline + 1
|
lend = self.cline + 1
|
||||||
caller.msg("Removed left margin (dedented) lines %i-%i." % (lstart + 1, lend))
|
caller.msg(_("Removed left margin (dedented) lines %i-%i.") % (lstart + 1, lend))
|
||||||
else:
|
else:
|
||||||
caller.msg("Removed left margin (dedented) %s." % self.lstr)
|
caller.msg(_("Removed left margin (dedented) %s.") % self.lstr)
|
||||||
fbuf = "\n".join(linebuffer[lstart:lend])
|
fbuf = "\n".join(linebuffer[lstart:lend])
|
||||||
fbuf = dedent(fbuf)
|
fbuf = dedent(fbuf)
|
||||||
buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:]
|
buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:]
|
||||||
|
|
@ -694,45 +705,45 @@ class CmdEditorGroup(CmdEditorBase):
|
||||||
elif cmd == ":echo":
|
elif cmd == ":echo":
|
||||||
# set echoing on/off
|
# set echoing on/off
|
||||||
editor._echo_mode = not editor._echo_mode
|
editor._echo_mode = not editor._echo_mode
|
||||||
caller.msg("Echo mode set to %s" % editor._echo_mode)
|
caller.msg(_("Echo mode set to %s") % editor._echo_mode)
|
||||||
elif cmd == ":!":
|
elif cmd == ":!":
|
||||||
if editor._codefunc:
|
if editor._codefunc:
|
||||||
editor._codefunc(caller, editor._buffer)
|
editor._codefunc(caller, editor._buffer)
|
||||||
else:
|
else:
|
||||||
caller.msg("This command is only available in code editor mode.")
|
caller.msg(_("This command is only available in code editor mode."))
|
||||||
elif cmd == ":<":
|
elif cmd == ":<":
|
||||||
# :<
|
# :<
|
||||||
if editor._codefunc:
|
if editor._codefunc:
|
||||||
editor.decrease_indent()
|
editor.decrease_indent()
|
||||||
indent = editor._indent
|
indent = editor._indent
|
||||||
if indent >= 0:
|
if indent >= 0:
|
||||||
caller.msg("Decreased indentation: new indentation is {}.".format(indent))
|
caller.msg(_("Decreased indentation: new indentation is {}.").format(indent))
|
||||||
else:
|
else:
|
||||||
caller.msg("|rManual indentation is OFF.|n Use := to turn it on.")
|
caller.msg(_("|rManual indentation is OFF.|n Use := to turn it on."))
|
||||||
else:
|
else:
|
||||||
caller.msg("This command is only available in code editor mode.")
|
caller.msg(_("This command is only available in code editor mode."))
|
||||||
elif cmd == ":>":
|
elif cmd == ":>":
|
||||||
# :>
|
# :>
|
||||||
if editor._codefunc:
|
if editor._codefunc:
|
||||||
editor.increase_indent()
|
editor.increase_indent()
|
||||||
indent = editor._indent
|
indent = editor._indent
|
||||||
if indent >= 0:
|
if indent >= 0:
|
||||||
caller.msg("Increased indentation: new indentation is {}.".format(indent))
|
caller.msg(_("Increased indentation: new indentation is {}.").format(indent))
|
||||||
else:
|
else:
|
||||||
caller.msg("|rManual indentation is OFF.|n Use := to turn it on.")
|
caller.msg(_("|rManual indentation is OFF.|n Use := to turn it on."))
|
||||||
else:
|
else:
|
||||||
caller.msg("This command is only available in code editor mode.")
|
caller.msg(_("This command is only available in code editor mode."))
|
||||||
elif cmd == ":=":
|
elif cmd == ":=":
|
||||||
# :=
|
# :=
|
||||||
if editor._codefunc:
|
if editor._codefunc:
|
||||||
editor.swap_autoindent()
|
editor.swap_autoindent()
|
||||||
indent = editor._indent
|
indent = editor._indent
|
||||||
if indent >= 0:
|
if indent >= 0:
|
||||||
caller.msg("Auto-indentation turned on.")
|
caller.msg(_("Auto-indentation turned on."))
|
||||||
else:
|
else:
|
||||||
caller.msg("Auto-indentation turned off.")
|
caller.msg(_("Auto-indentation turned off."))
|
||||||
else:
|
else:
|
||||||
caller.msg("This command is only available in code editor mode.")
|
caller.msg(_("This command is only available in code editor mode."))
|
||||||
|
|
||||||
|
|
||||||
class EvEditorCmdSet(CmdSet):
|
class EvEditorCmdSet(CmdSet):
|
||||||
|
|
@ -879,12 +890,13 @@ class EvEditor(object):
|
||||||
def load_buffer(self):
|
def load_buffer(self):
|
||||||
"""
|
"""
|
||||||
Load the buffer using the load function hook.
|
Load the buffer using the load function hook.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self._buffer = self._loadfunc(self._caller)
|
self._buffer = self._loadfunc(self._caller)
|
||||||
if not isinstance(self._buffer, str):
|
if not isinstance(self._buffer, str):
|
||||||
self._buffer = to_str(self._buffer)
|
self._buffer = to_str(self._buffer)
|
||||||
self._caller.msg("|rNote: input buffer was converted to a string.|n")
|
self._caller.msg(_("|rNote: input buffer was converted to a string.|n"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
||||||
|
|
@ -1021,7 +1033,7 @@ class EvEditor(object):
|
||||||
header = (
|
header = (
|
||||||
"|n"
|
"|n"
|
||||||
+ sep * 10
|
+ sep * 10
|
||||||
+ "Line Editor [%s]" % self._key
|
+ _("Line Editor [%s]") % self._key
|
||||||
+ sep * (_DEFAULT_WIDTH - 24 - len(self._key))
|
+ sep * (_DEFAULT_WIDTH - 24 - len(self._key))
|
||||||
)
|
)
|
||||||
footer = (
|
footer = (
|
||||||
|
|
@ -1029,7 +1041,7 @@ class EvEditor(object):
|
||||||
+ sep * 10
|
+ sep * 10
|
||||||
+ "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars)
|
+ "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars)
|
||||||
+ sep * 12
|
+ sep * 12
|
||||||
+ "(:h for help)"
|
+ _("(:h for help)")
|
||||||
+ sep * (_DEFAULT_WIDTH - 54)
|
+ sep * (_DEFAULT_WIDTH - 54)
|
||||||
)
|
)
|
||||||
if linenums:
|
if linenums:
|
||||||
|
|
|
||||||
|
|
@ -415,7 +415,8 @@ class CmdEvMenuNode(Command):
|
||||||
) # don't give the session as a kwarg here, direct to original
|
) # don't give the session as a kwarg here, direct to original
|
||||||
raise EvMenuError(err)
|
raise EvMenuError(err)
|
||||||
# we must do this after the caller with the menu has been correctly identified since it
|
# we must do this after the caller with the menu has been correctly identified since it
|
||||||
# can be either Account, Object or Session (in the latter case this info will be superfluous).
|
# can be either Account, Object or Session (in the latter case this info will be
|
||||||
|
# superfluous).
|
||||||
caller.ndb._evmenu._session = self.session
|
caller.ndb._evmenu._session = self.session
|
||||||
# we have a menu, use it.
|
# we have a menu, use it.
|
||||||
menu.parse_input(self.raw_string)
|
menu.parse_input(self.raw_string)
|
||||||
|
|
@ -619,7 +620,8 @@ class EvMenu:
|
||||||
).intersection(set(kwargs.keys()))
|
).intersection(set(kwargs.keys()))
|
||||||
if reserved_clash:
|
if reserved_clash:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"One or more of the EvMenu `**kwargs` ({list(reserved_clash)}) is reserved by EvMenu for internal use."
|
f"One or more of the EvMenu `**kwargs` ({list(reserved_clash)}) "
|
||||||
|
"is reserved by EvMenu for internal use."
|
||||||
)
|
)
|
||||||
for key, val in kwargs.items():
|
for key, val in kwargs.items():
|
||||||
setattr(self, key, val)
|
setattr(self, key, val)
|
||||||
|
|
@ -1262,7 +1264,7 @@ class EvMenu:
|
||||||
table.extend([" " for i in range(nrows - nlastcol)])
|
table.extend([" " for i in range(nrows - nlastcol)])
|
||||||
|
|
||||||
# build the actual table grid
|
# build the actual table grid
|
||||||
table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)]
|
table = [table[icol * nrows: (icol * nrows) + nrows] for icol in range(0, ncols)]
|
||||||
|
|
||||||
# adjust the width of each column
|
# adjust the width of each column
|
||||||
for icol in range(len(table)):
|
for icol in range(len(table)):
|
||||||
|
|
@ -1349,6 +1351,7 @@ def list_node(option_generator, select=None, pagesize=10):
|
||||||
def _select_parser(caller, raw_string, **kwargs):
|
def _select_parser(caller, raw_string, **kwargs):
|
||||||
"""
|
"""
|
||||||
Parse the select action
|
Parse the select action
|
||||||
|
|
||||||
"""
|
"""
|
||||||
available_choices = kwargs.get("available_choices", [])
|
available_choices = kwargs.get("available_choices", [])
|
||||||
|
|
||||||
|
|
@ -1356,7 +1359,7 @@ def list_node(option_generator, select=None, pagesize=10):
|
||||||
index = int(raw_string.strip()) - 1
|
index = int(raw_string.strip()) - 1
|
||||||
selection = available_choices[index]
|
selection = available_choices[index]
|
||||||
except Exception:
|
except Exception:
|
||||||
caller.msg("|rInvalid choice.|n")
|
caller.msg(_("|rInvalid choice.|n"))
|
||||||
else:
|
else:
|
||||||
if callable(select):
|
if callable(select):
|
||||||
try:
|
try:
|
||||||
|
|
@ -1388,7 +1391,7 @@ def list_node(option_generator, select=None, pagesize=10):
|
||||||
if option_list:
|
if option_list:
|
||||||
nall_options = len(option_list)
|
nall_options = len(option_list)
|
||||||
pages = [
|
pages = [
|
||||||
option_list[ind : ind + pagesize] for ind in range(0, nall_options, pagesize)
|
option_list[ind: ind + pagesize] for ind in range(0, nall_options, pagesize)
|
||||||
]
|
]
|
||||||
npages = len(pages)
|
npages = len(pages)
|
||||||
|
|
||||||
|
|
@ -1413,7 +1416,7 @@ def list_node(option_generator, select=None, pagesize=10):
|
||||||
# allows us to call ourselves over and over, using different kwargs.
|
# allows us to call ourselves over and over, using different kwargs.
|
||||||
options.append(
|
options.append(
|
||||||
{
|
{
|
||||||
"key": ("|Wcurrent|n", "c"),
|
"key": (_("|Wcurrent|n"), "c"),
|
||||||
"desc": "|W({}/{})|n".format(page_index + 1, npages),
|
"desc": "|W({}/{})|n".format(page_index + 1, npages),
|
||||||
"goto": (lambda caller: None, {"optionpage_index": page_index}),
|
"goto": (lambda caller: None, {"optionpage_index": page_index}),
|
||||||
}
|
}
|
||||||
|
|
@ -1421,14 +1424,14 @@ def list_node(option_generator, select=None, pagesize=10):
|
||||||
if page_index > 0:
|
if page_index > 0:
|
||||||
options.append(
|
options.append(
|
||||||
{
|
{
|
||||||
"key": ("|wp|Wrevious page|n", "p"),
|
"key": (_("|wp|Wrevious page|n"), "p"),
|
||||||
"goto": (lambda caller: None, {"optionpage_index": page_index - 1}),
|
"goto": (lambda caller: None, {"optionpage_index": page_index - 1}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if page_index < npages - 1:
|
if page_index < npages - 1:
|
||||||
options.append(
|
options.append(
|
||||||
{
|
{
|
||||||
"key": ("|wn|Wext page|n", "n"),
|
"key": (_("|wn|Wext page|n"), "n"),
|
||||||
"goto": (lambda caller: None, {"optionpage_index": page_index + 1}),
|
"goto": (lambda caller: None, {"optionpage_index": page_index + 1}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -1662,7 +1665,7 @@ class CmdYesNoQuestion(Command):
|
||||||
inp = raw
|
inp = raw
|
||||||
|
|
||||||
if inp in ('a', 'abort') and yes_no_question.allow_abort:
|
if inp in ('a', 'abort') and yes_no_question.allow_abort:
|
||||||
caller.msg("Aborted.")
|
caller.msg(_("Aborted."))
|
||||||
self._clean(caller)
|
self._clean(caller)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -1672,7 +1675,6 @@ class CmdYesNoQuestion(Command):
|
||||||
kwargs = yes_no_question.kwargs
|
kwargs = yes_no_question.kwargs
|
||||||
kwargs['caller_session'] = self.session
|
kwargs['caller_session'] = self.session
|
||||||
|
|
||||||
ok = False
|
|
||||||
if inp in ('yes', 'y'):
|
if inp in ('yes', 'y'):
|
||||||
yes_no_question.yes_callable(caller, *args, **kwargs)
|
yes_no_question.yes_callable(caller, *args, **kwargs)
|
||||||
elif inp in ('no', 'n'):
|
elif inp in ('no', 'n'):
|
||||||
|
|
@ -1684,9 +1686,9 @@ class CmdYesNoQuestion(Command):
|
||||||
|
|
||||||
# cleanup
|
# cleanup
|
||||||
self._clean(caller)
|
self._clean(caller)
|
||||||
except Exception as err:
|
except Exception:
|
||||||
# make sure to clean up cmdset if something goes wrong
|
# make sure to clean up cmdset if something goes wrong
|
||||||
caller.msg("|rError in ask_yes_no. Choice not confirmed (report to admin)|n")
|
caller.msg(_("|rError in ask_yes_no. Choice not confirmed (report to admin)|n"))
|
||||||
logger.log_trace("Error in ask_yes_no")
|
logger.log_trace("Error in ask_yes_no")
|
||||||
self._clean(caller)
|
self._clean(caller)
|
||||||
raise
|
raise
|
||||||
|
|
@ -1938,7 +1940,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None):
|
||||||
"""
|
"""
|
||||||
Validate goto-callable kwarg is on correct form.
|
Validate goto-callable kwarg is on correct form.
|
||||||
"""
|
"""
|
||||||
if not "=" in kwarg:
|
if "=" not in kwarg:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"EvMenu template error: goto-callable '{goto}' has a "
|
f"EvMenu template error: goto-callable '{goto}' has a "
|
||||||
f"non-kwarg argument ({kwarg}). All callables in the "
|
f"non-kwarg argument ({kwarg}). All callables in the "
|
||||||
|
|
@ -1955,6 +1957,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None):
|
||||||
def _parse_options(nodename, optiontxt, goto_callables):
|
def _parse_options(nodename, optiontxt, goto_callables):
|
||||||
"""
|
"""
|
||||||
Parse option section into option dict.
|
Parse option section into option dict.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
options = []
|
options = []
|
||||||
optiontxt = optiontxt[0].strip() if optiontxt else ""
|
optiontxt = optiontxt[0].strip() if optiontxt else ""
|
||||||
|
|
@ -2032,6 +2035,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None):
|
||||||
def _parse(caller, menu_template, goto_callables):
|
def _parse(caller, menu_template, goto_callables):
|
||||||
"""
|
"""
|
||||||
Parse the menu string format into a node tree.
|
Parse the menu string format into a node tree.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
nodetree = {}
|
nodetree = {}
|
||||||
splits = _RE_NODE.split(menu_template)
|
splits = _RE_NODE.split(menu_template)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ from evennia import Command, CmdSet
|
||||||
from evennia.commands import cmdhandler
|
from evennia.commands import cmdhandler
|
||||||
from evennia.utils.ansi import ANSIString
|
from evennia.utils.ansi import ANSIString
|
||||||
from evennia.utils.utils import make_iter, inherits_from, justify, dedent
|
from evennia.utils.utils import make_iter, inherits_from, justify, dedent
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
_CMD_NOMATCH = cmdhandler.CMD_NOMATCH
|
_CMD_NOMATCH = cmdhandler.CMD_NOMATCH
|
||||||
_CMD_NOINPUT = cmdhandler.CMD_NOINPUT
|
_CMD_NOINPUT = cmdhandler.CMD_NOINPUT
|
||||||
|
|
@ -231,7 +232,7 @@ class EvMore(object):
|
||||||
self._justify_kwargs = justify_kwargs
|
self._justify_kwargs = justify_kwargs
|
||||||
self.exit_on_lastpage = exit_on_lastpage
|
self.exit_on_lastpage = exit_on_lastpage
|
||||||
self.exit_cmd = exit_cmd
|
self.exit_cmd = exit_cmd
|
||||||
self._exit_msg = "Exited |wmore|n pager."
|
self._exit_msg = _("Exited |wmore|n pager.")
|
||||||
self._kwargs = kwargs
|
self._kwargs = kwargs
|
||||||
|
|
||||||
self._data = None
|
self._data = None
|
||||||
|
|
@ -354,8 +355,9 @@ class EvMore(object):
|
||||||
"""
|
"""
|
||||||
Paginate by slice. This is done with an eye on memory efficiency (usually for
|
Paginate by slice. This is done with an eye on memory efficiency (usually for
|
||||||
querysets); to avoid fetching all objects at the same time.
|
querysets); to avoid fetching all objects at the same time.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._data[pageno * self.height : pageno * self.height + self.height]
|
return self._data[pageno * self.height: pageno * self.height + self.height]
|
||||||
|
|
||||||
def paginator_django(self, pageno):
|
def paginator_django(self, pageno):
|
||||||
"""
|
"""
|
||||||
|
|
@ -433,7 +435,7 @@ class EvMore(object):
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
|
|
||||||
self._data = [
|
self._data = [
|
||||||
_LBR.join(lines[i : i + self.height]) for i in range(0, len(lines), self.height)
|
_LBR.join(lines[i: i + self.height]) for i in range(0, len(lines), self.height)
|
||||||
]
|
]
|
||||||
self._npages = len(self._data)
|
self._npages = len(self._data)
|
||||||
|
|
||||||
|
|
@ -451,13 +453,15 @@ class EvMore(object):
|
||||||
Notes:
|
Notes:
|
||||||
If overridden, this method must perform the following actions:
|
If overridden, this method must perform the following actions:
|
||||||
|
|
||||||
- read and re-store `self._data` (the incoming data set) if needed for pagination to work.
|
- read and re-store `self._data` (the incoming data set) if needed for pagination to
|
||||||
|
work.
|
||||||
- set `self._npages` to the total number of pages. Default is 1.
|
- set `self._npages` to the total number of pages. Default is 1.
|
||||||
- set `self._paginator` to a callable that will take a page number 1...N and return
|
- set `self._paginator` to a callable that will take a page number 1...N and return
|
||||||
the data to display on that page (not any decorations or next/prev buttons). If only
|
the data to display on that page (not any decorations or next/prev buttons). If only
|
||||||
wanting to change the paginator, override `self.paginator` instead.
|
wanting to change the paginator, override `self.paginator` instead.
|
||||||
- set `self._page_formatter` to a callable that will receive the page from `self._paginator`
|
- set `self._page_formatter` to a callable that will receive the page from
|
||||||
and format it with one element per line. Default is `str`. Or override `self.page_formatter`
|
`self._paginator` and format it with one element per line. Default is `str`. Or
|
||||||
|
override `self.page_formatter`
|
||||||
directly instead.
|
directly instead.
|
||||||
|
|
||||||
By default, helper methods are called that perform these actions
|
By default, helper methods are called that perform these actions
|
||||||
|
|
|
||||||
|
|
@ -239,12 +239,12 @@ class ANSITextWrapper(TextWrapper):
|
||||||
del chunks[-1]
|
del chunks[-1]
|
||||||
|
|
||||||
while chunks:
|
while chunks:
|
||||||
l = d_len(chunks[-1])
|
ln = d_len(chunks[-1])
|
||||||
|
|
||||||
# Can at least squeeze this chunk onto the current line.
|
# Can at least squeeze this chunk onto the current line.
|
||||||
if cur_len + l <= width:
|
if cur_len + ln <= width:
|
||||||
cur_line.append(chunks.pop())
|
cur_line.append(chunks.pop())
|
||||||
cur_len += l
|
cur_len += ln
|
||||||
|
|
||||||
# Nope, this line is full.
|
# Nope, this line is full.
|
||||||
else:
|
else:
|
||||||
|
|
@ -262,10 +262,10 @@ class ANSITextWrapper(TextWrapper):
|
||||||
# Convert current line back to a string and store it in list
|
# Convert current line back to a string and store it in list
|
||||||
# of all lines (return value).
|
# of all lines (return value).
|
||||||
if cur_line:
|
if cur_line:
|
||||||
l = ""
|
ln = ""
|
||||||
for w in cur_line: # ANSI fix
|
for w in cur_line: # ANSI fix
|
||||||
l += w #
|
ln += w #
|
||||||
lines.append(indent + l)
|
lines.append(indent + ln)
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1099,8 +1099,9 @@ class EvTable(object):
|
||||||
height (int, optional): Fixed height of table. Defaults to being unset. Width is
|
height (int, optional): Fixed height of table. Defaults to being unset. Width is
|
||||||
still given precedence. If given, table cells will crop text rather
|
still given precedence. If given, table cells will crop text rather
|
||||||
than expand vertically.
|
than expand vertically.
|
||||||
evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as even width as
|
evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as
|
||||||
possible. This often looks best also for mixed-length tables. Default is `False`.
|
even width as possible. This often looks best also for mixed-length tables. Default
|
||||||
|
is `False`.
|
||||||
maxwidth (int, optional): This will set a maximum width
|
maxwidth (int, optional): This will set a maximum width
|
||||||
of the table while allowing it to be smaller. Only if it grows wider than this
|
of the table while allowing it to be smaller. Only if it grows wider than this
|
||||||
size will it be resized by expanding horizontally (or crop `height` is given).
|
size will it be resized by expanding horizontally (or crop `height` is given).
|
||||||
|
|
@ -1347,7 +1348,8 @@ class EvTable(object):
|
||||||
self.ncols = ncols
|
self.ncols = ncols
|
||||||
self.nrows = nrowmax
|
self.nrows = nrowmax
|
||||||
|
|
||||||
# add borders - these add to the width/height, so we must do this before calculating width/height
|
# add borders - these add to the width/height, so we must do this before calculating
|
||||||
|
# width/height
|
||||||
self._borders()
|
self._borders()
|
||||||
|
|
||||||
# equalize widths within each column
|
# equalize widths within each column
|
||||||
|
|
@ -1434,7 +1436,8 @@ class EvTable(object):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# equalize heights for each row (we must do this here, since it may have changed to fit new widths)
|
# equalize heights for each row (we must do this here, since it may have changed to fit new
|
||||||
|
# widths)
|
||||||
cheights = [
|
cheights = [
|
||||||
max(cell.get_height() for cell in (col[iy] for col in self.worktable))
|
max(cell.get_height() for cell in (col[iy] for col in self.worktable))
|
||||||
for iy in range(nrowmax)
|
for iy in range(nrowmax)
|
||||||
|
|
|
||||||
|
|
@ -43,11 +43,9 @@ The `FuncParser` also accepts a direct dict mapping of `{'name': callable, ...}`
|
||||||
---
|
---
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import re
|
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import inspect
|
import inspect
|
||||||
import random
|
import random
|
||||||
from functools import partial
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import (
|
||||||
|
|
@ -234,8 +232,6 @@ class FuncParser:
|
||||||
f"(available: {available})")
|
f"(available: {available})")
|
||||||
return str(parsedfunc)
|
return str(parsedfunc)
|
||||||
|
|
||||||
nargs = len(args)
|
|
||||||
|
|
||||||
# build kwargs in the proper priority order
|
# build kwargs in the proper priority order
|
||||||
kwargs = {**self.default_kwargs, **kwargs, **reserved_kwargs,
|
kwargs = {**self.default_kwargs, **kwargs, **reserved_kwargs,
|
||||||
**{'funcparser': self, "raise_errors": raise_errors}}
|
**{'funcparser': self, "raise_errors": raise_errors}}
|
||||||
|
|
@ -606,7 +602,7 @@ def funcparser_callable_eval(*args, **kwargs):
|
||||||
- `$py(3 + 4) -> 7`
|
- `$py(3 + 4) -> 7`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
args, kwargs = safe_convert_to_types(("py", {}) , *args, **kwargs)
|
args, kwargs = safe_convert_to_types(("py", {}), *args, **kwargs)
|
||||||
return args[0] if args else ''
|
return args[0] if args else ''
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -694,7 +690,7 @@ def funcparser_callable_round(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
if not args:
|
if not args:
|
||||||
return ''
|
return ''
|
||||||
args, _ = safe_convert_to_types(((float, int), {}) *args, **kwargs)
|
args, _ = safe_convert_to_types(((float, int), {}), *args, **kwargs)
|
||||||
|
|
||||||
num, *significant = args
|
num, *significant = args
|
||||||
significant = significant[0] if significant else 0
|
significant = significant[0] if significant else 0
|
||||||
|
|
@ -1032,7 +1028,8 @@ def funcparser_callable_search_list(*args, caller=None, access="control", **kwar
|
||||||
return_list=True, **kwargs)
|
return_list=True, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, capitalize=False, **kwargs):
|
def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, capitalize=False,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Usage: $you() or $you(key)
|
Usage: $you() or $you(key)
|
||||||
|
|
||||||
|
|
@ -1081,10 +1078,12 @@ def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, cap
|
||||||
capitalize = bool(capitalize)
|
capitalize = bool(capitalize)
|
||||||
if caller == receiver:
|
if caller == receiver:
|
||||||
return "You" if capitalize else "you"
|
return "You" if capitalize else "you"
|
||||||
return caller.get_display_name(looker=receiver) if hasattr(caller, "get_display_name") else str(caller)
|
return (caller.get_display_name(looker=receiver)
|
||||||
|
if hasattr(caller, "get_display_name") else str(caller))
|
||||||
|
|
||||||
|
|
||||||
def funcparser_callable_You(*args, you=None, receiver=None, mapping=None, capitalize=True, **kwargs):
|
def funcparser_callable_You(*args, you=None, receiver=None, mapping=None, capitalize=True,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Usage: $You() - capitalizes the 'you' output.
|
Usage: $You() - capitalizes the 'you' output.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ total runtime of the server and the current uptime.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from calendar import monthrange
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
from django.db.utils import OperationalError
|
from django.db.utils import OperationalError
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ class SharedMemoryModelBase(ModelBase):
|
||||||
return super(SharedMemoryModelBase, cls).__call__(*args, **kwargs)
|
return super(SharedMemoryModelBase, cls).__call__(*args, **kwargs)
|
||||||
|
|
||||||
instance_key = cls._get_cache_key(args, kwargs)
|
instance_key = cls._get_cache_key(args, kwargs)
|
||||||
# depending on the arguments, we might not be able to infer the PK, so in that case we create a new instance
|
# depending on the arguments, we might not be able to infer the PK, so in that case we
|
||||||
|
# create a new instance
|
||||||
if instance_key is None:
|
if instance_key is None:
|
||||||
return new_instance()
|
return new_instance()
|
||||||
cached_instance = cls.get_cached_instance(instance_key)
|
cached_instance = cls.get_cached_instance(instance_key)
|
||||||
|
|
@ -154,9 +155,9 @@ class SharedMemoryModelBase(ModelBase):
|
||||||
if isinstance(value, (str, int)):
|
if isinstance(value, (str, int)):
|
||||||
value = to_str(value)
|
value = to_str(value)
|
||||||
if value.isdigit() or value.startswith("#"):
|
if value.isdigit() or value.startswith("#"):
|
||||||
# we also allow setting using dbrefs, if so we try to load the matching object.
|
# we also allow setting using dbrefs, if so we try to load the matching
|
||||||
# (we assume the object is of the same type as the class holding the field, if
|
# object. (we assume the object is of the same type as the class holding
|
||||||
# not a custom handler must be used for that field)
|
# the field, if not a custom handler must be used for that field)
|
||||||
dbid = dbref(value, reqhash=False)
|
dbid = dbref(value, reqhash=False)
|
||||||
if dbid:
|
if dbid:
|
||||||
model = _GA(cls, "_meta").get_field(fname).model
|
model = _GA(cls, "_meta").get_field(fname).model
|
||||||
|
|
@ -266,21 +267,24 @@ class SharedMemoryModel(Model, metaclass=SharedMemoryModelBase):
|
||||||
pk = cls._meta.pks[0]
|
pk = cls._meta.pks[0]
|
||||||
else:
|
else:
|
||||||
pk = cls._meta.pk
|
pk = cls._meta.pk
|
||||||
# get the index of the pk in the class fields. this should be calculated *once*, but isn't atm
|
# get the index of the pk in the class fields. this should be calculated *once*, but isn't
|
||||||
|
# atm
|
||||||
pk_position = cls._meta.fields.index(pk)
|
pk_position = cls._meta.fields.index(pk)
|
||||||
if len(args) > pk_position:
|
if len(args) > pk_position:
|
||||||
# if it's in the args, we can get it easily by index
|
# if it's in the args, we can get it easily by index
|
||||||
result = args[pk_position]
|
result = args[pk_position]
|
||||||
elif pk.attname in kwargs:
|
elif pk.attname in kwargs:
|
||||||
# retrieve the pk value. Note that we use attname instead of name, to handle the case where the pk is a
|
# retrieve the pk value. Note that we use attname instead of name, to handle the case
|
||||||
# a ForeignKey.
|
# where the pk is a a ForeignKey.
|
||||||
result = kwargs[pk.attname]
|
result = kwargs[pk.attname]
|
||||||
elif pk.name != pk.attname and pk.name in kwargs:
|
elif pk.name != pk.attname and pk.name in kwargs:
|
||||||
# ok we couldn't find the value, but maybe it's a FK and we can find the corresponding object instead
|
# ok we couldn't find the value, but maybe it's a FK and we can find the corresponding
|
||||||
|
# object instead
|
||||||
result = kwargs[pk.name]
|
result = kwargs[pk.name]
|
||||||
|
|
||||||
if result is not None and isinstance(result, Model):
|
if result is not None and isinstance(result, Model):
|
||||||
# if the pk value happens to be a model instance (which can happen wich a FK), we'd rather use its own pk as the key
|
# if the pk value happens to be a model instance (which can happen wich a FK), we'd
|
||||||
|
# rather use its own pk as the key
|
||||||
result = result._get_pk_val()
|
result = result._get_pk_val()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ log_typemsg(). This is for historical, back-compatible reasons.
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import glob
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from twisted.python import log, logfile
|
from twisted.python import log, logfile
|
||||||
|
|
@ -47,6 +46,7 @@ def timeformat(when=None):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
timestring (str): A formatted string of the given time.
|
timestring (str): A formatted string of the given time.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
when = when if when else time.time()
|
when = when if when else time.time()
|
||||||
|
|
||||||
|
|
@ -126,6 +126,7 @@ class WeeklyLogFile(logfile.DailyLogFile):
|
||||||
server.log.2020_01_29
|
server.log.2020_01_29
|
||||||
server.log.2020_01_29__1
|
server.log.2020_01_29__1
|
||||||
server.log.2020_01_29__2
|
server.log.2020_01_29__2
|
||||||
|
|
||||||
"""
|
"""
|
||||||
suffix = ""
|
suffix = ""
|
||||||
copy_suffix = 0
|
copy_suffix = 0
|
||||||
|
|
@ -146,7 +147,10 @@ class WeeklyLogFile(logfile.DailyLogFile):
|
||||||
return suffix
|
return suffix
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
"Write data to log file"
|
"""
|
||||||
|
Write data to log file
|
||||||
|
|
||||||
|
"""
|
||||||
logfile.BaseLogFile.write(self, data)
|
logfile.BaseLogFile.write(self, data)
|
||||||
self.lastDate = max(self.lastDate, self.toDate())
|
self.lastDate = max(self.lastDate, self.toDate())
|
||||||
self.size += len(data)
|
self.size += len(data)
|
||||||
|
|
@ -155,6 +159,7 @@ class WeeklyLogFile(logfile.DailyLogFile):
|
||||||
class PortalLogObserver(log.FileLogObserver):
|
class PortalLogObserver(log.FileLogObserver):
|
||||||
"""
|
"""
|
||||||
Reformat logging
|
Reformat logging
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
timeFormat = None
|
timeFormat = None
|
||||||
|
|
@ -289,6 +294,7 @@ def log_info(infomsg):
|
||||||
Prints any generic debugging/informative info that should appear in the log.
|
Prints any generic debugging/informative info that should appear in the log.
|
||||||
|
|
||||||
infomsg: (string) The message to be logged.
|
infomsg: (string) The message to be logged.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
infomsg = str(infomsg)
|
infomsg = str(infomsg)
|
||||||
|
|
@ -307,6 +313,7 @@ def log_dep(depmsg):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
depmsg (str): The deprecation message to log.
|
depmsg (str): The deprecation message to log.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
depmsg = str(depmsg)
|
depmsg = str(depmsg)
|
||||||
|
|
@ -325,6 +332,7 @@ def log_sec(secmsg):
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
secmsg (str): The security message to log.
|
secmsg (str): The security message to log.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
secmsg = str(secmsg)
|
secmsg = str(secmsg)
|
||||||
|
|
@ -346,6 +354,7 @@ class EvenniaLogFile(logfile.LogFile):
|
||||||
the LogFile's rotate method in order to append some of the last
|
the LogFile's rotate method in order to append some of the last
|
||||||
lines of the previous log to the start of the new log, in order
|
lines of the previous log to the start of the new log, in order
|
||||||
to preserve a continuous chat history for channel log files.
|
to preserve a continuous chat history for channel log files.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# we delay import of settings to keep logger module as free
|
# we delay import of settings to keep logger module as free
|
||||||
|
|
@ -361,6 +370,7 @@ class EvenniaLogFile(logfile.LogFile):
|
||||||
"""
|
"""
|
||||||
Rotates our log file and appends some number of lines from
|
Rotates our log file and appends some number of lines from
|
||||||
the previous log to the start of the new one.
|
the previous log to the start of the new one.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
append_tail = (num_lines_to_append
|
append_tail = (num_lines_to_append
|
||||||
if num_lines_to_append is not None
|
if num_lines_to_append is not None
|
||||||
|
|
@ -377,9 +387,11 @@ class EvenniaLogFile(logfile.LogFile):
|
||||||
"""
|
"""
|
||||||
Convenience method for accessing our _file attribute's seek method,
|
Convenience method for accessing our _file attribute's seek method,
|
||||||
which is used in tail_log_function.
|
which is used in tail_log_function.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*args: Same args as file.seek
|
*args: Same args as file.seek
|
||||||
**kwargs: Same kwargs as file.seek
|
**kwargs: Same kwargs as file.seek
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return self._file.seek(*args, **kwargs)
|
return self._file.seek(*args, **kwargs)
|
||||||
|
|
||||||
|
|
@ -387,12 +399,14 @@ class EvenniaLogFile(logfile.LogFile):
|
||||||
"""
|
"""
|
||||||
Convenience method for accessing our _file attribute's readlines method,
|
Convenience method for accessing our _file attribute's readlines method,
|
||||||
which is used in tail_log_function.
|
which is used in tail_log_function.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*args: same args as file.readlines
|
*args: same args as file.readlines
|
||||||
**kwargs: same kwargs as file.readlines
|
**kwargs: same kwargs as file.readlines
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
lines (list): lines from our _file attribute.
|
lines (list): lines from our _file attribute.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return [line.decode("utf-8") for line in self._file.readlines(*args, **kwargs)]
|
return [line.decode("utf-8") for line in self._file.readlines(*args, **kwargs)]
|
||||||
|
|
||||||
|
|
@ -550,7 +564,7 @@ def tail_log_file(filename, offset, nlines, callback=None):
|
||||||
lines_found = filehandle.readlines()
|
lines_found = filehandle.readlines()
|
||||||
block_count -= 1
|
block_count -= 1
|
||||||
# return the right number of lines
|
# return the right number of lines
|
||||||
lines_found = lines_found[-nlines - offset : -offset if offset else None]
|
lines_found = lines_found[-nlines - offset: -offset if offset else None]
|
||||||
if callback:
|
if callback:
|
||||||
callback(lines_found)
|
callback(lines_found)
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from evennia.utils.utils import string_partial_matching
|
from evennia.utils.utils import string_partial_matching
|
||||||
from evennia.utils.containers import OPTION_CLASSES
|
from evennia.utils.containers import OPTION_CLASSES
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
|
|
@ -134,7 +135,7 @@ class OptionHandler:
|
||||||
"""
|
"""
|
||||||
if key not in self.options_dict:
|
if key not in self.options_dict:
|
||||||
if raise_error:
|
if raise_error:
|
||||||
raise KeyError("Option not found!")
|
raise KeyError(_("Option not found!"))
|
||||||
return default
|
return default
|
||||||
# get the options or load/recache it
|
# get the options or load/recache it
|
||||||
op_found = self.options.get(key) or self._load_option(key)
|
op_found = self.options.get(key) or self._load_option(key)
|
||||||
|
|
@ -155,12 +156,14 @@ class OptionHandler:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not key:
|
if not key:
|
||||||
raise ValueError("Option field blank!")
|
raise ValueError(_("Option field blank!"))
|
||||||
match = string_partial_matching(list(self.options_dict.keys()), key, ret_index=False)
|
match = string_partial_matching(list(self.options_dict.keys()), key, ret_index=False)
|
||||||
if not match:
|
if not match:
|
||||||
raise ValueError("Option not found!")
|
raise ValueError(_("Option not found!"))
|
||||||
if len(match) > 1:
|
if len(match) > 1:
|
||||||
raise ValueError(f"Multiple matches: {', '.join(match)}. Please be more specific.")
|
raise ValueError(_("Multiple matches:")
|
||||||
|
+ f"{', '.join(match)}. "
|
||||||
|
+ _("Please be more specific."))
|
||||||
match = match[0]
|
match = match[0]
|
||||||
op = self.get(match, return_obj=True)
|
op = self.get(match, return_obj=True)
|
||||||
op.set(value, **kwargs)
|
op.set(value, **kwargs)
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,7 @@ except OperationalError:
|
||||||
from evennia.scripts.models import ScriptDB
|
from evennia.scripts.models import ScriptDB
|
||||||
from evennia.comms.models import Msg, ChannelDB
|
from evennia.comms.models import Msg, ChannelDB
|
||||||
from evennia.help.models import HelpEntry
|
from evennia.help.models import HelpEntry
|
||||||
from evennia.typeclasses.tags import Tag
|
from evennia.typeclasses.tags import Tag # noqa
|
||||||
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
# Search manager-wrappers
|
# Search manager-wrappers
|
||||||
|
|
@ -243,7 +242,7 @@ def search_script_attribute(
|
||||||
def search_channel_attribute(
|
def search_channel_attribute(
|
||||||
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
key=None, category=None, value=None, strvalue=None, attrtype=None, **kwargs
|
||||||
):
|
):
|
||||||
return Channel.objects.get_by_attribute(
|
return ChannelDB.objects.get_by_attribute(
|
||||||
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
key=key, category=category, value=value, strvalue=strvalue, attrtype=attrtype, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ be of use when designing your own game.
|
||||||
import os
|
import os
|
||||||
import gc
|
import gc
|
||||||
import sys
|
import sys
|
||||||
import copy
|
|
||||||
import types
|
import types
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
|
|
@ -35,6 +34,7 @@ from django.utils.translation import gettext as _
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.validators import validate_email as django_validate_email
|
from django.core.validators import validate_email as django_validate_email
|
||||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||||
|
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
||||||
_MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE
|
_MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE
|
||||||
|
|
@ -204,7 +204,7 @@ def dedent(text, baseline_index=None, indent=None):
|
||||||
baseline = lines[baseline_index]
|
baseline = lines[baseline_index]
|
||||||
spaceremove = len(baseline) - len(baseline.lstrip(" "))
|
spaceremove = len(baseline) - len(baseline.lstrip(" "))
|
||||||
return "\n".join(
|
return "\n".join(
|
||||||
line[min(spaceremove, len(line) - len(line.lstrip(" "))) :] for line in lines
|
line[min(spaceremove, len(line) - len(line.lstrip(" "))):] for line in lines
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -343,7 +343,7 @@ def columnize(string, columns=2, spacing=4, align="l", width=None):
|
||||||
cols = []
|
cols = []
|
||||||
istart = 0
|
istart = 0
|
||||||
for irows in nrows:
|
for irows in nrows:
|
||||||
cols.append(onecol[istart : istart + irows])
|
cols.append(onecol[istart: istart + irows])
|
||||||
istart = istart + irows
|
istart = istart + irows
|
||||||
for col in cols:
|
for col in cols:
|
||||||
if len(col) < height:
|
if len(col) < height:
|
||||||
|
|
@ -1029,8 +1029,6 @@ def uses_database(name="sqlite3"):
|
||||||
return engine == "django.db.backends.%s" % name
|
return engine == "django.db.backends.%s" % name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def delay(timedelay, callback, *args, **kwargs):
|
def delay(timedelay, callback, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Delay the calling of a callback (function).
|
Delay the calling of a callback (function).
|
||||||
|
|
@ -1238,8 +1236,8 @@ def check_evennia_dependencies():
|
||||||
except ImportError:
|
except ImportError:
|
||||||
errstring += (
|
errstring += (
|
||||||
"\n ERROR: IRC is enabled, but twisted.words is not installed. Please install it."
|
"\n ERROR: IRC is enabled, but twisted.words is not installed. Please install it."
|
||||||
"\n Linux Debian/Ubuntu users should install package 'python-twisted-words', others"
|
"\n Linux Debian/Ubuntu users should install package 'python-twisted-words', "
|
||||||
"\n can get it from http://twistedmatrix.com/trac/wiki/TwistedWords."
|
"\n others can get it from http://twistedmatrix.com/trac/wiki/TwistedWords."
|
||||||
)
|
)
|
||||||
not_error = False
|
not_error = False
|
||||||
errstring = errstring.strip()
|
errstring = errstring.strip()
|
||||||
|
|
@ -1911,7 +1909,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
|
||||||
|
|
||||||
wl = wls[ie]
|
wl = wls[ie]
|
||||||
lrow = len(row)
|
lrow = len(row)
|
||||||
debug = row.replace(" ", ".")
|
# debug = row.replace(" ", ".")
|
||||||
|
|
||||||
if lrow + wl > width:
|
if lrow + wl > width:
|
||||||
# this slot extends outside grid, move to next line
|
# this slot extends outside grid, move to next line
|
||||||
|
|
@ -1970,8 +1968,6 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
|
||||||
return _weighted_rows(elements)
|
return _weighted_rows(elements)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_evennia_pids():
|
def get_evennia_pids():
|
||||||
"""
|
"""
|
||||||
Get the currently valid PIDs (Process IDs) of the Portal and
|
Get the currently valid PIDs (Process IDs) of the Portal and
|
||||||
|
|
@ -2323,7 +2319,7 @@ def get_game_dir_path():
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# current working directory, assumed to be somewhere inside gamedir.
|
# current working directory, assumed to be somewhere inside gamedir.
|
||||||
for _ in range(10):
|
for inum in range(10):
|
||||||
gpath = os.getcwd()
|
gpath = os.getcwd()
|
||||||
if "server" in os.listdir(gpath):
|
if "server" in os.listdir(gpath):
|
||||||
if os.path.isfile(os.path.join("server", "conf", "settings.py")):
|
if os.path.isfile(os.path.join("server", "conf", "settings.py")):
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,20 @@ def text(entry, option_key="Text", **kwargs):
|
||||||
try:
|
try:
|
||||||
return str(entry)
|
return str(entry)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise ValueError(f"Input could not be converted to text ({err})")
|
raise ValueError(_("Input could not be converted to text ({err})").format(err=err))
|
||||||
|
|
||||||
|
|
||||||
def color(entry, option_key="Color", **kwargs):
|
def color(entry, option_key="Color", **kwargs):
|
||||||
"""
|
"""
|
||||||
The color should be just a color character, so 'r' if red color is desired.
|
The color should be just a color character, so 'r' if red color is desired.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError(f"Nothing entered for a {option_key}!")
|
raise ValueError(_("Nothing entered for a {option_key}!").format(option_key=option_key))
|
||||||
test_str = strip_ansi(f"|{entry}|n")
|
test_str = strip_ansi(f"|{entry}|n")
|
||||||
if test_str:
|
if test_str:
|
||||||
raise ValueError(f"'{entry}' is not a valid {option_key}.")
|
raise ValueError(_("'{entry}' is not a valid {option_key}.").format(
|
||||||
|
entry=entry, option_key=option_key))
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -83,13 +85,17 @@ def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs)
|
||||||
entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {split_time[3]}"
|
entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {split_time[3]}"
|
||||||
else:
|
else:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}"
|
_("{option_key} must be entered in a 24-hour format such as: {timeformat}").format(
|
||||||
|
option_key=option_key,
|
||||||
|
timeformat=now.strftime('%b %d %H:%M'))
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
local = _dt.datetime.strptime(entry, "%b %d %H:%M %Y")
|
local = _dt.datetime.strptime(entry, "%b %d %H:%M %Y")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}"
|
_("{option_key} must be entered in a 24-hour format such as: {timeformat}").format(
|
||||||
|
option_key=option_key,
|
||||||
|
timeformat=now.strftime('%b %d %H:%M'))
|
||||||
)
|
)
|
||||||
local_tz = from_tz.localize(local)
|
local_tz = from_tz.localize(local)
|
||||||
return local_tz.astimezone(utc)
|
return local_tz.astimezone(utc)
|
||||||
|
|
@ -100,8 +106,9 @@ def duration(entry, option_key="Duration", **kwargs):
|
||||||
Take a string and derive a datetime timedelta from it.
|
Take a string and derive a datetime timedelta from it.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entry (string): This is a string from user-input. The intended format is, for example: "5d 2w 90s" for
|
entry (string): This is a string from user-input. The intended format is, for example:
|
||||||
'five days, two weeks, and ninety seconds.' Invalid sections are ignored.
|
"5d 2w 90s" for 'five days, two weeks, and ninety seconds.' Invalid sections are
|
||||||
|
ignored.
|
||||||
option_key (str): Name to display this query as.
|
option_key (str): Name to display this query as.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -129,7 +136,10 @@ def duration(entry, option_key="Duration", **kwargs):
|
||||||
elif _re.match(r"^[\d]+y$", interval):
|
elif _re.match(r"^[\d]+y$", interval):
|
||||||
days += int(interval.rstrip("y")) * 365
|
days += int(interval.rstrip("y")) * 365
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Could not convert section '{interval}' to a {option_key}.")
|
raise ValueError(
|
||||||
|
_("Could not convert section '{interval}' to a {option_key}.").format(
|
||||||
|
interval=interval, option_key=option_key)
|
||||||
|
)
|
||||||
|
|
||||||
return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)
|
return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)
|
||||||
|
|
||||||
|
|
@ -137,45 +147,56 @@ def duration(entry, option_key="Duration", **kwargs):
|
||||||
def future(entry, option_key="Future Datetime", from_tz=None, **kwargs):
|
def future(entry, option_key="Future Datetime", from_tz=None, **kwargs):
|
||||||
time = datetime(entry, option_key, from_tz=from_tz)
|
time = datetime(entry, option_key, from_tz=from_tz)
|
||||||
if time < _dt.datetime.utcnow().replace(tzinfo=_dt.timezone.utc):
|
if time < _dt.datetime.utcnow().replace(tzinfo=_dt.timezone.utc):
|
||||||
raise ValueError(f"That {option_key} is in the past! Must give a Future datetime!")
|
raise ValueError(_("That {option_key} is in the past! Must give a Future datetime!").format(
|
||||||
|
option_key=option_key))
|
||||||
return time
|
return time
|
||||||
|
|
||||||
|
|
||||||
def signed_integer(entry, option_key="Signed Integer", **kwargs):
|
def signed_integer(entry, option_key="Signed Integer", **kwargs):
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError(f"Must enter a whole number for {option_key}!")
|
raise ValueError(_("Must enter a whole number for {option_key}!").format(
|
||||||
|
option_key=option_key))
|
||||||
try:
|
try:
|
||||||
num = int(entry)
|
num = int(entry)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError(f"Could not convert '{entry}' to a whole number for {option_key}!")
|
raise ValueError(_("Could not convert '{entry}' to a whole "
|
||||||
|
"number for {option_key}!").format(
|
||||||
|
entry=entry, option_key=option_key))
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
def positive_integer(entry, option_key="Positive Integer", **kwargs):
|
def positive_integer(entry, option_key="Positive Integer", **kwargs):
|
||||||
num = signed_integer(entry, option_key)
|
num = signed_integer(entry, option_key)
|
||||||
if not num >= 1:
|
if not num >= 1:
|
||||||
raise ValueError(f"Must enter a whole number greater than 0 for {option_key}!")
|
raise ValueError(_("Must enter a whole number greater than 0 for {option_key}!").format(
|
||||||
|
option_key=option_key))
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
def unsigned_integer(entry, option_key="Unsigned Integer", **kwargs):
|
def unsigned_integer(entry, option_key="Unsigned Integer", **kwargs):
|
||||||
num = signed_integer(entry, option_key)
|
num = signed_integer(entry, option_key)
|
||||||
if not num >= 0:
|
if not num >= 0:
|
||||||
raise ValueError(f"{option_key} must be a whole number greater than or equal to 0!")
|
raise ValueError(_("{option_key} must be a whole number greater than "
|
||||||
|
"or equal to 0!").format(
|
||||||
|
option_key=option_key))
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
def boolean(entry, option_key="True/False", **kwargs):
|
def boolean(entry, option_key="True/False", **kwargs):
|
||||||
"""
|
"""
|
||||||
Simplest check in computer logic, right? This will take user input to flick the switch on or off
|
Simplest check in computer logic, right? This will take user input to flick the switch on or off
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entry (str): A value such as True, On, Enabled, Disabled, False, 0, or 1.
|
entry (str): A value such as True, On, Enabled, Disabled, False, 0, or 1.
|
||||||
option_key (str): What kind of Boolean we are setting. What Option is this for?
|
option_key (str): What kind of Boolean we are setting. What Option is this for?
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Boolean
|
Boolean
|
||||||
|
|
||||||
"""
|
"""
|
||||||
error = f"Must enter 0 (false) or 1 (true) for {option_key}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled"
|
error = (_("Must enter a true/false input for {option_key}. Accepts {alternatives}.").format(
|
||||||
|
option_key=option_key,
|
||||||
|
alternatives="0/1, True/False, On/Off, Yes/No, Enabled/Disabled"))
|
||||||
if not isinstance(entry, str):
|
if not isinstance(entry, str):
|
||||||
raise ValueError(error)
|
raise ValueError(error)
|
||||||
entry = entry.upper()
|
entry = entry.upper()
|
||||||
|
|
@ -196,39 +217,42 @@ def timezone(entry, option_key="Timezone", **kwargs):
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A PYTZ timezone.
|
A PYTZ timezone.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError(f"No {option_key} entered!")
|
raise ValueError(_("No {option_key} entered!").format(option_key=option_key))
|
||||||
found = _partial(list(_TZ_DICT.keys()), entry, ret_index=False)
|
found = _partial(list(_TZ_DICT.keys()), entry, ret_index=False)
|
||||||
if len(found) > 1:
|
if len(found) > 1:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"That matched: {', '.join(str(t) for t in found)}. Please be more specific!"
|
_("That matched: {matches}. Please be more specific!").format(
|
||||||
)
|
matches=', '.join(str(t) for t in found)))
|
||||||
if found:
|
if found:
|
||||||
return _TZ_DICT[found[0]]
|
return _TZ_DICT[found[0]]
|
||||||
raise ValueError(f"Could not find timezone '{entry}' for {option_key}!")
|
raise ValueError(_("Could not find timezone '{entry}' for {option_key}!").format(
|
||||||
|
entry=entry, option_key=option_key))
|
||||||
|
|
||||||
|
|
||||||
def email(entry, option_key="Email Address", **kwargs):
|
def email(entry, option_key="Email Address", **kwargs):
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError("Email address field empty!")
|
raise ValueError(_("Email address field empty!"))
|
||||||
valid = validate_email_address(entry)
|
valid = validate_email_address(entry)
|
||||||
if not valid:
|
if not valid:
|
||||||
raise ValueError(f"That isn't a valid {option_key}!")
|
raise ValueError(_("That isn't a valid {option_key}!").format(option_key=option_key))
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def lock(entry, option_key="locks", access_options=None, **kwargs):
|
def lock(entry, option_key="locks", access_options=None, **kwargs):
|
||||||
entry = entry.strip()
|
entry = entry.strip()
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError(f"No {option_key} entered to set!")
|
raise ValueError(_("No {option_key} entered to set!").format(option_key=option_key))
|
||||||
for locksetting in entry.split(";"):
|
for locksetting in entry.split(";"):
|
||||||
access_type, lockfunc = locksetting.split(":", 1)
|
access_type, lockfunc = locksetting.split(":", 1)
|
||||||
if not access_type:
|
if not access_type:
|
||||||
raise ValueError("Must enter an access type!")
|
raise ValueError(_("Must enter an access type!"))
|
||||||
if access_options:
|
if access_options:
|
||||||
if access_type not in access_options:
|
if access_type not in access_options:
|
||||||
raise ValueError(f"Access type must be one of: {', '.join(access_options)}")
|
raise ValueError(_("Access type must be one of: {alternatives}").format(
|
||||||
|
alternatives=', '.join(access_options)))
|
||||||
if not lockfunc:
|
if not lockfunc:
|
||||||
raise ValueError("Lock func not entered.")
|
raise ValueError(_("Lock func not entered."))
|
||||||
return entry
|
return entry
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue