mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
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* (often abbreviated *i18n* since there are 18 characters between the first "i"
|
||||
and the last "n" in that word) allows Evennia's core server to return texts in other languages than
|
||||
English - without anyone having to edit the source code. Take a look at the `locale` directory of
|
||||
the Evennia installation, there you will find which languages are currently supported.
|
||||
*Internationalization* (often abbreviated *i18n* since there are 18 characters
|
||||
between the first "i" and the last "n" in that word) allows Evennia's core
|
||||
server to return texts in other languages than English - without anyone having
|
||||
to edit the source code. Take a look at the `locale` directory of the Evennia
|
||||
installation, there you will find which languages are currently supported.
|
||||
|
||||
## 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
|
||||
|
||||
USE_I18N = True
|
||||
LANGUAGE_CODE = 'en'
|
||||
|
||||
```
|
||||
|
||||
Here `'en'` should be changed to the abbreviation for one of the supported languages found in
|
||||
`locale/`. Restart the server to activate i18n. The two-character international language codes are
|
||||
found [here](http://www.science.co.il/Language/Codes.asp).
|
||||
Here `'en'` should be changed to the abbreviation for one of the supported
|
||||
languages found in `locale/`. Restart the server to activate i18n. The
|
||||
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
|
||||
documentation](https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#gettext-on-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).
|
||||
> Windows Note: If you get errors concerning `gettext` or `xgettext` on Windows,
|
||||
> see the
|
||||
> [Django documentation](https://docs.djangoproject.com/en/3.2/topics/i18n/translation/#gettext-on-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
|
||||
|
||||
> **Important Note:** Evennia offers translations of hard-coded strings in the server, things like
|
||||
"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).
|
||||
```important::
|
||||
|
||||
> In addition, text in default Commands and in default Typeclasses will *not* be translated by
|
||||
switching *i18n* language. To translate Commands and Typeclass 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.
|
||||
Evennia offers translations of hard-coded strings in the server, things like
|
||||
"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).
|
||||
|
||||
If you cannot find your language in `evennia/locale/` it's because noone has translated it yet.
|
||||
Alternatively you might have the language but find the translation bad ... You are welcome to help
|
||||
improve the situation!
|
||||
In addition, text in default Commands and in default Typeclasses will *not* be
|
||||
translated by switching *i18n* language. To translate Commands and Typeclass
|
||||
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
|
||||
activated a python virtualenv as described on the [Setup Quickstart](../Setup/Setup-Quickstart) page. You now
|
||||
need to `cd` to the `evennia/` directory. This is *not* your created game folder but the main
|
||||
Evennia library folder. If you see a folder `locale/` then you are in the right place. From here you
|
||||
run:
|
||||
If you cannot find your language in `evennia/locale/` it's because noone has
|
||||
translated it yet. Alternatively you might have the language but find the
|
||||
translation bad ... You are welcome to help improve the situation!
|
||||
|
||||
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)
|
||||
for the language you want, like 'sv' for Swedish or 'es' for Spanish. After a moment it will tell
|
||||
you the language has been processed. For instance:
|
||||
for the language you want to translate, like 'sv' for Swedish or 'es' for
|
||||
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/`
|
||||
folder. Otherwise the system will just have updated the existing translation with eventual new
|
||||
strings found in the server. Running this command will not overwrite any existing strings so you can
|
||||
run it as much as you want.
|
||||
If you started a new language, a new folder for that language will have emerged
|
||||
in the `locale/` folder. Otherwise the system will just have updated the
|
||||
existing translation with eventual new strings found in the server. Running this
|
||||
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 (`...
|
||||
makemessages -l sv` for instance). This syntax is not allowed in Evennia, due to the fact that `-l`
|
||||
is the option to tail log files. Hence, `makemessages` doesn't use the `-l` flag.
|
||||
Next head to `locale/<language-code>/LC_MESSAGES` and edit the `**.po` file you
|
||||
find there. You can edit this with a normal text editor but it is easiest if
|
||||
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
|
||||
edit this with a normal text editor but it is easiest if you use a special po-file editor from the
|
||||
web (search the web for "po editor" for many free alternatives).
|
||||
|
||||
The concept of translating is simple, it's just a matter of taking the english strings you find in
|
||||
the `**.po` file and add your language's translation best 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 are unsure about the translation, or that you couldn't find a translation that
|
||||
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:
|
||||
The concept of translating is simple, it's just a matter of taking the english
|
||||
strings you find in the `**.po` file and add your language's translation best
|
||||
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
|
||||
are unsure about the translation, or that you couldn't find a translation that
|
||||
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
|
||||
|
||||
This will go through all languages and create/update compiled files (`**.mo`) for them. This needs
|
||||
to be done whenever a `**.po` file is updated.
|
||||
This will go through all languages and create/update compiled files (`**.mo`)
|
||||
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
|
||||
your own repository clone) so we can integrate your translation into Evennia!
|
||||
When you are done, make sure that everyone can benefit from your translation!
|
||||
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
|
||||
store OOC information, not game info! This
|
||||
|
|
@ -54,7 +54,8 @@ _CMDHANDLER = None
|
|||
|
||||
# Create throttles for too many account-creations and login attempts
|
||||
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(
|
||||
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")
|
||||
if self.get_puppet(session) == obj:
|
||||
# already puppeting this object
|
||||
self.msg(_("You are already puppeting this object."))
|
||||
self.msg("You are already puppeting this object.")
|
||||
return
|
||||
if not obj.access(self, "puppet"):
|
||||
# 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
|
||||
if obj.account:
|
||||
# object already puppeted
|
||||
|
|
@ -312,8 +313,8 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
else:
|
||||
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"
|
||||
self.msg(_(txt1), session=session)
|
||||
self.msg(_(txt2), session=obj.sessions.all())
|
||||
self.msg(txt1, session=session)
|
||||
self.msg(txt2, session=obj.sessions.all())
|
||||
self.unpuppet_object(obj.sessions.get())
|
||||
elif obj.account.is_connected:
|
||||
# controlled by another account
|
||||
|
|
@ -543,7 +544,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
|
||||
# Update throttle
|
||||
if ip:
|
||||
LOGIN_THROTTLE.update(ip, "Too many authentication failures.")
|
||||
LOGIN_THROTTLE.update(ip, _("Too many authentication failures."))
|
||||
|
||||
# Try to call post-failure hook
|
||||
session = kwargs.get("session", None)
|
||||
|
|
@ -658,8 +659,9 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
password (str): Password to set.
|
||||
|
||||
Notes:
|
||||
This is called by Django also when logging in; it should not be mixed up with validation, since that
|
||||
would mean old passwords in the database (pre validation checks) could get invalidated.
|
||||
This is called by Django also when logging in; it should not be mixed up with
|
||||
validation, since that would mean old passwords in the database (pre validation checks)
|
||||
could get invalidated.
|
||||
|
||||
"""
|
||||
super(DefaultAccount, self).set_password(password)
|
||||
|
|
@ -798,12 +800,10 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
)
|
||||
logger.log_sec(f"Account Created: {account} (IP: {ip}).")
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
errors.append(
|
||||
_(
|
||||
"There was an error creating the Account. If this problem persists, contact an admin."
|
||||
)
|
||||
)
|
||||
_("There was an error creating the Account. "
|
||||
"If this problem persists, contact an admin."))
|
||||
logger.log_trace()
|
||||
return None, errors
|
||||
|
||||
|
|
@ -819,7 +819,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
# join the new account to the public channel
|
||||
pchannel = ChannelDB.objects.get_channel(settings.DEFAULT_CHANNELS[0]["key"])
|
||||
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)
|
||||
logger.log_err(string)
|
||||
|
||||
|
|
@ -1574,7 +1574,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
|||
if hasattr(target, "return_appearance"):
|
||||
return target.return_appearance(self)
|
||||
else:
|
||||
return _("{target} has no in-game appearance.").format(target=target)
|
||||
return f"{target} has no in-game appearance."
|
||||
else:
|
||||
# list of targets - make list to disconnect from db
|
||||
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 not characters:
|
||||
result.append(
|
||||
_(
|
||||
"\n\n You don't have any characters yet. See |whelp @charcreate|n for creating one."
|
||||
)
|
||||
"\n\n You don't have any characters yet. See |whelp charcreate|n for "
|
||||
"creating one."
|
||||
)
|
||||
else:
|
||||
result.append("\n |w@charcreate <name> [=description]|n - create new character")
|
||||
result.append("\n |wcharcreate <name> [=description]|n - create new character")
|
||||
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:
|
||||
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:
|
||||
result.append(
|
||||
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
|
||||
if sess and sid:
|
||||
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:
|
||||
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:
|
||||
# character is "free to puppet"
|
||||
|
|
@ -1668,6 +1668,7 @@ class DefaultGuest(DefaultAccount):
|
|||
"""
|
||||
This class is used for guest logins. Unlike Accounts, Guests and
|
||||
their characters are deleted after disconnection.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
|
|
@ -1675,6 +1676,7 @@ class DefaultGuest(DefaultAccount):
|
|||
"""
|
||||
Forwards request to cls.authenticate(); returns a DefaultGuest object
|
||||
if one is available for use.
|
||||
|
||||
"""
|
||||
return cls.authenticate(**kwargs)
|
||||
|
||||
|
|
@ -1742,7 +1744,7 @@ class DefaultGuest(DefaultAccount):
|
|||
|
||||
return account, errors
|
||||
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# We are in the middle between logged in and -not, so we have
|
||||
# to handle tracebacks ourselves at this point. If we don't,
|
||||
# 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()))
|
||||
for obj in self._nicklist_callers:
|
||||
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 = []
|
||||
return
|
||||
|
|
@ -341,7 +341,7 @@ class IRCBot(Bot):
|
|||
chstr = f"{self.db.irc_channel} ({self.db.irc_network}:{self.db.irc_port})"
|
||||
for obj in self._ping_callers:
|
||||
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"]
|
||||
)
|
||||
)
|
||||
|
|
@ -397,6 +397,7 @@ class IRCBot(Bot):
|
|||
|
||||
#
|
||||
# RSS
|
||||
#
|
||||
|
||||
|
||||
class RSSBot(Bot):
|
||||
|
|
|
|||
|
|
@ -94,7 +94,8 @@ class AccountDB(TypedObject, AbstractUser):
|
|||
"cmdset",
|
||||
max_length=255,
|
||||
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
|
||||
db_is_bot = models.BooleanField(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
from mock import Mock, MagicMock, patch
|
||||
from random import randint
|
||||
from unittest import TestCase
|
||||
|
|
@ -12,8 +11,6 @@ from evennia.utils.test_resources import EvenniaTest
|
|||
from evennia.utils import create
|
||||
from evennia.utils.utils import uses_database
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class TestAccountSessionHandler(TestCase):
|
||||
"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.
|
||||
|
||||
_ERROR_UNTRAPPED = (
|
||||
"""
|
||||
_("""
|
||||
An untrapped error occurred.
|
||||
""",
|
||||
"""
|
||||
"""),
|
||||
_("""
|
||||
An untrapped error occurred. Please file a bug report detailing the steps to reproduce.
|
||||
""",
|
||||
"""),
|
||||
)
|
||||
|
||||
_ERROR_CMDSETS = (
|
||||
"""
|
||||
_("""
|
||||
A cmdset merger-error occurred. This is often due to a syntax
|
||||
error in one of the cmdsets to merge.
|
||||
""",
|
||||
"""
|
||||
"""),
|
||||
_("""
|
||||
A cmdset merger-error occurred. Please file a bug report detailing the
|
||||
steps to reproduce.
|
||||
""",
|
||||
"""),
|
||||
)
|
||||
|
||||
_ERROR_NOCMDSETS = (
|
||||
"""
|
||||
_("""
|
||||
No command sets found! This is a critical bug that can have
|
||||
multiple causes.
|
||||
""",
|
||||
"""
|
||||
"""),
|
||||
_("""
|
||||
No command sets found! This is a sign of a critical bug. If
|
||||
disconnecting/reconnecting doesn't" solve the problem, try to contact
|
||||
the server admin through" some other means for assistance.
|
||||
""",
|
||||
"""),
|
||||
)
|
||||
|
||||
_ERROR_CMDHANDLER = (
|
||||
"""
|
||||
_("""
|
||||
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
|
||||
traceback and steps to reproduce.
|
||||
""",
|
||||
"""
|
||||
"""),
|
||||
_("""
|
||||
A command handler bug occurred. Please notify staff - they should
|
||||
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})."
|
||||
)
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ def _msg_err(receiver, stringtuple):
|
|||
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()
|
||||
tracestring = format_exc()
|
||||
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):
|
||||
"""
|
||||
Helper-method; Get Object-level cmdsets
|
||||
|
||||
"""
|
||||
# Gather cmdsets from location, objects in location or carried
|
||||
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
|
||||
hooks safely. Returns the stack and the valid options.
|
||||
|
||||
"""
|
||||
try:
|
||||
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"
|
||||
]
|
||||
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":
|
||||
# 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"
|
||||
]
|
||||
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":
|
||||
# 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"
|
||||
]
|
||||
cmdsets += yield local_obj_cmdsets
|
||||
# if not current.no_channels:
|
||||
# # also objs may have channels
|
||||
# cmdsets += yield _get_channel_cmdset(obj)
|
||||
else:
|
||||
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
|
||||
])
|
||||
options = (", " + options) if options else ""
|
||||
return f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: " + ", ".join(
|
||||
[str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)])
|
||||
return (f"<CmdSet {self.key}, {self.mergetype}, {perm}, prio {self.priority}{options}>: "
|
||||
+ ", ".join([str(cmd) for cmd in sorted(self.commands, key=lambda o: o.key)]))
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
|
|
@ -477,7 +477,8 @@ class CmdSet(object, metaclass=_CmdSetMeta):
|
|||
# This is used for diagnosis.
|
||||
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
|
||||
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
|
||||
class, and should take care of populating the cmdset by use of
|
||||
self.add().
|
||||
|
||||
"""
|
||||
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
|
||||
example, you can have a 'On a boat' set, onto which you then tack on
|
||||
the 'Fishing' set. Fishing from a boat? No problem!
|
||||
|
||||
"""
|
||||
import sys
|
||||
from traceback import format_exc
|
||||
|
|
@ -168,7 +169,7 @@ def import_cmdset(path, cmdsetobj, emit_to_obj=None, no_logging=False):
|
|||
if "." in path:
|
||||
modpath, classname = python_path.rsplit(".", 1)
|
||||
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:
|
||||
# first try to get from cache
|
||||
|
|
@ -312,6 +313,7 @@ class CmdSetHandler(object):
|
|||
def __str__(self):
|
||||
"""
|
||||
Display current commands
|
||||
|
||||
"""
|
||||
|
||||
strings = ["<CmdSetHandler> stack:"]
|
||||
|
|
@ -331,7 +333,7 @@ class CmdSetHandler(object):
|
|||
|
||||
if mergelist:
|
||||
# current is a result of mergers
|
||||
mergelist="+".join(mergelist)
|
||||
mergelist = "+".join(mergelist)
|
||||
strings.append(f" <Merged {mergelist}>: {self.current}")
|
||||
else:
|
||||
# current is a single cmdset
|
||||
|
|
@ -421,7 +423,8 @@ class CmdSetHandler(object):
|
|||
self.mergetype_stack.append(new_current.actual_mergetype)
|
||||
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
|
||||
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.
|
||||
emit_to_obj (Object, optional): An object to receive error messages.
|
||||
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 position (there is only one such position,
|
||||
always at the bottom of the stack).
|
||||
|
|
@ -450,10 +451,9 @@ class CmdSetHandler(object):
|
|||
|
||||
"""
|
||||
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.")
|
||||
|
||||
permanent = persistent or permanent
|
||||
persistent = kwargs['permanent'] if persistent is None else persistent
|
||||
|
||||
if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)):
|
||||
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.
|
||||
cmdset = self._import_cmdset(cmdset)
|
||||
if cmdset and cmdset.key != "_CMDSET_ERROR":
|
||||
cmdset.permanent = permanent
|
||||
if permanent and cmdset.key != "_CMDSET_ERROR":
|
||||
cmdset.permanent = persistent # TODO change on cmdset too
|
||||
if persistent and cmdset.key != "_CMDSET_ERROR":
|
||||
# store the path permanently
|
||||
storage = self.obj.cmdset_storage or [""]
|
||||
if default_cmdset:
|
||||
|
|
@ -480,17 +480,21 @@ class CmdSetHandler(object):
|
|||
self.cmdset_stack.append(cmdset)
|
||||
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.
|
||||
|
||||
Args:
|
||||
cmdset (Cmdset): The Cmdset to add.
|
||||
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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -572,6 +572,7 @@ Command {self} has no defined `func()` - showing on-command variables:
|
|||
border_left_char=border_left_char,
|
||||
border_right_char=border_right_char,
|
||||
border_top_char=border_top_char,
|
||||
border_bottom_char=border_bottom_char,
|
||||
**kwargs,
|
||||
)
|
||||
return table
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from django.urls import reverse
|
|||
from django.utils.text import slugify
|
||||
|
||||
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.utils import create, logger
|
||||
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_replacement = "channel {channelname} = $1"
|
||||
|
||||
|
||||
def at_first_save(self):
|
||||
"""
|
||||
Called by the typeclass system the very first time the channel
|
||||
|
|
@ -706,7 +705,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
"""
|
||||
try:
|
||||
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
HTML anchor.
|
||||
|
|
@ -740,7 +741,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
"%s-detail" % slugify(self._meta.verbose_name),
|
||||
kwargs={"slug": slugify(self.db_key)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
HTML anchor.
|
||||
|
|
@ -774,7 +777,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
"%s-update" % slugify(self._meta.verbose_name),
|
||||
kwargs={"slug": slugify(self.db_key)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
def web_get_delete_url(self):
|
||||
|
|
@ -807,7 +810,7 @@ class DefaultChannel(ChannelDB, metaclass=TypeclassBase):
|
|||
"%s-delete" % slugify(self._meta.verbose_name),
|
||||
kwargs={"slug": slugify(self.db_key)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
# Used by Django Sites/Admin
|
||||
|
|
|
|||
|
|
@ -218,7 +218,6 @@ class MsgManager(TypedObjectManager):
|
|||
else:
|
||||
raise CommError
|
||||
|
||||
|
||||
def search_message(self, sender=None, receiver=None, freetext=None, dbref=None):
|
||||
"""
|
||||
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
|
||||
connect to channels by use of a ChannelConnect object (this object is
|
||||
necessary to easily be able to delete connections on the fly).
|
||||
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
|
@ -165,7 +166,8 @@ class Msg(SharedMemoryModel):
|
|||
db_tags = models.ManyToManyField(
|
||||
Tag,
|
||||
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
|
||||
|
|
@ -309,7 +311,6 @@ class Msg(SharedMemoryModel):
|
|||
self.db_receiver_external = ""
|
||||
self.save()
|
||||
|
||||
|
||||
def remove_receiver(self, receivers):
|
||||
"""
|
||||
Remove a single receiver, a list of receivers, or a single extral receiver.
|
||||
|
|
@ -337,8 +338,8 @@ class Msg(SharedMemoryModel):
|
|||
elif clsname == "ScriptDB":
|
||||
self.db_receivers_scripts.remove(receiver)
|
||||
|
||||
|
||||
def __hide_from_get(self):
|
||||
@property
|
||||
def hide_from(self):
|
||||
"""
|
||||
Getter. Allows for value = self.hide_from.
|
||||
Returns two lists of accounts and objects.
|
||||
|
|
@ -349,9 +350,12 @@ class Msg(SharedMemoryModel):
|
|||
self.db_hide_from_objects.all(),
|
||||
)
|
||||
|
||||
# @hide_from_sender.setter
|
||||
def __hide_from_set(self, hiders):
|
||||
"Setter. Allows for self.hide_from = value. Will append to hiders"
|
||||
@hide_from.setter
|
||||
def hide_from(self, hiders):
|
||||
"""
|
||||
Setter. Allows for self.hide_from = value. Will append to hiders.
|
||||
|
||||
"""
|
||||
for hider in make_iter(hiders):
|
||||
if not hider:
|
||||
continue
|
||||
|
|
@ -363,21 +367,25 @@ class Msg(SharedMemoryModel):
|
|||
elif clsname == "ObjectDB":
|
||||
self.db_hide_from_objects.add(hider.__dbclass__)
|
||||
|
||||
# @hide_from_sender.deleter
|
||||
def __hide_from_del(self):
|
||||
"Deleter. Allows for del self.hide_from_senders"
|
||||
@hide_from.deleter
|
||||
def hide_from(self):
|
||||
"""
|
||||
Deleter. Allows for del self.hide_from_senders
|
||||
|
||||
"""
|
||||
self.db_hide_from_accounts.clear()
|
||||
self.db_hide_from_objects.clear()
|
||||
self.save()
|
||||
|
||||
hide_from = property(__hide_from_get, __hide_from_set, __hide_from_del)
|
||||
|
||||
#
|
||||
# Msg class methods
|
||||
#
|
||||
|
||||
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)
|
||||
receivers = ",".join(getattr(obj, "key", str(obj)) for obj in self.receivers)
|
||||
return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40))
|
||||
|
|
@ -407,9 +415,8 @@ class Msg(SharedMemoryModel):
|
|||
|
||||
class TempMsg(object):
|
||||
"""
|
||||
This is a non-persistent object for sending temporary messages
|
||||
that will not be stored. It mimics the "real" Msg object, but
|
||||
doesn't require sender to be given.
|
||||
This is a non-persistent object for sending temporary messages that will not be stored. It
|
||||
mimics the "real" Msg object, but doesn't require sender to be given.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -452,6 +459,7 @@ class TempMsg(object):
|
|||
def __str__(self):
|
||||
"""
|
||||
This handles what is shown when e.g. printing the message.
|
||||
|
||||
"""
|
||||
senders = ",".join(obj.key for obj in self.senders)
|
||||
receivers = ",".join(obj.key for obj in self.receivers)
|
||||
|
|
@ -477,6 +485,7 @@ class TempMsg(object):
|
|||
|
||||
Args:
|
||||
receiver (Object, Account, Script, str or list): Receivers to remove.
|
||||
|
||||
"""
|
||||
|
||||
for o in make_iter(receiver):
|
||||
|
|
@ -513,6 +522,7 @@ class SubscriptionHandler(object):
|
|||
This handler manages subscriptions to the
|
||||
channel and hides away which type of entity is
|
||||
subscribing (Account or Object)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, obj):
|
||||
|
|
@ -634,7 +644,8 @@ class SubscriptionHandler(object):
|
|||
if not obj.is_connected:
|
||||
continue
|
||||
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
|
||||
continue
|
||||
subs.append(obj)
|
||||
|
|
@ -688,7 +699,7 @@ class ChannelDB(TypedObject):
|
|||
__defaultclasspath__ = "evennia.comms.comms.DefaultChannel"
|
||||
__applabel__ = "comms"
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Channel"
|
||||
verbose_name_plural = "Channels"
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ class FileHelpStorageHandler:
|
|||
|
||||
Note that this is not meant to any searching/lookup - that is all handled
|
||||
by the help command.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, help_file_modules=settings.FILE_HELP_ENTRY_MODULES):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
Custom manager for HelpEntry objects.
|
||||
"""
|
||||
from django.db import models
|
||||
from evennia.utils import logger, utils
|
||||
from evennia.typeclasses.managers import TypedObjectManager
|
||||
|
||||
|
|
@ -131,7 +130,7 @@ class HelpEntryManager(TypedObjectManager):
|
|||
for topic in topics:
|
||||
topic.help_category = default_category
|
||||
topic.save()
|
||||
string = _("Help database moved to category {default_category}").format(
|
||||
string = "Help database moved to category {default_category}".format(
|
||||
default_category=default_category
|
||||
)
|
||||
logger.log_info(string)
|
||||
|
|
|
|||
|
|
@ -168,7 +168,9 @@ class HelpEntry(SharedMemoryModel):
|
|||
a named view of 'character-create' would be referenced by this method.
|
||||
|
||||
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
|
||||
HTML anchor.
|
||||
|
|
@ -183,7 +185,7 @@ class HelpEntry(SharedMemoryModel):
|
|||
"""
|
||||
try:
|
||||
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
HTML anchor.
|
||||
|
|
@ -217,8 +220,7 @@ class HelpEntry(SharedMemoryModel):
|
|||
"%s-detail" % slugify(self._meta.verbose_name),
|
||||
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
HTML anchor.
|
||||
|
|
@ -252,7 +256,7 @@ class HelpEntry(SharedMemoryModel):
|
|||
"%s-update" % slugify(self._meta.verbose_name),
|
||||
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
HTML anchor.
|
||||
|
|
@ -285,7 +291,7 @@ class HelpEntry(SharedMemoryModel):
|
|||
"%s-delete" % slugify(self._meta.verbose_name),
|
||||
kwargs={"category": slugify(self.db_help_category), "topic": slugify(self.db_key)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
# 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)
|
||||
_LUNR_BUILDER_PIPELINE = (trimmer, custom_stop_words_filter, stemmer)
|
||||
|
||||
|
||||
indx = [cnd.search_index_entry for cnd in 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
|
||||
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):
|
||||
"Always returns True."
|
||||
return True
|
||||
"""
|
||||
Always returns True.
|
||||
|
||||
"""
|
||||
return True
|
||||
|
||||
def all(*args, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
def false(*args, **kwargs):
|
||||
"Always returns False"
|
||||
"""
|
||||
Always returns False
|
||||
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -129,6 +59,10 @@ def none(*args, **kwargs):
|
|||
return False
|
||||
|
||||
|
||||
def superuser(*args, **kwargs):
|
||||
return False
|
||||
|
||||
|
||||
def self(accessing_obj, accessed_obj, *args, **kwargs):
|
||||
"""
|
||||
Check if accessing_obj is the same as accessed_obj
|
||||
|
|
@ -167,7 +101,7 @@ def perm(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
try:
|
||||
permission = args[0].lower()
|
||||
perms_object = accessing_obj.permissions.all()
|
||||
except (AttributeError, IndexError) as err:
|
||||
except (AttributeError, IndexError):
|
||||
return False
|
||||
|
||||
gtmode = kwargs.pop("_greater_than", False)
|
||||
|
|
@ -644,17 +578,6 @@ def holds(accessing_obj, accessed_obj, *args, **kwargs):
|
|||
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):
|
||||
"""
|
||||
Only returns true if accessing_obj has_account is true, that is,
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ _LOCK_HANDLER = None
|
|||
class LockException(Exception):
|
||||
"""
|
||||
Raised during an error in a lock.
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
@ -139,6 +140,7 @@ _LOCKFUNCS = {}
|
|||
def _cache_lockfuncs():
|
||||
"""
|
||||
Updates the cache.
|
||||
|
||||
"""
|
||||
global _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
|
||||
permission checks, under the property 'lockhandler'.
|
||||
|
|
@ -260,16 +262,13 @@ class LockHandler(object):
|
|||
continue
|
||||
if access_type in locks:
|
||||
duplicates += 1
|
||||
wlist.append(
|
||||
_(
|
||||
"LockHandler on %(obj)s: access type '%(access_type)s' changed from '%(source)s' to '%(goal)s' "
|
||||
% {
|
||||
"obj": self.obj,
|
||||
"access_type": access_type,
|
||||
"source": locks[access_type][2],
|
||||
"goal": raw_lockstring,
|
||||
}
|
||||
)
|
||||
wlist.append(_(
|
||||
"LockHandler on {obj}: access type '{access_type}' "
|
||||
"changed from '{source}' to '{goal}' ".format(
|
||||
obj=self.obj,
|
||||
access_type=access_type,
|
||||
source=locks[access_type][2],
|
||||
goal=raw_lockstring))
|
||||
)
|
||||
locks[access_type] = (evalstring, tuple(lock_funcs), raw_lockstring)
|
||||
if wlist and WARNING_LOG:
|
||||
|
|
@ -284,12 +283,14 @@ class LockHandler(object):
|
|||
def _cache_locks(self, storage_lockstring):
|
||||
"""
|
||||
Store data
|
||||
|
||||
"""
|
||||
self.locks = self._parse_lockstring(storage_lockstring)
|
||||
|
||||
def _save_locks(self):
|
||||
"""
|
||||
Store locks to obj
|
||||
|
||||
"""
|
||||
self.obj.lock_storage = ";".join([tup[2] for tup in self.locks.values()])
|
||||
|
||||
|
|
@ -693,8 +694,7 @@ def check_lockstring(
|
|||
access_type=access_type,
|
||||
)
|
||||
|
||||
def check_perm(
|
||||
obj, permission, no_superuser_bypass=False):
|
||||
def check_perm(obj, permission, no_superuser_bypass=False):
|
||||
"""
|
||||
Shortcut for checking if an object has the given `permission`. If the
|
||||
permission is in `settings.PERMISSION_HIERARCHY`, the check passes
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
Custom manager for Objects.
|
||||
"""
|
||||
import re
|
||||
from itertools import chain
|
||||
from django.db.models import Q
|
||||
from django.conf import settings
|
||||
from django.db.models.fields import exceptions
|
||||
|
|
@ -154,7 +153,8 @@ class ObjectDBManager(TypedObjectManager):
|
|||
|
||||
Args:
|
||||
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.
|
||||
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
|
||||
db_account field set.
|
||||
|
||||
"""
|
||||
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
|
||||
location having the property destination != `None`.
|
||||
|
||||
"""
|
||||
return [exi for exi in self.contents if exi.destination]
|
||||
|
||||
|
|
@ -345,6 +346,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
Returns:
|
||||
singular (str): The singular form to display.
|
||||
plural (str): The determined plural form of the key, including the count.
|
||||
|
||||
"""
|
||||
plural_category = "plural_key"
|
||||
key = kwargs.get("key", self.key)
|
||||
|
|
@ -700,6 +702,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
|
||||
Keyword Args:
|
||||
Keyword arguments will be passed to the function for all objects.
|
||||
|
||||
"""
|
||||
contents = self.contents
|
||||
if exclude:
|
||||
|
|
@ -947,6 +950,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
"""
|
||||
Destroys all of the exits and any exits pointing to this
|
||||
object as a destination.
|
||||
|
||||
"""
|
||||
for out_exit in [exi for exi in ObjectDB.objects.get_contents(self) if exi.db_destination]:
|
||||
out_exit.delete()
|
||||
|
|
@ -957,6 +961,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
"""
|
||||
Moves all objects (accounts/things) to their home location or
|
||||
to default home.
|
||||
|
||||
"""
|
||||
# Gather up everything that thinks this is its location.
|
||||
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 not home:
|
||||
string = "Missing default home, '%s(#%d)' "
|
||||
string += "now has a null location."
|
||||
obj.location = None
|
||||
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
|
||||
|
||||
if obj.has_account:
|
||||
|
|
@ -1539,7 +1543,8 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
if not source_location and self.location.has_account:
|
||||
# This was created from nowhere and added to an account's
|
||||
# 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)
|
||||
return
|
||||
|
||||
|
|
@ -1547,9 +1552,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
|||
if msg:
|
||||
string = msg
|
||||
else:
|
||||
string = "{object} arrives to {destination} from {origin}."
|
||||
string = _("{object} arrives to {destination} from {origin}.")
|
||||
else:
|
||||
string = "{object} arrives to {destination}."
|
||||
string = _("{object} arrives to {destination}.")
|
||||
|
||||
origin = source_location
|
||||
destination = self.location
|
||||
|
|
@ -2157,7 +2162,7 @@ class DefaultCharacter(DefaultObject):
|
|||
key = cls.normalize_name(key)
|
||||
|
||||
if not cls.validate_name(key):
|
||||
errors.append("Invalid character name.")
|
||||
errors.append(_("Invalid character name."))
|
||||
return obj, errors
|
||||
|
||||
# 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
|
||||
if account:
|
||||
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
|
||||
|
||||
# Create the Character
|
||||
|
|
@ -2202,10 +2207,10 @@ class DefaultCharacter(DefaultObject):
|
|||
|
||||
# If no description is set, set a default description
|
||||
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:
|
||||
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)
|
||||
|
||||
return obj, errors
|
||||
|
|
@ -2274,6 +2279,7 @@ class DefaultCharacter(DefaultObject):
|
|||
Args:
|
||||
account (Account): This is the connecting account.
|
||||
session (Session): Session controlling the connection.
|
||||
|
||||
"""
|
||||
if (
|
||||
self.location is None
|
||||
|
|
@ -2287,7 +2293,8 @@ class DefaultCharacter(DefaultObject):
|
|||
self.db.prelogout_location = self.location # save location again to be sure.
|
||||
else:
|
||||
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.
|
||||
|
||||
def at_post_puppet(self, **kwargs):
|
||||
|
|
@ -2305,11 +2312,12 @@ class DefaultCharacter(DefaultObject):
|
|||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -2332,7 +2340,8 @@ class DefaultCharacter(DefaultObject):
|
|||
if self.location:
|
||||
|
||||
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.db.prelogout_location = self.location
|
||||
|
|
@ -2343,6 +2352,7 @@ class DefaultCharacter(DefaultObject):
|
|||
"""
|
||||
Returns the idle time of the least idle session in seconds. If
|
||||
no sessions are connected it returns nothing.
|
||||
|
||||
"""
|
||||
idle = [session.cmd_last_visible for session in self.sessions.all()]
|
||||
if idle:
|
||||
|
|
@ -2354,6 +2364,7 @@ class DefaultCharacter(DefaultObject):
|
|||
"""
|
||||
Returns the maximum connection time of all connected sessions
|
||||
in seconds. Returns nothing if there are no sessions.
|
||||
|
||||
"""
|
||||
conn = [session.conn_time for session in self.sessions.all()]
|
||||
if conn:
|
||||
|
|
@ -2447,7 +2458,7 @@ class DefaultRoom(DefaultObject):
|
|||
|
||||
# If no description is set, set a default description
|
||||
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:
|
||||
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 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:
|
||||
errors.append("An error occurred while creating this '%s' object." % key)
|
||||
|
|
@ -2750,4 +2761,4 @@ class DefaultExit(DefaultObject):
|
|||
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
|
||||
|
||||
|
||||
|
||||
|
||||
# this is picked up by FuncParser
|
||||
FUNCPARSER_CALLABLES = {
|
||||
"protkey": protfunc_callable_protkey,
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos
|
|||
|
||||
import hashlib
|
||||
import time
|
||||
from ast import literal_eval
|
||||
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.utils.translation import gettext as _
|
||||
from evennia.scripts.scripts import DefaultScript
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.typeclasses.attributes import Attribute
|
||||
|
|
@ -22,10 +22,6 @@ from evennia.utils.utils import (
|
|||
make_iter,
|
||||
is_iter,
|
||||
dbid_to_obj,
|
||||
callables_from_module,
|
||||
get_all_typeclasses,
|
||||
to_str,
|
||||
dbref,
|
||||
justify,
|
||||
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.
|
||||
|
||||
|
||||
Args:
|
||||
prototype (dict): Prototype.
|
||||
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):
|
||||
"""
|
||||
This stores a single prototype, in an Attribute `prototype`.
|
||||
|
||||
"""
|
||||
|
||||
def at_script_creation(self):
|
||||
|
|
@ -262,7 +258,7 @@ def save_prototype(prototype):
|
|||
|
||||
prototype_key = in_prototype.get("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()
|
||||
|
||||
|
|
@ -270,7 +266,8 @@ def save_prototype(prototype):
|
|||
if prototype_key in _MODULE_PROTOTYPES:
|
||||
mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key, "N/A")
|
||||
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
|
||||
|
|
@ -337,20 +334,21 @@ def delete_prototype(prototype_key, caller=None):
|
|||
if prototype_key in _MODULE_PROTOTYPES:
|
||||
mod = _MODULE_PROTOTYPE_MODULES.get(prototype_key.lower(), "N/A")
|
||||
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)
|
||||
|
||||
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]
|
||||
if caller:
|
||||
if not stored_prototype.access(caller, "edit"):
|
||||
raise PermissionError(
|
||||
"{} needs explicit 'edit' permissions to "
|
||||
"delete prototype {}.".format(caller, prototype_key)
|
||||
_("{} needs explicit 'edit' permissions to "
|
||||
"delete prototype {}.").format(caller, prototype_key)
|
||||
)
|
||||
stored_prototype.delete()
|
||||
return True
|
||||
|
|
@ -449,7 +447,11 @@ def search_prototype(key=None, tags=None, require_single=False, return_iterators
|
|||
nmodules = len(module_prototypes)
|
||||
ndbprots = db_matches.count()
|
||||
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:
|
||||
# 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
|
||||
display an EvTable per paginated page rather than to try creating an
|
||||
EvTable for the entire dataset and then paginate it.
|
||||
|
||||
"""
|
||||
|
||||
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_edit = kwargs.pop("show_non_edit", False)
|
||||
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
|
||||
way. We will build the prototypes so that the db-prototypes come first (they
|
||||
are likely the most volatile), followed by the mod-prototypes.
|
||||
|
||||
"""
|
||||
dbprot_query, modprot_list = inp
|
||||
# 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
|
||||
one to pick based on the page number. Also, pageno starts from 0.
|
||||
|
||||
"""
|
||||
dbprot_pages, modprot_list = self._data
|
||||
|
||||
|
|
@ -522,15 +530,16 @@ class PrototypeEvMore(EvMore):
|
|||
else:
|
||||
# get the correct slice, adjusted for the db-prototypes
|
||||
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):
|
||||
"""Input is a queryset page from django.Paginator"""
|
||||
"""
|
||||
Input is a queryset page from django.Paginator
|
||||
|
||||
"""
|
||||
caller = self._caller
|
||||
|
||||
# get use-permissions of readonly attributes (edit is always False)
|
||||
display_tuples = []
|
||||
|
||||
table = EvTable(
|
||||
"|wKey|n",
|
||||
"|wSpawn/Edit|n",
|
||||
|
|
@ -599,7 +608,7 @@ def list_prototypes(
|
|||
dbprot_query, modprot_list = search_prototype(key, tags, return_iterators=True)
|
||||
|
||||
if not dbprot_query and not modprot_list:
|
||||
caller.msg("No prototypes found.", session=session)
|
||||
caller.msg(_("No prototypes found."), session=session)
|
||||
return None
|
||||
|
||||
# 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)
|
||||
|
||||
if strict and not bool(protkey):
|
||||
_flags["errors"].append("Prototype lacks a `prototype_key`.")
|
||||
_flags["errors"].append(_("Prototype lacks a `prototype_key`."))
|
||||
protkey = "[UNSET]"
|
||||
|
||||
typeclass = prototype.get("typeclass")
|
||||
|
|
@ -659,12 +668,13 @@ def validate_prototype(
|
|||
if strict and not (typeclass or prototype_parent):
|
||||
if is_prototype_base:
|
||||
_flags["errors"].append(
|
||||
"Prototype {} requires `typeclass` " "or 'prototype_parent'.".format(protkey)
|
||||
_("Prototype {protkey} requires `typeclass` " "or 'prototype_parent'.").format(
|
||||
protkey=protkey)
|
||||
)
|
||||
else:
|
||||
_flags["warnings"].append(
|
||||
"Prototype {} can only be used as a mixin since it lacks "
|
||||
"a typeclass or a prototype_parent.".format(protkey)
|
||||
_("Prototype {protkey} can only be used as a mixin since it lacks "
|
||||
"a typeclass or a prototype_parent.").format(protkey=protkey)
|
||||
)
|
||||
|
||||
if strict and typeclass:
|
||||
|
|
@ -672,9 +682,9 @@ def validate_prototype(
|
|||
class_from_module(typeclass)
|
||||
except ImportError as err:
|
||||
_flags["errors"].append(
|
||||
"{}: Prototype {} is based on typeclass {}, which could not be imported!".format(
|
||||
err, protkey, typeclass
|
||||
)
|
||||
_("{err}: Prototype {protkey} is based on typeclass {typeclass}, "
|
||||
"which could not be imported!").format(
|
||||
err=err, protkey=protkey, typeclass=typeclass)
|
||||
)
|
||||
|
||||
# recursively traverese prototype_parent chain
|
||||
|
|
@ -682,19 +692,22 @@ def validate_prototype(
|
|||
for protstring in make_iter(prototype_parent):
|
||||
protstring = protstring.lower()
|
||||
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)
|
||||
if not protparent:
|
||||
_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"]:
|
||||
_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"]:
|
||||
raise RuntimeError("Error: " + "\nError: ".join(_flags["errors"]))
|
||||
raise RuntimeError(_("Error: ") + _("\nError: ").join(_flags["errors"]))
|
||||
_flags["visited"].append(id(prototype))
|
||||
_flags["depth"] += 1
|
||||
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 strict and is_prototype_base and _flags["depth"] <= 0 and not _flags["typeclass"]:
|
||||
_flags["errors"].append(
|
||||
"Prototype {} has no `typeclass` defined anywhere in its parent\n "
|
||||
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
||||
"prototype with a typeclass.".format(protkey)
|
||||
_("Prototype {protkey} has no `typeclass` defined anywhere in its parent\n "
|
||||
"chain. Add `typeclass`, or a `prototype_parent` pointing to a "
|
||||
"prototype with a typeclass.").format(protkey=protkey)
|
||||
)
|
||||
|
||||
if _flags["depth"] <= 0:
|
||||
if _flags["errors"]:
|
||||
raise RuntimeError("Error: " + "\nError: ".join(_flags["errors"]))
|
||||
raise RuntimeError(_("Error: " + "\nError: ").join(_flags["errors"]))
|
||||
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
|
||||
prototype_locks = [
|
||||
|
|
@ -831,10 +844,10 @@ def prototype_to_str(prototype):
|
|||
category=category if category else "|wNone|n"
|
||||
)
|
||||
out.append(
|
||||
"{attrkey}{cat_locks} |c=|n {value}".format(
|
||||
"{attrkey}{cat_locks}{locks} |c=|n {value}".format(
|
||||
attrkey=attrkey,
|
||||
cat_locks=cat_locks,
|
||||
locks=locks if locks else "|wNone|n",
|
||||
locks=" |w(locks:|n {locks})".format(locks=locks) if locks else "",
|
||||
value=value,
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ Possible keywords are:
|
|||
supported are 'edit' and 'use'.
|
||||
prototype_tags(list, optional): List of tags or tuples (tag, category) used to group prototype
|
||||
in listings
|
||||
prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent prototype, or
|
||||
a list of parents, for multiple left-to-right inheritance.
|
||||
prototype_parent (str, tuple or callable, optional): name (prototype_key) of eventual parent
|
||||
prototype, or a list of parents, for multiple left-to-right inheritance.
|
||||
prototype: Deprecated. Same meaning as 'parent'.
|
||||
|
||||
typeclass (str or callable, optional): if not set, will use typeclass of parent prototype or use
|
||||
|
|
@ -138,6 +138,7 @@ import hashlib
|
|||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
import evennia
|
||||
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.
|
||||
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
|
||||
not in `prototype2`, it will not be REMOVEd but set to KEEP instead. This is particularly
|
||||
useful for auto-generated prototypes when updating objects.
|
||||
not in `prototype2`, it will not be REMOVEd but set to KEEP instead. This is
|
||||
particularly useful for auto-generated prototypes when updating objects.
|
||||
|
||||
Returns:
|
||||
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))
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Diff contains non-dicts that are not on the "
|
||||
"form (old, new, inst): {}".format(diffpart)
|
||||
_("Diff contains non-dicts that are not on the "
|
||||
"form (old, new, inst): {diffpart}").format(diffpart)
|
||||
)
|
||||
return out
|
||||
|
||||
|
|
@ -693,11 +694,13 @@ def batch_update_objects_with_prototype(prototype, diff=None, objects=None,
|
|||
elif key == "permissions":
|
||||
if directive == "REPLACE":
|
||||
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":
|
||||
if directive == "REPLACE":
|
||||
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":
|
||||
if directive == "REPLACE":
|
||||
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)
|
||||
else:
|
||||
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:
|
||||
# settings.DEFAULT_HOME not existing is common for unittests
|
||||
pass
|
||||
|
|
@ -945,7 +949,8 @@ def spawn(*prototypes, caller=None, **kwargs):
|
|||
val = prot.pop("tags", [])
|
||||
tags = []
|
||||
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)
|
||||
if prototype_key:
|
||||
|
|
|
|||
|
|
@ -27,11 +27,13 @@ class MonitorHandler(object):
|
|||
"""
|
||||
This is a resource singleton that allows for registering
|
||||
callbacks for when a field or Attribute is updated (saved).
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""
|
||||
Initialize the handler.
|
||||
|
||||
"""
|
||||
self.savekey = "_monitorhandler_save"
|
||||
self.monitors = defaultdict(lambda: defaultdict(dict))
|
||||
|
|
|
|||
|
|
@ -62,22 +62,28 @@ class TaskHandlerTask:
|
|||
return TASK_HANDLER.get_deferred(self.task_id)
|
||||
|
||||
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
|
||||
if d:
|
||||
d.pause()
|
||||
|
||||
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
|
||||
if d:
|
||||
d.unpause()
|
||||
|
||||
@property
|
||||
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.
|
||||
|
||||
|
|
@ -93,7 +99,8 @@ class TaskHandlerTask:
|
|||
return None
|
||||
|
||||
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.
|
||||
Remove the task from the dictionary of current tasks on a successful
|
||||
callback.
|
||||
|
|
@ -106,7 +113,8 @@ class TaskHandlerTask:
|
|||
return TASK_HANDLER.do_task(self.task_id)
|
||||
|
||||
def call(self):
|
||||
"""Call the callback of a task.
|
||||
"""
|
||||
Call the callback of a task.
|
||||
Leave the task unaffected otherwise.
|
||||
This does not use the task's deferred instance.
|
||||
The only requirement is that the task exist in task handler.
|
||||
|
|
@ -173,7 +181,8 @@ class TaskHandlerTask:
|
|||
return None
|
||||
|
||||
def exists(self):
|
||||
"""Check if a task exists.
|
||||
"""
|
||||
Check if a task exists.
|
||||
Most task handler methods check for existence for you.
|
||||
|
||||
Returns:
|
||||
|
|
@ -183,7 +192,8 @@ class TaskHandlerTask:
|
|||
return TASK_HANDLER.exists(self.task_id)
|
||||
|
||||
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`.
|
||||
|
||||
Returns:
|
||||
|
|
@ -215,7 +225,7 @@ class TaskHandler(object):
|
|||
self.clock = reactor
|
||||
# number of seconds before an uncalled canceled task is removed from TaskHandler
|
||||
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):
|
||||
"""Load from the ServerConfig.
|
||||
|
|
@ -271,7 +281,10 @@ class TaskHandler(object):
|
|||
return True
|
||||
|
||||
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():
|
||||
if task_id in self.to_save:
|
||||
|
|
@ -286,14 +299,12 @@ class TaskHandler(object):
|
|||
callback = (obj, name)
|
||||
|
||||
# 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))
|
||||
ServerConfig.objects.conf("delayed_tasks", self.to_save)
|
||||
|
||||
def add(self, timedelay, callback, *args, **kwargs):
|
||||
"""Add a new task.
|
||||
"""
|
||||
Add a new task.
|
||||
|
||||
If the persistent kwarg is truthy:
|
||||
The callback, args and values for kwarg will be serialized. Type
|
||||
|
|
@ -399,7 +410,8 @@ class TaskHandler(object):
|
|||
return TaskHandlerTask(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.
|
||||
|
||||
Args:
|
||||
|
|
@ -415,7 +427,8 @@ class TaskHandler(object):
|
|||
return False
|
||||
|
||||
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:
|
||||
task_id (int): an existing task ID.
|
||||
|
|
@ -433,7 +446,8 @@ class TaskHandler(object):
|
|||
return False
|
||||
|
||||
def cancel(self, task_id):
|
||||
"""Stop a task from automatically executing.
|
||||
"""
|
||||
Stop a task from automatically executing.
|
||||
This will not remove the task.
|
||||
|
||||
Args:
|
||||
|
|
@ -459,7 +473,8 @@ class TaskHandler(object):
|
|||
return False
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
|
@ -485,8 +500,8 @@ class TaskHandler(object):
|
|||
return 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:
|
||||
save=True (bool): Should changes to persistent tasks be saved to database.
|
||||
|
|
@ -508,7 +523,8 @@ class TaskHandler(object):
|
|||
return True
|
||||
|
||||
def call_task(self, task_id):
|
||||
"""Call the callback of a task.
|
||||
"""
|
||||
Call the callback of a task.
|
||||
Leave the task unaffected otherwise.
|
||||
This does not use the task's deferred instance.
|
||||
The only requirement is that the task exist in task handler.
|
||||
|
|
@ -528,7 +544,8 @@ class TaskHandler(object):
|
|||
return callback(*args, **kwargs)
|
||||
|
||||
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.
|
||||
Remove the task from the dictionary of current tasks on a successful
|
||||
callback.
|
||||
|
|
@ -573,7 +590,8 @@ class TaskHandler(object):
|
|||
return None
|
||||
|
||||
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.
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ manager's conf() method.
|
|||
|
||||
"""
|
||||
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 import logger, utils
|
||||
|
|
|
|||
|
|
@ -93,7 +93,10 @@ def loads(data):
|
|||
|
||||
|
||||
def _get_logger():
|
||||
"Delay import of logger until absolutely necessary"
|
||||
"""
|
||||
Delay import of logger until absolutely necessary
|
||||
|
||||
"""
|
||||
global _LOGGER
|
||||
if not _LOGGER:
|
||||
from evennia.utils import logger as _LOGGER
|
||||
|
|
@ -102,7 +105,10 @@ def _get_logger():
|
|||
|
||||
@wraps
|
||||
def catch_traceback(func):
|
||||
"Helper decorator"
|
||||
"""
|
||||
Helper decorator
|
||||
|
||||
"""
|
||||
|
||||
def decorator(*args, **kwargs):
|
||||
try:
|
||||
|
|
@ -353,6 +359,7 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
def dataReceived(self, data):
|
||||
"""
|
||||
Handle non-AMP messages, such as HTTP communication.
|
||||
|
||||
"""
|
||||
# print("dataReceived: {}".format(data))
|
||||
if data[:1] == NUL:
|
||||
|
|
@ -413,6 +420,7 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
that is irrelevant. If a true connection error happens, the
|
||||
portal will continuously try to reconnect, showing the problem
|
||||
that way.
|
||||
|
||||
"""
|
||||
# print("ConnectionLost: {}: {}".format(self, reason))
|
||||
try:
|
||||
|
|
@ -422,20 +430,20 @@ class AMPMultiConnectionProtocol(amp.AMP):
|
|||
|
||||
# Error handling
|
||||
|
||||
def errback(self, e, info):
|
||||
def errback(self, err, info):
|
||||
"""
|
||||
Error callback.
|
||||
Handles errors to avoid dropping connections on server tracebacks.
|
||||
|
||||
Args:
|
||||
e (Failure): Deferred error instance.
|
||||
err (Failure): Deferred error instance.
|
||||
info (str): Error string.
|
||||
|
||||
"""
|
||||
e.trap(Exception)
|
||||
err.trap(Exception)
|
||||
_get_logger().log_err(
|
||||
"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
|
||||
|
||||
def logPrefix(self):
|
||||
"How this is named in logs"
|
||||
"""
|
||||
How this is named in logs
|
||||
|
||||
"""
|
||||
return "AMP"
|
||||
|
||||
def __init__(self, portal):
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ class GrapevineClient(WebSocketClientProtocol, Session):
|
|||
# incoming broadcast from network
|
||||
payload = data["payload"]
|
||||
|
||||
print("channels/broadcast:", payload["channel"], self.channel)
|
||||
# print("channels/broadcast:", payload["channel"], self.channel)
|
||||
if str(payload["channel"]) != self.channel:
|
||||
# only echo from channels this particular bot actually listens to
|
||||
return
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ class Portal(object):
|
|||
|
||||
Returns:
|
||||
server_twistd_cmd (list): An instruction for starting the server, to pass to Popen.
|
||||
|
||||
"""
|
||||
server_twistd_cmd = [
|
||||
"twistd",
|
||||
|
|
@ -196,7 +197,10 @@ class Portal(object):
|
|||
return server_twistd_cmd
|
||||
|
||||
def get_info_dict(self):
|
||||
"Return the Portal info, for display."
|
||||
"""
|
||||
Return the Portal info, for display.
|
||||
|
||||
"""
|
||||
return INFO_DICT
|
||||
|
||||
def shutdown(self, _reactor_stopping=False, _stop_server=False):
|
||||
|
|
@ -354,7 +358,8 @@ if SSH_ENABLED:
|
|||
for port in SSH_PORTS:
|
||||
pstring = "%s:%s" % (ifacestr, port)
|
||||
factory = ssh.makeFactory(
|
||||
{"protocolFactory": _ssh_protocol, "protocolArgs": (), "sessions": PORTAL_SESSIONS,}
|
||||
{"protocolFactory": _ssh_protocol,
|
||||
"protocolArgs": (), "sessions": PORTAL_SESSIONS}
|
||||
)
|
||||
factory.noisy = False
|
||||
ssh_service = internet.TCPServer(port, factory, interface=interface)
|
||||
|
|
@ -390,7 +395,7 @@ if WEBSERVER_ENABLED:
|
|||
if WEBSOCKET_CLIENT_ENABLED and not websocket_started:
|
||||
# start websocket client port for the webclient
|
||||
# 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
|
||||
|
||||
w_interface = WEBSOCKET_CLIENT_INTERFACE
|
||||
|
|
@ -417,10 +422,13 @@ if WEBSERVER_ENABLED:
|
|||
if WEB_PLUGINS_MODULE:
|
||||
try:
|
||||
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"] = (
|
||||
"WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() not found - "
|
||||
"copy 'evennia/game_template/server/conf/web_plugins.py to mygame/server/conf."
|
||||
"WARNING: WEB_PLUGINS_MODULE is enabled but at_webproxy_root_creation() "
|
||||
"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.is_portal = True
|
||||
|
|
@ -435,4 +443,3 @@ for plugin_module in PORTAL_SERVICES_PLUGIN_MODULES:
|
|||
# external plugin services to start
|
||||
if plugin_module:
|
||||
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.utils.logger import log_trace
|
||||
from evennia.utils.utils import class_from_module
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
# module import
|
||||
_MOD_IMPORT = None
|
||||
|
|
@ -35,6 +37,8 @@ DUMMYSESSION = namedtuple("DummySession", ["sessid"])(0)
|
|||
# Portal-SessionHandler class
|
||||
# -------------------------------------------------------------
|
||||
|
||||
DOS_PROTECTION_MSG = _("{servername} DoS protection is active. You are queued to connect in {num} seconds ...")
|
||||
|
||||
|
||||
class PortalSessionHandler(SessionHandler):
|
||||
"""
|
||||
|
|
@ -111,16 +115,12 @@ class PortalSessionHandler(SessionHandler):
|
|||
_CONNECTION_QUEUE.appendleft(session)
|
||||
if len(_CONNECTION_QUEUE) > 1:
|
||||
session.data_out(
|
||||
text=[
|
||||
[
|
||||
"%s DoS protection is active. You are queued to connect in %g seconds ..."
|
||||
% (
|
||||
settings.SERVERNAME,
|
||||
len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS,
|
||||
)
|
||||
],
|
||||
text=(
|
||||
(DOS_PROTECTION_MSG.format(
|
||||
servername=settings.SERVERNAME,
|
||||
num=len(_CONNECTION_QUEUE) * _MIN_TIME_BETWEEN_CONNECTS),),
|
||||
{},
|
||||
]
|
||||
)
|
||||
)
|
||||
now = time.time()
|
||||
if (
|
||||
|
|
@ -220,6 +220,7 @@ class PortalSessionHandler(SessionHandler):
|
|||
def disconnect_all(self):
|
||||
"""
|
||||
Disconnect all sessions, informing the Server.
|
||||
|
||||
"""
|
||||
|
||||
def _callback(result, sessionhandler):
|
||||
|
|
@ -478,5 +479,4 @@ class PortalSessionHandler(SessionHandler):
|
|||
|
||||
|
||||
_PORTAL_SESSION_HANDLER_CLASS = class_from_module(settings.PORTAL_SESSION_HANDLER_CLASS)
|
||||
|
||||
PORTAL_SESSIONS = _PORTAL_SESSION_HANDLER_CLASS()
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ class RSSBotFactory(object):
|
|||
def start(self):
|
||||
"""
|
||||
Called by portalsessionhandler. Starts the bot.
|
||||
|
||||
"""
|
||||
|
||||
def errback(fail):
|
||||
|
|
|
|||
|
|
@ -61,24 +61,25 @@ CTRL_D = "\x04"
|
|||
CTRL_BACKSLASH = "\x1c"
|
||||
CTRL_L = "\x0c"
|
||||
|
||||
_NO_AUTOGEN = """
|
||||
_NO_AUTOGEN = f"""
|
||||
Evennia could not generate SSH private- and public keys ({{err}})
|
||||
Using conch default keys instead.
|
||||
|
||||
If this error persists, create the keys manually (using the tools for your OS)
|
||||
and put them here:
|
||||
{}
|
||||
{}
|
||||
""".format(
|
||||
_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
|
||||
)
|
||||
{_PRIVATE_KEY_FILE}
|
||||
{_PUBLIC_KEY_FILE}
|
||||
"""
|
||||
|
||||
_BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
||||
|
||||
|
||||
# not used atm
|
||||
class SSHServerFactory(protocol.ServerFactory):
|
||||
"This is only to name this better in logs"
|
||||
"""
|
||||
This is only to name this better in logs
|
||||
|
||||
"""
|
||||
noisy = False
|
||||
|
||||
def logPrefix(self):
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ class SSLProtocol(_TELNET_PROTOCOL_CLASS):
|
|||
"""
|
||||
Communication is the same as telnet, except data transfer
|
||||
is done with encryption.
|
||||
|
||||
"""
|
||||
|
||||
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
|
||||
directory. If files ssl.key and ssl.cert does not exist, they
|
||||
are created.
|
||||
|
||||
"""
|
||||
|
||||
if not (os.path.exists(keyfile) and os.path.exists(certfile)):
|
||||
|
|
@ -74,10 +76,11 @@ def verify_SSL_key_and_cert(keyfile, certfile):
|
|||
|
||||
try:
|
||||
# create the RSA key and store it.
|
||||
KEY_LENGTH = 1024
|
||||
rsaKey = Key(RSA.generate(KEY_LENGTH))
|
||||
keyString = rsaKey.toString(type="OPENSSH")
|
||||
file(keyfile, "w+b").write(keyString)
|
||||
KEY_LENGTH = 2048
|
||||
rsa_key = Key(RSA.generate(KEY_LENGTH))
|
||||
key_string = rsa_key.toString(type="OPENSSH")
|
||||
with open(keyfile, "w+b") as fil:
|
||||
fil.write(key_string)
|
||||
except Exception as err:
|
||||
print(NO_AUTOGEN.format(err=err, keyfile=keyfile))
|
||||
sys.exit(5)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,10 @@ _BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
|||
|
||||
|
||||
class TelnetServerFactory(protocol.ServerFactory):
|
||||
"This is only to name this better in logs"
|
||||
"""
|
||||
This exists only to name this better in logs.
|
||||
|
||||
"""
|
||||
noisy = False
|
||||
|
||||
def logPrefix(self):
|
||||
|
|
@ -71,6 +74,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
Each player connecting over telnet (ie using most traditional mud
|
||||
clients) gets a telnet protocol instance assigned to them. All
|
||||
communication between game and player goes through here.
|
||||
|
||||
"""
|
||||
|
||||
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
|
||||
of incoming data.
|
||||
|
||||
"""
|
||||
# print(f"telnet dataReceived: {data}")
|
||||
try:
|
||||
|
|
@ -145,11 +150,15 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
Client refuses do(linemode). This is common for MUD-specific
|
||||
clients, but we must ask for the sake of raw telnet. We ignore
|
||||
this error.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
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"):
|
||||
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
|
||||
can't even handle a NOP instruction. This is turned off by the
|
||||
protocol_flag NOPKEEPALIVE (settable e.g. by the default
|
||||
`@option` command).
|
||||
`option` command).
|
||||
|
||||
"""
|
||||
if self.nop_keep_alive and self.nop_keep_alive.running:
|
||||
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.
|
||||
The system will force-call this sync after a small time to handle
|
||||
clients that don't reply to handshakes at all.
|
||||
|
||||
"""
|
||||
if timeout:
|
||||
if self.handshakes > 0:
|
||||
|
|
@ -186,6 +197,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
def at_login(self):
|
||||
"""
|
||||
Called when this session gets authenticated by the server.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
|
@ -321,7 +333,10 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
self.data_in(text=dat + b"\n")
|
||||
|
||||
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")
|
||||
super()._write(mccp_compress(self, data))
|
||||
|
||||
|
|
@ -347,7 +362,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
|
@ -376,6 +391,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
|
||||
Keyword Args:
|
||||
kwargs (any): Options to the protocol
|
||||
|
||||
"""
|
||||
self.sessionhandler.data_out(self, **kwargs)
|
||||
|
||||
|
|
@ -442,7 +458,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
|||
prompt = mxp_parse(prompt)
|
||||
prompt = to_bytes(prompt, self)
|
||||
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)):
|
||||
prompt += IAC + GA
|
||||
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):
|
||||
"""
|
||||
Send other oob data
|
||||
|
||||
"""
|
||||
if not cmdname == "options":
|
||||
self.oob.data_out(cmdname, *args, **kwargs)
|
||||
|
|
|
|||
|
|
@ -46,32 +46,29 @@ _CERTIFICATE_ISSUER = {
|
|||
|
||||
# messages
|
||||
|
||||
NO_AUTOGEN = """
|
||||
NO_AUTOGEN = f"""
|
||||
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
|
||||
should be placed and named like this:
|
||||
{}
|
||||
{}
|
||||
""".format(
|
||||
_PRIVATE_KEY_FILE, _PUBLIC_KEY_FILE
|
||||
)
|
||||
{_PRIVATE_KEY_FILE}
|
||||
{_PUBLIC_KEY_FILE}
|
||||
"""
|
||||
|
||||
NO_AUTOCERT = """
|
||||
Evennia's could not auto-generate the SSL certificate ({{err}}).
|
||||
The private key already exists here:
|
||||
{}
|
||||
{_PRIVATE_KEY_FILE}
|
||||
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:
|
||||
{}
|
||||
""".format(
|
||||
_PRIVATE_KEY_FILE, _CERTIFICATE_FILE
|
||||
)
|
||||
{_CERTIFICATE_FILE}
|
||||
"""
|
||||
|
||||
|
||||
class SSLProtocol(TelnetProtocol):
|
||||
"""
|
||||
Communication is the same as telnet, except data transfer
|
||||
is done with encryption set up by the portal at start time.
|
||||
|
||||
"""
|
||||
|
||||
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):
|
||||
"""
|
||||
Implements the server-side of the Websocket connection.
|
||||
|
||||
"""
|
||||
|
||||
# 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.
|
||||
#
|
||||
# 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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ http://localhost:4001/webclient.)
|
|||
The WebClient resource in this module will
|
||||
handle these requests and act as a gateway
|
||||
to sessions connected over the webclient.
|
||||
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
|
|
@ -27,7 +28,7 @@ from django.utils.functional import Promise
|
|||
from django.conf import settings
|
||||
from evennia.utils.ansi import parse_ansi
|
||||
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.server import session
|
||||
|
||||
|
|
@ -223,10 +224,13 @@ class AjaxWebClient(resource.Resource):
|
|||
return jsonify({"msg": host_string, "csessid": csessid})
|
||||
|
||||
def mode_keepalive(self, request):
|
||||
|
||||
"""
|
||||
This is called by render_POST when the
|
||||
client is replying to the keepalive.
|
||||
|
||||
Args:
|
||||
request (Request): Incoming request.
|
||||
|
||||
"""
|
||||
csessid = self.get_client_sessid(request)
|
||||
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
|
||||
tracks and interlinks all the twisted network services that make up
|
||||
evennia.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, application):
|
||||
|
|
@ -243,6 +244,7 @@ class Evennia:
|
|||
This allows for changing default cmdset locations and default
|
||||
typeclasses in the settings file and have them auto-update all
|
||||
already existing objects.
|
||||
|
||||
"""
|
||||
global INFO_DICT
|
||||
|
||||
|
|
@ -471,7 +473,10 @@ class Evennia:
|
|||
ServerConfig.objects.conf("runtime", _GAMETIME_MODULE.runtime())
|
||||
|
||||
def get_info_dict(self):
|
||||
"Return the server info, for display."
|
||||
"""
|
||||
Return the server info, for display.
|
||||
|
||||
"""
|
||||
return INFO_DICT
|
||||
|
||||
# server start/stop hooks
|
||||
|
|
@ -480,6 +485,7 @@ class Evennia:
|
|||
"""
|
||||
This is called every time the server starts up, regardless of
|
||||
how it was shut down.
|
||||
|
||||
"""
|
||||
if SERVER_STARTSTOP_MODULE:
|
||||
SERVER_STARTSTOP_MODULE.at_server_start()
|
||||
|
|
@ -488,6 +494,7 @@ class Evennia:
|
|||
"""
|
||||
This is called just before a server is shut down, regardless
|
||||
of it is fore a reload, reset or shutdown.
|
||||
|
||||
"""
|
||||
if SERVER_STARTSTOP_MODULE:
|
||||
SERVER_STARTSTOP_MODULE.at_server_stop()
|
||||
|
|
@ -495,6 +502,7 @@ class Evennia:
|
|||
def at_server_reload_start(self):
|
||||
"""
|
||||
This is called only when server starts back up after a reload.
|
||||
|
||||
"""
|
||||
if SERVER_STARTSTOP_MODULE:
|
||||
SERVER_STARTSTOP_MODULE.at_server_reload_start()
|
||||
|
|
@ -505,7 +513,7 @@ class Evennia:
|
|||
after reconnecting.
|
||||
|
||||
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):
|
||||
"""
|
||||
This is called only time the server stops before a reload.
|
||||
|
||||
"""
|
||||
if SERVER_STARTSTOP_MODULE:
|
||||
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
|
||||
shutdown or a reset.
|
||||
|
||||
"""
|
||||
# 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.
|
||||
|
|
@ -590,6 +600,7 @@ class Evennia:
|
|||
def at_server_cold_stop(self):
|
||||
"""
|
||||
This is called only when the server goes down due to a shutdown or reset.
|
||||
|
||||
"""
|
||||
if SERVER_STARTSTOP_MODULE:
|
||||
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.utils import make_iter, lazy_property, class_from_module
|
||||
from evennia.commands.cmdsethandler import CmdSetHandler
|
||||
from evennia.server.session import Session
|
||||
from evennia.scripts.monitorhandler import MONITOR_HANDLER
|
||||
from evennia.typeclasses.attributes import AttributeHandler, InMemoryAttributeBackend, DbHolder
|
||||
|
||||
|
|
@ -22,9 +21,6 @@ _SA = object.__setattr__
|
|||
_ObjectDB = None
|
||||
_ANSI = None
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_BASE_SESSION_CLASS = class_from_module(settings.BASE_SESSION_CLASS)
|
||||
|
||||
|
||||
|
|
@ -45,7 +41,10 @@ class ServerSession(_BASE_SESSION_CLASS):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initiate to avoid AttributeErrors down the line"""
|
||||
"""
|
||||
Initiate to avoid AttributeErrors down the line
|
||||
|
||||
"""
|
||||
self.puppet = None
|
||||
self.account = None
|
||||
self.cmdset_storage_string = ""
|
||||
|
|
@ -321,7 +320,10 @@ class ServerSession(_BASE_SESSION_CLASS):
|
|||
self.sessionhandler.data_in(session or self, **kwargs)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""Handle session comparisons"""
|
||||
"""
|
||||
Handle session comparisons
|
||||
|
||||
"""
|
||||
try:
|
||||
return self.address == other.address
|
||||
except AttributeError:
|
||||
|
|
@ -368,6 +370,7 @@ class ServerSession(_BASE_SESSION_CLASS):
|
|||
def at_cmdset_get(self, **kwargs):
|
||||
"""
|
||||
A dummy hook all objects with cmdsets need to have
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
|
@ -414,7 +417,10 @@ class ServerSession(_BASE_SESSION_CLASS):
|
|||
|
||||
# @ndb.deleter
|
||||
def ndb_del(self):
|
||||
"""Stop accidental deletion."""
|
||||
"""
|
||||
Stop accidental deletion.
|
||||
|
||||
"""
|
||||
raise Exception("Cannot delete the ndb object!")
|
||||
|
||||
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
|
||||
# at this stage, so we just present a uniform API)
|
||||
def access(self, *args, **kwargs):
|
||||
"""Dummy method to mimic the logged-in API."""
|
||||
"""
|
||||
Dummy method to mimic the logged-in API.
|
||||
|
||||
"""
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from django.conf import settings
|
|||
from evennia.commands.cmdhandler import CMD_LOGINSTART
|
||||
from evennia.utils.logger import log_trace
|
||||
from evennia.utils.utils import (
|
||||
variable_from_module, class_from_module,
|
||||
is_iter,
|
||||
make_iter,
|
||||
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_FIRST_LOGIN, SIGNAL_ACCOUNT_POST_LAST_LOGOUT
|
||||
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
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ _ServerConfig = None
|
|||
_ScriptDB = 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):
|
||||
|
|
@ -48,9 +48,6 @@ class DummySession(object):
|
|||
|
||||
DUMMYSESSION = DummySession()
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_SERVERNAME = settings.SERVERNAME
|
||||
_MULTISESSION_MODE = settings.MULTISESSION_MODE
|
||||
_IDLE_TIMEOUT = settings.IDLE_TIMEOUT
|
||||
|
|
@ -61,7 +58,6 @@ _MODEL_MAP = None
|
|||
_FUNCPARSER = None
|
||||
|
||||
|
||||
|
||||
# input handlers
|
||||
|
||||
_INPUT_FUNCS = {}
|
||||
|
|
@ -103,24 +99,36 @@ class SessionHandler(dict):
|
|||
"""
|
||||
|
||||
def __getitem__(self, key):
|
||||
"Clean out None-sessions automatically."
|
||||
"""
|
||||
Clean out None-sessions automatically.
|
||||
|
||||
"""
|
||||
if None in self:
|
||||
del self[None]
|
||||
return super().__getitem__(key)
|
||||
|
||||
def get(self, key, default=None):
|
||||
"Clean out None-sessions automatically."
|
||||
"""
|
||||
Clean out None-sessions automatically.
|
||||
|
||||
"""
|
||||
if None in self:
|
||||
del self[None]
|
||||
return super().get(key, default)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"Don't assign None sessions"
|
||||
"""
|
||||
Don't assign None sessions"
|
||||
|
||||
"""
|
||||
if key is not None:
|
||||
super().__setitem__(key, value)
|
||||
|
||||
def __contains__(self, key):
|
||||
"None-keys are not accepted."
|
||||
"""
|
||||
None-keys are not accepted.
|
||||
|
||||
"""
|
||||
return False if key is None else super().__contains__(key)
|
||||
|
||||
def get_sessions(self, include_unloggedin=False):
|
||||
|
|
@ -158,9 +166,8 @@ class SessionHandler(dict):
|
|||
|
||||
Args:
|
||||
session (Session): The relevant session instance.
|
||||
kwargs (dict) Each keyword represents a send-instruction, with the keyword itself being the name
|
||||
of the instruction (like "text"). Suitable values for each
|
||||
keyword are:
|
||||
kwargs (dict) Each keyword represents a send-instruction, with the keyword itself being
|
||||
the name of the instruction (like "text"). Suitable values for each keyword are:
|
||||
- arg -> [[arg], {}]
|
||||
- [args] -> [[args], {}]
|
||||
- {kwargs} -> [[], {kwargs}]
|
||||
|
|
@ -177,7 +184,8 @@ class SessionHandler(dict):
|
|||
global _FUNCPARSER
|
||||
if not _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 {}
|
||||
raw = options.get("raw", False)
|
||||
|
|
@ -199,7 +207,10 @@ class SessionHandler(dict):
|
|||
return 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):
|
||||
newdict = {}
|
||||
for key, part in data.items():
|
||||
|
|
@ -210,7 +221,8 @@ class SessionHandler(dict):
|
|||
elif isinstance(data, (str, bytes)):
|
||||
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->)
|
||||
# data = parse_inlinefunc(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):
|
||||
"""
|
||||
This object holds the stack of sessions active in the game at
|
||||
any time.
|
||||
This object holds the stack of sessions active in the game at any time.
|
||||
|
||||
A session register with the handler in two steps, first by
|
||||
registering itself with the connect() method. This indicates an
|
||||
non-authenticated session. Whenever the session is authenticated
|
||||
the session together with the related account is sent to the login()
|
||||
method.
|
||||
A session register with the handler in two steps, first by registering itself with the connect()
|
||||
method. This indicates an non-authenticated session. Whenever the session is authenticated 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):
|
||||
"""
|
||||
Log in the previously unloggedin session and the account we by
|
||||
now should know is connected to it. After this point we assume
|
||||
the session to be logged in one way or another.
|
||||
Log in the previously unloggedin session and the account we by now should know is connected
|
||||
to it. After this point we assume the session to be logged in one way or another.
|
||||
|
||||
Args:
|
||||
session (Session): The Session to authenticate.
|
||||
|
|
@ -627,7 +635,8 @@ class ServerSessionHandler(SessionHandler):
|
|||
# mean connecting from the same host would not catch duplicates
|
||||
sid = id(curr_session)
|
||||
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:
|
||||
|
|
@ -737,8 +746,8 @@ class ServerSessionHandler(SessionHandler):
|
|||
puppet (Object): Object puppeted
|
||||
|
||||
Returns.
|
||||
sessions (Session or list): Can be more than one of Object is controlled by
|
||||
more than one Session (MULTISESSION_MODE > 1).
|
||||
sessions (Session or list): Can be more than one of Object is controlled by more than
|
||||
one Session (MULTISESSION_MODE > 1).
|
||||
|
||||
"""
|
||||
sessions = puppet.sessid.get()
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ from django.core.cache import caches
|
|||
from collections import deque
|
||||
from evennia.utils import logger
|
||||
import time
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
|
||||
class Throttle(object):
|
||||
class Throttle:
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
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):
|
||||
"""
|
||||
|
|
@ -34,7 +35,7 @@ class Throttle(object):
|
|||
"""
|
||||
try:
|
||||
self.storage = caches['throttle']
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
logger.log_trace("Throttle: Errors encountered; using default cache.")
|
||||
self.storage = caches['default']
|
||||
|
||||
|
|
@ -123,7 +124,10 @@ class Throttle(object):
|
|||
|
||||
# If this makes it engage, log a single activation event
|
||||
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)
|
||||
|
||||
|
|
@ -136,14 +140,15 @@ class Throttle(object):
|
|||
|
||||
"""
|
||||
exists = self.get(ip)
|
||||
if not exists: return False
|
||||
if not exists:
|
||||
return False
|
||||
|
||||
cache_key = self.get_cache_key(ip)
|
||||
self.storage.delete(cache_key)
|
||||
self.unrecord_ip(ip)
|
||||
|
||||
# Return True if NOT exists
|
||||
return ~bool(self.get(ip))
|
||||
return not bool(self.get(ip))
|
||||
|
||||
def record_ip(self, ip, *args, **kwargs):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ which is a non-db version of Attributes.
|
|||
"""
|
||||
import re
|
||||
import fnmatch
|
||||
import weakref
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
|
|
@ -117,6 +116,7 @@ class IAttribute:
|
|||
class InMemoryAttribute(IAttribute):
|
||||
"""
|
||||
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.
|
||||
|
|
@ -126,11 +126,12 @@ class InMemoryAttribute(IAttribute):
|
|||
Create an Attribute that exists only in Memory.
|
||||
|
||||
Args:
|
||||
pk (int): This is a fake 'primary key' / id-field. It doesn't actually have to be unique, but is fed an
|
||||
incrementing number from the InMemoryBackend by default. This is needed only so Attributes can be
|
||||
sorted. Some parts of the API also see the lack of a .pk field as a sign that the Attribute was
|
||||
deleted.
|
||||
pk (int): This is a fake 'primary key' / id-field. It doesn't actually have to be
|
||||
unique, but is fed an incrementing number from the InMemoryBackend by default. This
|
||||
is needed only so Attributes can be sorted. Some parts of the API also see the lack
|
||||
of a .pk field as a sign that the Attribute was deleted.
|
||||
**kwargs: Other keyword arguments are used to construct the actual Attribute.
|
||||
|
||||
"""
|
||||
self.id = pk
|
||||
self.pk = pk
|
||||
|
|
@ -245,8 +246,8 @@ class Attribute(IAttribute, SharedMemoryModel):
|
|||
lock_storage = property(__lock_storage_get, __lock_storage_set, __lock_storage_del)
|
||||
|
||||
# value property (wraps db_value)
|
||||
# @property
|
||||
def __value_get(self):
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
Getter. Allows for `value = self.value`.
|
||||
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)
|
||||
|
||||
# @value.setter
|
||||
def __value_set(self, new_value):
|
||||
@value.setter
|
||||
def value(self, new_value):
|
||||
"""
|
||||
Setter. Allows for self.value = value. We cannot cache here,
|
||||
see self.__value_get.
|
||||
|
|
@ -264,14 +265,11 @@ class Attribute(IAttribute, SharedMemoryModel):
|
|||
self.db_value = to_pickle(new_value)
|
||||
self.save(update_fields=["db_value"])
|
||||
|
||||
# @value.deleter
|
||||
def __value_del(self):
|
||||
@value.deleter
|
||||
def value(self):
|
||||
"""Deleter. Allows for del attr.value. This removes the entire attribute."""
|
||||
self.delete()
|
||||
|
||||
value = property(__value_get, __value_set, __value_del)
|
||||
|
||||
|
||||
#
|
||||
# Handlers making use of the Attribute model
|
||||
#
|
||||
|
|
@ -348,7 +346,7 @@ class IAttributeBackend:
|
|||
|
||||
def _get_cache_key(self, key, category):
|
||||
"""
|
||||
|
||||
Fetch cache key.
|
||||
|
||||
Args:
|
||||
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):
|
||||
"""
|
||||
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
|
||||
call self.set_cache(key, category, new_attrobj)
|
||||
|
|
@ -714,7 +713,8 @@ class IAttributeBackend:
|
|||
]
|
||||
)
|
||||
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.reset_cache()
|
||||
|
||||
|
|
@ -735,10 +735,10 @@ class IAttributeBackend:
|
|||
|
||||
class InMemoryAttributeBackend(IAttributeBackend):
|
||||
"""
|
||||
This Backend for Attributes stores NOTHING in the database. Everything is kept in memory, and normally lost
|
||||
on a crash, reload, shared memory flush, etc. It generates IDs for the Attributes it manages, but these are
|
||||
of little importance beyond sorting and satisfying the caching logic to know an Attribute hasn't been
|
||||
deleted out from under the cache's nose.
|
||||
This Backend for Attributes stores NOTHING in the database. Everything is kept in memory, and
|
||||
normally lost on a crash, reload, shared memory flush, etc. It generates IDs for the Attributes
|
||||
it manages, but these are of little importance beyond sorting and satisfying the caching logic
|
||||
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):
|
||||
"""
|
||||
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:
|
||||
attr (IAttribute): The attribute to delete.
|
||||
|
|
@ -929,8 +930,9 @@ class AttributeHandler:
|
|||
Setup the AttributeHandler.
|
||||
|
||||
Args:
|
||||
obj (TypedObject): An Account, Object, Channel, ServerSession (not technically a typed object), etc.
|
||||
backend_class (IAttributeBackend class): The class of the backend to use.
|
||||
obj (TypedObject): An Account, Object, Channel, ServerSession (not technically a typed
|
||||
object), etc. backend_class (IAttributeBackend class): The class of the backend to
|
||||
use.
|
||||
"""
|
||||
self.obj = obj
|
||||
self.backend = backend_class(self, self._attrtype)
|
||||
|
|
@ -1263,6 +1265,7 @@ class DbHolder:
|
|||
all = property(get_all)
|
||||
|
||||
|
||||
#
|
||||
# Nick templating
|
||||
#
|
||||
|
||||
|
|
@ -1282,7 +1285,7 @@ This happens in two steps:
|
|||
|
||||
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)
|
||||
* 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
|
||||
# attach the line-break/ending extras all regexes require.
|
||||
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))
|
||||
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -595,13 +595,21 @@ class TypeclassManager(TypedObjectManager):
|
|||
Search by supplying a string with optional extra search criteria to aid the query.
|
||||
|
||||
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:
|
||||
matches (queryset): A queryset result matching all queries exactly. If wanting to use spaces or
|
||||
==, != in tags or attributes, enclose them in quotes.
|
||||
matches (queryset): A queryset result matching all queries exactly. If wanting to use
|
||||
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:
|
||||
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):
|
||||
"""
|
||||
Receives a signal just after the object is saved.
|
||||
|
||||
"""
|
||||
if created:
|
||||
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):
|
||||
"""
|
||||
Wipe object's Attributes when it's deleted
|
||||
|
||||
"""
|
||||
instance.db_attributes.all().delete()
|
||||
|
||||
|
|
@ -98,6 +100,7 @@ class TypeclassBase(SharedMemoryModelBase):
|
|||
Metaclass which should be set for the root of model proxies
|
||||
that don't define any new fields, like Object, Script etc. This
|
||||
is the basis for the typeclassing system.
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, name, bases, attrs):
|
||||
|
|
@ -208,7 +211,8 @@ class TypedObject(SharedMemoryModel):
|
|||
"typeclass",
|
||||
max_length=255,
|
||||
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,
|
||||
)
|
||||
# Creation date. This is not changed once the object is created.
|
||||
|
|
@ -217,16 +221,20 @@ class TypedObject(SharedMemoryModel):
|
|||
db_lock_storage = models.TextField(
|
||||
"locks",
|
||||
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
|
||||
db_attributes = models.ManyToManyField(
|
||||
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(
|
||||
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
|
||||
|
|
@ -701,8 +709,8 @@ class TypedObject(SharedMemoryModel):
|
|||
# Attribute storage
|
||||
#
|
||||
|
||||
# @property db
|
||||
def __db_get(self):
|
||||
@property
|
||||
def db(self):
|
||||
"""
|
||||
Attribute handler wrapper. Allows for the syntax
|
||||
|
||||
|
|
@ -725,26 +733,24 @@ class TypedObject(SharedMemoryModel):
|
|||
self._db_holder = DbHolder(self, "attributes")
|
||||
return self._db_holder
|
||||
|
||||
# @db.setter
|
||||
def __db_set(self, value):
|
||||
@db.setter
|
||||
def db(self, value):
|
||||
"Stop accidentally replacing the db object"
|
||||
string = "Cannot assign directly to db object! "
|
||||
string += "Use db.attr=value instead."
|
||||
raise Exception(string)
|
||||
|
||||
# @db.deleter
|
||||
def __db_del(self):
|
||||
@db.deleter
|
||||
def db(self):
|
||||
"Stop accidental deletion."
|
||||
raise Exception("Cannot delete the db object!")
|
||||
|
||||
db = property(__db_get, __db_set, __db_del)
|
||||
|
||||
#
|
||||
# Non-persistent (ndb) storage
|
||||
#
|
||||
|
||||
# @property ndb
|
||||
def __ndb_get(self):
|
||||
@property
|
||||
def ndb(self):
|
||||
"""
|
||||
A non-attr_obj store (ndb: NonDataBase). Everything stored
|
||||
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")
|
||||
return self._ndb_holder
|
||||
|
||||
# @db.setter
|
||||
def __ndb_set(self, value):
|
||||
@ndb.setter
|
||||
def ndb(self, value):
|
||||
"Stop accidentally replacing the ndb object"
|
||||
string = "Cannot assign directly to ndb object! "
|
||||
string += "Use ndb.attr=value instead."
|
||||
raise Exception(string)
|
||||
|
||||
# @db.deleter
|
||||
def __ndb_del(self):
|
||||
@ndb.deleter
|
||||
def ndb(self):
|
||||
"Stop accidental deletion."
|
||||
raise Exception("Cannot delete the ndb object!")
|
||||
|
||||
ndb = property(__ndb_get, __ndb_set, __ndb_del)
|
||||
|
||||
def get_display_name(self, looker, **kwargs):
|
||||
"""
|
||||
Displays the name of the object in a viewer-aware manner.
|
||||
|
|
@ -879,7 +883,7 @@ class TypedObject(SharedMemoryModel):
|
|||
"""
|
||||
try:
|
||||
return reverse("%s-create" % slugify(cls._meta.verbose_name))
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
def web_get_detail_url(self):
|
||||
|
|
@ -919,7 +923,7 @@ class TypedObject(SharedMemoryModel):
|
|||
"%s-detail" % slugify(self._meta.verbose_name),
|
||||
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
def web_get_puppet_url(self):
|
||||
|
|
@ -931,19 +935,17 @@ class TypedObject(SharedMemoryModel):
|
|||
str: URI path to object puppet page, if defined.
|
||||
|
||||
Examples:
|
||||
::
|
||||
|
||||
```python
|
||||
Oscar (Character) = '/characters/oscar/1/puppet/'
|
||||
```
|
||||
|
||||
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
|
||||
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/$',
|
||||
CharPuppetView.as_view(), name='character-puppet')
|
||||
```
|
||||
url(r'characters/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/puppet/$',
|
||||
CharPuppetView.as_view(), name='character-puppet')
|
||||
|
||||
If no View has been created and defined in urls.py, returns an
|
||||
HTML anchor.
|
||||
|
|
@ -959,7 +961,7 @@ class TypedObject(SharedMemoryModel):
|
|||
"%s-puppet" % slugify(self._meta.verbose_name),
|
||||
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
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
|
||||
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.
|
||||
::
|
||||
|
||||
```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')
|
||||
```
|
||||
|
||||
If no View has been created and defined in urls.py, returns an
|
||||
HTML anchor.
|
||||
|
|
@ -999,7 +1000,7 @@ class TypedObject(SharedMemoryModel):
|
|||
"%s-update" % slugify(self._meta.verbose_name),
|
||||
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
def web_get_delete_url(self):
|
||||
|
|
@ -1019,11 +1020,10 @@ class TypedObject(SharedMemoryModel):
|
|||
somewhere in urls.py that follows the format 'modelname-action', so
|
||||
in this case a named view of 'character-detail' would be referenced
|
||||
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')
|
||||
```
|
||||
|
||||
If no View has been created and defined in urls.py, returns an HTML
|
||||
anchor.
|
||||
|
|
@ -1039,7 +1039,7 @@ class TypedObject(SharedMemoryModel):
|
|||
"%s-delete" % slugify(self._meta.verbose_name),
|
||||
kwargs={"pk": self.pk, "slug": slugify(self.name)},
|
||||
)
|
||||
except:
|
||||
except Exception:
|
||||
return "#"
|
||||
|
||||
# Used by Django Sites/Admin
|
||||
|
|
|
|||
|
|
@ -125,7 +125,10 @@ class TagHandler(object):
|
|||
self._cache_complete = False
|
||||
|
||||
def _query_all(self):
|
||||
"Get all tags for this objects"
|
||||
"""
|
||||
Get all tags for this object.
|
||||
|
||||
"""
|
||||
query = {
|
||||
"%s__id" % self._model: self._objid,
|
||||
"tag__db_model": self._model,
|
||||
|
|
@ -137,7 +140,10 @@ class TagHandler(object):
|
|||
]
|
||||
|
||||
def _fullcache(self):
|
||||
"Cache all tags of this object"
|
||||
"""
|
||||
Cache all tags of this object.
|
||||
|
||||
"""
|
||||
if not _TYPECLASS_AGGRESSIVE_CACHE:
|
||||
return
|
||||
tags = self._query_all()
|
||||
|
|
@ -277,6 +283,7 @@ class TagHandler(object):
|
|||
def reset_cache(self):
|
||||
"""
|
||||
Reset the cache from the outside.
|
||||
|
||||
"""
|
||||
self._cache_complete = False
|
||||
self._cache = {}
|
||||
|
|
@ -483,8 +490,9 @@ class TagHandler(object):
|
|||
Batch-add tags from a list of tuples.
|
||||
|
||||
Args:
|
||||
*args (tuple or str): Each argument should be a `tagstr` keys or tuple `(keystr, category)` or
|
||||
`(keystr, category, data)`. It's possible to mix input types.
|
||||
*args (tuple or str): Each argument should be a `tagstr` keys or tuple
|
||||
`(keystr, category)` or `(keystr, category, data)`. It's possible to mix input
|
||||
types.
|
||||
|
||||
Notes:
|
||||
This will generate a mimimal number of self.add calls,
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ Supported standards:
|
|||
|
||||
## Markup
|
||||
|
||||
ANSI colors: `r` ed, `g` reen, `y` ellow, `b` lue, `m` agenta, `c` yan, `n` ormal (no color). Capital
|
||||
letters indicate the 'dark' variant.
|
||||
ANSI colors: `r` ed, `g` reen, `y` ellow, `b` lue, `m` agenta, `c` yan, `n` ormal (no color).
|
||||
Capital letters indicate the 'dark' variant.
|
||||
|
||||
- `|r` fg bright red
|
||||
- `|R` fg dark red
|
||||
|
|
@ -337,8 +337,9 @@ class ANSIParser(object):
|
|||
colval = 16 + (red * 36) + (green * 6) + blue
|
||||
|
||||
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.
|
||||
# return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10)
|
||||
# replaced since some clients (like Potato) does not accept codes with leading zeroes,
|
||||
# see issue #1024.
|
||||
# return "\033[%s8;5;%s%s%sm" % (3 + int(background), colval // 100, (colval % 100) // 10, colval%10) # noqa
|
||||
|
||||
else:
|
||||
# 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(
|
||||
r"(?i)(?P<just>(?P<fill>.)?(?P<align>\<|\>|\=|\^))?(?P<sign>\+|\-| )?(?P<alt>\#)?"
|
||||
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.
|
||||
|
||||
Args:
|
||||
format_spec (str): The format specification passed by f-string or str.format(). This is a string such as
|
||||
"0<30" which would mean "left justify to 30, filling with zeros". The full specification can be found
|
||||
at https://docs.python.org/3/library/string.html#formatspec
|
||||
format_spec (str): The format specification passed by f-string or str.format(). This is
|
||||
a string such as "0<30" which would mean "left justify to 30, filling with zeros".
|
||||
The full specification can be found at
|
||||
https://docs.python.org/3/library/string.html#formatspec
|
||||
|
||||
Returns:
|
||||
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.
|
||||
# It returns a dictionary.
|
||||
|
|
@ -1067,7 +1071,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
current_index = 0
|
||||
result = tuple()
|
||||
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)
|
||||
return result
|
||||
|
||||
|
|
@ -1187,7 +1191,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
start = next + bylen
|
||||
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:
|
||||
return [part for part in res if part != ""]
|
||||
return res
|
||||
|
|
@ -1230,7 +1234,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
if next < 0:
|
||||
break
|
||||
# Get character codes after the index as well.
|
||||
res.append(self[next + bylen : end])
|
||||
res.append(self[next + bylen: end])
|
||||
end = next
|
||||
maxsplit -= 1 # NB. if it's already < 0, it stays < 0
|
||||
|
||||
|
|
@ -1284,7 +1288,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
ic -= 1
|
||||
ir2 -= 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):
|
||||
"""
|
||||
|
|
@ -1403,7 +1407,7 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
start = None
|
||||
end = char._char_indexes[0]
|
||||
prefix = char._raw_string[start:end]
|
||||
postfix = char._raw_string[end + 1 :]
|
||||
postfix = char._raw_string[end + 1:]
|
||||
line = char._clean_string * amount
|
||||
code_indexes = [i for i in range(0, len(prefix))]
|
||||
length = len(prefix) + len(line)
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ class BatchCommandProcessor(object):
|
|||
try:
|
||||
path = match.group(1)
|
||||
return "\n#\n".join(self.parse_file(path))
|
||||
except IOError as err:
|
||||
except IOError:
|
||||
raise IOError("#INSERT {} failed.".format(path))
|
||||
|
||||
text = _RE_INSERT.sub(replace_insert, text)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ from evennia.utils import logger
|
|||
SCRIPTDB = None
|
||||
|
||||
|
||||
class Container(object):
|
||||
class Container:
|
||||
"""
|
||||
Base container class. A container is simply a storage object whose
|
||||
properties can be acquired as a property on it. This is generally
|
||||
|
|
@ -156,9 +156,8 @@ class GlobalScriptContainer(Container):
|
|||
return new_script
|
||||
|
||||
if ((found.interval != interval)
|
||||
or (found.start_delay != start_delay)
|
||||
or (found.repeats != repeats)
|
||||
):
|
||||
or (found.start_delay != start_delay)
|
||||
or (found.repeats != repeats)):
|
||||
# the setup changed
|
||||
found.start(interval=interval, start_delay=start_delay, repeats=repeats)
|
||||
if found.desc != desc:
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ except ImportError:
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
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
|
||||
|
||||
__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
|
||||
|
||||
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.ansi import raw
|
||||
from evennia.commands import cmdhandler
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
# we use cmdhandler instead of evennia.syscmdkeys to
|
||||
# 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.
|
||||
: <l> - 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>
|
||||
|
||||
:echo - turn echoing of the input on/off (helpful for some clients)
|
||||
"""
|
||||
""")
|
||||
|
||||
_HELP_LEGEND = """
|
||||
_HELP_LEGEND = _("""
|
||||
Legend:
|
||||
<l> - line number, like '5' or range, like '3:7'.
|
||||
<w> - a single word, or multiple words with quotes around them.
|
||||
<txt> - longer string, usually not needing quotes.
|
||||
"""
|
||||
""")
|
||||
|
||||
_HELP_CODE = """
|
||||
_HELP_CODE = _("""
|
||||
:! - Execute code buffer without saving
|
||||
:< - Decrease the level of automatic indentation for the next lines
|
||||
:> - Increase the level of automatic indentation for the next lines
|
||||
:= - Switch automatic indentation on/off
|
||||
""".lstrip(
|
||||
"\n"
|
||||
)
|
||||
))
|
||||
|
||||
_ERROR_LOADFUNC = """
|
||||
_ERROR_LOADFUNC = _("""
|
||||
{error}
|
||||
|
||||
|rBuffer load function error. Could not load initial data.|n
|
||||
"""
|
||||
""")
|
||||
|
||||
_ERROR_SAVEFUNC = """
|
||||
_ERROR_SAVEFUNC = _("""
|
||||
{error}
|
||||
|
||||
|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"
|
||||
_DEFAULT_NO_QUITFUNC = "Exited editor."
|
||||
_MSG_SAVE_NO_CHANGE = _("No changes need saving")
|
||||
_DEFAULT_NO_QUITFUNC = _("Exited editor.")
|
||||
|
||||
_ERROR_QUITFUNC = """
|
||||
_ERROR_QUITFUNC = _("""
|
||||
{error}
|
||||
|
||||
|rQuit function gave an error. Skipping.|n
|
||||
"""
|
||||
""")
|
||||
|
||||
_ERROR_PERSISTENT_SAVING = """
|
||||
_ERROR_PERSISTENT_SAVING = _("""
|
||||
{error}
|
||||
|
||||
|rThe editor state could not be saved for persistent mode. Switching
|
||||
to non-persistent mode (which means the editor session won't survive
|
||||
an eventual server reload - so save often!)|n
|
||||
"""
|
||||
""")
|
||||
|
||||
_TRACE_PERSISTENT_SAVING = (
|
||||
_TRACE_PERSISTENT_SAVING = _(
|
||||
"EvEditor persistent-mode error. Commonly, this is because one or "
|
||||
"more of the EvEditor callbacks could not be pickled, for example "
|
||||
"because it's a class method or is defined inside another function."
|
||||
)
|
||||
|
||||
|
||||
_MSG_NO_UNDO = "Nothing to undo."
|
||||
_MSG_NO_REDO = "Nothing to redo."
|
||||
_MSG_UNDO = "Undid one step."
|
||||
_MSG_REDO = "Redid one step."
|
||||
_MSG_NO_UNDO = _("Nothing to undo.")
|
||||
_MSG_NO_REDO = _("Nothing to redo.")
|
||||
_MSG_UNDO = _("Undid one step.")
|
||||
_MSG_REDO = _("Redid one step.")
|
||||
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
|
|
@ -180,7 +181,10 @@ class CmdSaveYesNo(_COMMAND_DEFAULT_CLASS):
|
|||
help_cateogory = "LineEditor"
|
||||
|
||||
def func(self):
|
||||
"""Implement the yes/no choice."""
|
||||
"""
|
||||
Implement the yes/no choice.
|
||||
|
||||
"""
|
||||
# this is only called from inside the lineeditor
|
||||
# so caller.ndb._lineditor must be set.
|
||||
|
||||
|
|
@ -195,7 +199,10 @@ class CmdSaveYesNo(_COMMAND_DEFAULT_CLASS):
|
|||
|
||||
|
||||
class SaveYesNoCmdSet(CmdSet):
|
||||
"""Stores the yesno question"""
|
||||
"""
|
||||
Stores the yesno question
|
||||
|
||||
"""
|
||||
|
||||
key = "quitsave_yesno"
|
||||
priority = 150 # override other cmdsets.
|
||||
|
|
@ -331,6 +338,7 @@ class CmdEditorBase(_COMMAND_DEFAULT_CLASS):
|
|||
def _load_editor(caller):
|
||||
"""
|
||||
Load persistent editor from storage.
|
||||
|
||||
"""
|
||||
saved_options = caller.attributes.get("_eveditor_saved")
|
||||
saved_buffer, saved_undo = caller.attributes.get("_eveditor_buffer_temp", (None, None))
|
||||
|
|
@ -356,6 +364,7 @@ def _load_editor(caller):
|
|||
class CmdLineInput(CmdEditorBase):
|
||||
"""
|
||||
No command match - Inputs line of text into buffer.
|
||||
|
||||
"""
|
||||
|
||||
key = _CMD_NOMATCH
|
||||
|
|
@ -440,6 +449,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
This command handles all the in-editor :-style commands. Since
|
||||
each command is small and very limited, this makes for a more
|
||||
efficient presentation.
|
||||
|
||||
"""
|
||||
caller = self.caller
|
||||
editor = caller.ndb._eveditor
|
||||
|
|
@ -467,7 +477,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
# Insert single colon alone on a line
|
||||
editor.update_buffer([":"] if lstart == 0 else linebuffer + [":"])
|
||||
if echo_mode:
|
||||
caller.msg("Single ':' added to buffer.")
|
||||
caller.msg(_("Single ':' added to buffer."))
|
||||
elif cmd == ":h":
|
||||
# help entry
|
||||
editor.display_help()
|
||||
|
|
@ -482,7 +492,7 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
# quit. If not saved, will ask
|
||||
if self.editor._unsaved:
|
||||
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:
|
||||
editor.quit()
|
||||
elif cmd == ":q!":
|
||||
|
|
@ -497,24 +507,24 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
elif cmd == ":UU":
|
||||
# reset 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":
|
||||
# :dd <l> - delete line <l>
|
||||
buf = linebuffer[:lstart] + linebuffer[lend:]
|
||||
editor.update_buffer(buf)
|
||||
caller.msg("Deleted %s." % self.lstr)
|
||||
caller.msg(_("Deleted {string}.").format(string= self.lstr))
|
||||
elif cmd == ":dw":
|
||||
# :dw <w> - delete word in entire buffer
|
||||
# :dw <l> <w> delete word only on line(s) <l>
|
||||
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:
|
||||
if not self.linerange:
|
||||
lstart = 0
|
||||
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:
|
||||
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 = re.sub(r"%s" % self.arg1.strip("'").strip('"'), "", sarea, re.MULTILINE)
|
||||
buf = linebuffer[:lstart] + sarea.split("\n") + linebuffer[lend:]
|
||||
|
|
@ -529,49 +539,49 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
editor._indent = 0
|
||||
if editor._persistent:
|
||||
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":
|
||||
# :y <l> - yank line(s) to copy buffer
|
||||
cbuf = linebuffer[lstart:lend]
|
||||
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":
|
||||
# :x <l> - cut line to copy buffer
|
||||
cbuf = linebuffer[lstart:lend]
|
||||
editor._copy_buffer = cbuf
|
||||
buf = linebuffer[:lstart] + linebuffer[lend:]
|
||||
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":
|
||||
# :p <l> paste line(s) from copy buffer
|
||||
if not editor._copy_buffer:
|
||||
caller.msg("Copy buffer is empty.")
|
||||
caller.msg(_("Copy buffer is empty."))
|
||||
else:
|
||||
buf = linebuffer[:lstart] + editor._copy_buffer + linebuffer[lstart:]
|
||||
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":
|
||||
# :i <l> <txt> - insert new line
|
||||
new_lines = self.args.split("\n")
|
||||
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:
|
||||
buf = linebuffer[:lstart] + new_lines + linebuffer[lstart:]
|
||||
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":
|
||||
# :r <l> <txt> - replace lines
|
||||
new_lines = self.args.split("\n")
|
||||
if not new_lines:
|
||||
caller.msg("You need to enter a replacement string.")
|
||||
caller.msg(_("You need to enter a replacement string."))
|
||||
else:
|
||||
buf = linebuffer[:lstart] + new_lines + linebuffer[lend:]
|
||||
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":
|
||||
# :I <l> <txt> - insert text at beginning of line(s) <l>
|
||||
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:
|
||||
buf = (
|
||||
linebuffer[:lstart]
|
||||
|
|
@ -579,11 +589,11 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
+ linebuffer[lend:]
|
||||
)
|
||||
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":
|
||||
# :A <l> <txt> - append text after end of line(s)
|
||||
if not self.args:
|
||||
caller.msg("You need to enter text to append.")
|
||||
caller.msg(_("You need to enter text to append."))
|
||||
else:
|
||||
buf = (
|
||||
linebuffer[:lstart]
|
||||
|
|
@ -591,23 +601,23 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
+ linebuffer[lend:]
|
||||
)
|
||||
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":
|
||||
# :s <li> <w> <txt> - search and replace words
|
||||
# in entire buffer or on certain lines
|
||||
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:
|
||||
if not self.linerange:
|
||||
lstart = 0
|
||||
lend = self.cline + 1
|
||||
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)
|
||||
)
|
||||
else:
|
||||
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])
|
||||
|
||||
|
|
@ -629,9 +639,9 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.linerange:
|
||||
lstart = 0
|
||||
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:
|
||||
caller.msg("Flood filled %s." % self.lstr)
|
||||
caller.msg(_("Flood filled %s.") % self.lstr)
|
||||
fbuf = "\n".join(linebuffer[lstart:lend])
|
||||
fbuf = fill(fbuf, width=width)
|
||||
buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:]
|
||||
|
|
@ -653,16 +663,17 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
width = _DEFAULT_WIDTH
|
||||
if self.arg1 and self.arg1.lower() not in align_map:
|
||||
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
|
||||
align = align_map[self.arg1.lower()] if self.arg1 else "f"
|
||||
if not self.linerange:
|
||||
lstart = 0
|
||||
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:
|
||||
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 = justify(jbuf, width=width, align=align)
|
||||
buf = linebuffer[:lstart] + jbuf.split("\n") + linebuffer[lend:]
|
||||
|
|
@ -673,9 +684,9 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.linerange:
|
||||
lstart = 0
|
||||
lend = self.cline + 1
|
||||
caller.msg("Indented lines %i-%i." % (lstart + 1, lend))
|
||||
caller.msg(_("Indented lines %i-%i.") % (lstart + 1, lend))
|
||||
else:
|
||||
caller.msg("Indented %s." % self.lstr)
|
||||
caller.msg(_("Indented %s.") % self.lstr)
|
||||
fbuf = [indent + line for line in linebuffer[lstart:lend]]
|
||||
buf = linebuffer[:lstart] + fbuf + linebuffer[lend:]
|
||||
editor.update_buffer(buf)
|
||||
|
|
@ -684,9 +695,9 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
if not self.linerange:
|
||||
lstart = 0
|
||||
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:
|
||||
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 = dedent(fbuf)
|
||||
buf = linebuffer[:lstart] + fbuf.split("\n") + linebuffer[lend:]
|
||||
|
|
@ -694,45 +705,45 @@ class CmdEditorGroup(CmdEditorBase):
|
|||
elif cmd == ":echo":
|
||||
# set echoing on/off
|
||||
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 == ":!":
|
||||
if editor._codefunc:
|
||||
editor._codefunc(caller, editor._buffer)
|
||||
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 == ":<":
|
||||
# :<
|
||||
if editor._codefunc:
|
||||
editor.decrease_indent()
|
||||
indent = editor._indent
|
||||
if indent >= 0:
|
||||
caller.msg("Decreased indentation: new indentation is {}.".format(indent))
|
||||
caller.msg(_("Decreased indentation: new indentation is {}.").format(indent))
|
||||
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:
|
||||
caller.msg("This command is only available in code editor mode.")
|
||||
caller.msg(_("This command is only available in code editor mode."))
|
||||
elif cmd == ":>":
|
||||
# :>
|
||||
if editor._codefunc:
|
||||
editor.increase_indent()
|
||||
indent = editor._indent
|
||||
if indent >= 0:
|
||||
caller.msg("Increased indentation: new indentation is {}.".format(indent))
|
||||
caller.msg(_("Increased indentation: new indentation is {}.").format(indent))
|
||||
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:
|
||||
caller.msg("This command is only available in code editor mode.")
|
||||
caller.msg(_("This command is only available in code editor mode."))
|
||||
elif cmd == ":=":
|
||||
# :=
|
||||
if editor._codefunc:
|
||||
editor.swap_autoindent()
|
||||
indent = editor._indent
|
||||
if indent >= 0:
|
||||
caller.msg("Auto-indentation turned on.")
|
||||
caller.msg(_("Auto-indentation turned on."))
|
||||
else:
|
||||
caller.msg("Auto-indentation turned off.")
|
||||
caller.msg(_("Auto-indentation turned off."))
|
||||
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):
|
||||
|
|
@ -879,12 +890,13 @@ class EvEditor(object):
|
|||
def load_buffer(self):
|
||||
"""
|
||||
Load the buffer using the load function hook.
|
||||
|
||||
"""
|
||||
try:
|
||||
self._buffer = self._loadfunc(self._caller)
|
||||
if not isinstance(self._buffer, str):
|
||||
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:
|
||||
from evennia.utils import logger
|
||||
|
||||
|
|
@ -1021,7 +1033,7 @@ class EvEditor(object):
|
|||
header = (
|
||||
"|n"
|
||||
+ sep * 10
|
||||
+ "Line Editor [%s]" % self._key
|
||||
+ _("Line Editor [%s]") % self._key
|
||||
+ sep * (_DEFAULT_WIDTH - 24 - len(self._key))
|
||||
)
|
||||
footer = (
|
||||
|
|
@ -1029,7 +1041,7 @@ class EvEditor(object):
|
|||
+ sep * 10
|
||||
+ "[l:%02i w:%03i c:%04i]" % (nlines, nwords, nchars)
|
||||
+ sep * 12
|
||||
+ "(:h for help)"
|
||||
+ _("(:h for help)")
|
||||
+ sep * (_DEFAULT_WIDTH - 54)
|
||||
)
|
||||
if linenums:
|
||||
|
|
|
|||
|
|
@ -415,7 +415,8 @@ class CmdEvMenuNode(Command):
|
|||
) # don't give the session as a kwarg here, direct to original
|
||||
raise EvMenuError(err)
|
||||
# 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
|
||||
# we have a menu, use it.
|
||||
menu.parse_input(self.raw_string)
|
||||
|
|
@ -619,7 +620,8 @@ class EvMenu:
|
|||
).intersection(set(kwargs.keys()))
|
||||
if reserved_clash:
|
||||
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():
|
||||
setattr(self, key, val)
|
||||
|
|
@ -1262,7 +1264,7 @@ class EvMenu:
|
|||
table.extend([" " for i in range(nrows - nlastcol)])
|
||||
|
||||
# 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
|
||||
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):
|
||||
"""
|
||||
Parse the select action
|
||||
|
||||
"""
|
||||
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
|
||||
selection = available_choices[index]
|
||||
except Exception:
|
||||
caller.msg("|rInvalid choice.|n")
|
||||
caller.msg(_("|rInvalid choice.|n"))
|
||||
else:
|
||||
if callable(select):
|
||||
try:
|
||||
|
|
@ -1388,7 +1391,7 @@ def list_node(option_generator, select=None, pagesize=10):
|
|||
if option_list:
|
||||
nall_options = len(option_list)
|
||||
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)
|
||||
|
||||
|
|
@ -1413,7 +1416,7 @@ def list_node(option_generator, select=None, pagesize=10):
|
|||
# allows us to call ourselves over and over, using different kwargs.
|
||||
options.append(
|
||||
{
|
||||
"key": ("|Wcurrent|n", "c"),
|
||||
"key": (_("|Wcurrent|n"), "c"),
|
||||
"desc": "|W({}/{})|n".format(page_index + 1, npages),
|
||||
"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:
|
||||
options.append(
|
||||
{
|
||||
"key": ("|wp|Wrevious page|n", "p"),
|
||||
"key": (_("|wp|Wrevious page|n"), "p"),
|
||||
"goto": (lambda caller: None, {"optionpage_index": page_index - 1}),
|
||||
}
|
||||
)
|
||||
if page_index < npages - 1:
|
||||
options.append(
|
||||
{
|
||||
"key": ("|wn|Wext page|n", "n"),
|
||||
"key": (_("|wn|Wext page|n"), "n"),
|
||||
"goto": (lambda caller: None, {"optionpage_index": page_index + 1}),
|
||||
}
|
||||
)
|
||||
|
|
@ -1662,7 +1665,7 @@ class CmdYesNoQuestion(Command):
|
|||
inp = raw
|
||||
|
||||
if inp in ('a', 'abort') and yes_no_question.allow_abort:
|
||||
caller.msg("Aborted.")
|
||||
caller.msg(_("Aborted."))
|
||||
self._clean(caller)
|
||||
return
|
||||
|
||||
|
|
@ -1672,7 +1675,6 @@ class CmdYesNoQuestion(Command):
|
|||
kwargs = yes_no_question.kwargs
|
||||
kwargs['caller_session'] = self.session
|
||||
|
||||
ok = False
|
||||
if inp in ('yes', 'y'):
|
||||
yes_no_question.yes_callable(caller, *args, **kwargs)
|
||||
elif inp in ('no', 'n'):
|
||||
|
|
@ -1684,9 +1686,9 @@ class CmdYesNoQuestion(Command):
|
|||
|
||||
# cleanup
|
||||
self._clean(caller)
|
||||
except Exception as err:
|
||||
except Exception:
|
||||
# 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")
|
||||
self._clean(caller)
|
||||
raise
|
||||
|
|
@ -1938,7 +1940,7 @@ def parse_menu_template(caller, menu_template, goto_callables=None):
|
|||
"""
|
||||
Validate goto-callable kwarg is on correct form.
|
||||
"""
|
||||
if not "=" in kwarg:
|
||||
if "=" not in kwarg:
|
||||
raise RuntimeError(
|
||||
f"EvMenu template error: goto-callable '{goto}' has a "
|
||||
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):
|
||||
"""
|
||||
Parse option section into option dict.
|
||||
|
||||
"""
|
||||
options = []
|
||||
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):
|
||||
"""
|
||||
Parse the menu string format into a node tree.
|
||||
|
||||
"""
|
||||
nodetree = {}
|
||||
splits = _RE_NODE.split(menu_template)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ from evennia import Command, CmdSet
|
|||
from evennia.commands import cmdhandler
|
||||
from evennia.utils.ansi import ANSIString
|
||||
from evennia.utils.utils import make_iter, inherits_from, justify, dedent
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_CMD_NOMATCH = cmdhandler.CMD_NOMATCH
|
||||
_CMD_NOINPUT = cmdhandler.CMD_NOINPUT
|
||||
|
|
@ -231,7 +232,7 @@ class EvMore(object):
|
|||
self._justify_kwargs = justify_kwargs
|
||||
self.exit_on_lastpage = exit_on_lastpage
|
||||
self.exit_cmd = exit_cmd
|
||||
self._exit_msg = "Exited |wmore|n pager."
|
||||
self._exit_msg = _("Exited |wmore|n pager.")
|
||||
self._kwargs = kwargs
|
||||
|
||||
self._data = None
|
||||
|
|
@ -354,8 +355,9 @@ class EvMore(object):
|
|||
"""
|
||||
Paginate by slice. This is done with an eye on memory efficiency (usually for
|
||||
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):
|
||||
"""
|
||||
|
|
@ -433,7 +435,7 @@ class EvMore(object):
|
|||
lines = text.split("\n")
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -451,13 +453,15 @@ class EvMore(object):
|
|||
Notes:
|
||||
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._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
|
||||
wanting to change the paginator, override `self.paginator` instead.
|
||||
- set `self._page_formatter` to a callable that will receive the page from `self._paginator`
|
||||
and format it with one element per line. Default is `str`. Or override `self.page_formatter`
|
||||
- set `self._page_formatter` to a callable that will receive the page from
|
||||
`self._paginator` and format it with one element per line. Default is `str`. Or
|
||||
override `self.page_formatter`
|
||||
directly instead.
|
||||
|
||||
By default, helper methods are called that perform these actions
|
||||
|
|
|
|||
|
|
@ -239,12 +239,12 @@ class ANSITextWrapper(TextWrapper):
|
|||
del chunks[-1]
|
||||
|
||||
while chunks:
|
||||
l = d_len(chunks[-1])
|
||||
ln = d_len(chunks[-1])
|
||||
|
||||
# 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_len += l
|
||||
cur_len += ln
|
||||
|
||||
# Nope, this line is full.
|
||||
else:
|
||||
|
|
@ -262,10 +262,10 @@ class ANSITextWrapper(TextWrapper):
|
|||
# Convert current line back to a string and store it in list
|
||||
# of all lines (return value).
|
||||
if cur_line:
|
||||
l = ""
|
||||
ln = ""
|
||||
for w in cur_line: # ANSI fix
|
||||
l += w #
|
||||
lines.append(indent + l)
|
||||
ln += w #
|
||||
lines.append(indent + ln)
|
||||
return lines
|
||||
|
||||
|
||||
|
|
@ -1099,8 +1099,9 @@ class EvTable(object):
|
|||
height (int, optional): Fixed height of table. Defaults to being unset. Width is
|
||||
still given precedence. If given, table cells will crop text rather
|
||||
than expand vertically.
|
||||
evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as even width as
|
||||
possible. This often looks best also for mixed-length tables. Default is `False`.
|
||||
evenwidth (bool, optional): Used with the `width` keyword. Adjusts columns to have as
|
||||
even width as possible. This often looks best also for mixed-length tables. Default
|
||||
is `False`.
|
||||
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
|
||||
size will it be resized by expanding horizontally (or crop `height` is given).
|
||||
|
|
@ -1347,7 +1348,8 @@ class EvTable(object):
|
|||
self.ncols = ncols
|
||||
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()
|
||||
|
||||
# equalize widths within each column
|
||||
|
|
@ -1434,7 +1436,8 @@ class EvTable(object):
|
|||
except Exception:
|
||||
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 = [
|
||||
max(cell.get_height() for cell in (col[iy] for col in self.worktable))
|
||||
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 inspect
|
||||
import random
|
||||
from functools import partial
|
||||
from django.conf import settings
|
||||
from evennia.utils import logger
|
||||
from evennia.utils.utils import (
|
||||
|
|
@ -234,8 +232,6 @@ class FuncParser:
|
|||
f"(available: {available})")
|
||||
return str(parsedfunc)
|
||||
|
||||
nargs = len(args)
|
||||
|
||||
# build kwargs in the proper priority order
|
||||
kwargs = {**self.default_kwargs, **kwargs, **reserved_kwargs,
|
||||
**{'funcparser': self, "raise_errors": raise_errors}}
|
||||
|
|
@ -606,7 +602,7 @@ def funcparser_callable_eval(*args, **kwargs):
|
|||
- `$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 ''
|
||||
|
||||
|
||||
|
|
@ -694,7 +690,7 @@ def funcparser_callable_round(*args, **kwargs):
|
|||
"""
|
||||
if not args:
|
||||
return ''
|
||||
args, _ = safe_convert_to_types(((float, int), {}) *args, **kwargs)
|
||||
args, _ = safe_convert_to_types(((float, int), {}), *args, **kwargs)
|
||||
|
||||
num, *significant = args
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -1081,10 +1078,12 @@ def funcparser_callable_you(*args, caller=None, receiver=None, mapping=None, cap
|
|||
capitalize = bool(capitalize)
|
||||
if caller == receiver:
|
||||
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.
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ total runtime of the server and the current uptime.
|
|||
"""
|
||||
|
||||
import time
|
||||
from calendar import monthrange
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.db.utils import OperationalError
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ class SharedMemoryModelBase(ModelBase):
|
|||
return super(SharedMemoryModelBase, cls).__call__(*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:
|
||||
return new_instance()
|
||||
cached_instance = cls.get_cached_instance(instance_key)
|
||||
|
|
@ -154,9 +155,9 @@ class SharedMemoryModelBase(ModelBase):
|
|||
if isinstance(value, (str, int)):
|
||||
value = to_str(value)
|
||||
if value.isdigit() or value.startswith("#"):
|
||||
# we also allow setting using dbrefs, if so we try to load the matching object.
|
||||
# (we assume the object is of the same type as the class holding the field, if
|
||||
# not a custom handler must be used for that field)
|
||||
# we also allow setting using dbrefs, if so we try to load the matching
|
||||
# object. (we assume the object is of the same type as the class holding
|
||||
# the field, if not a custom handler must be used for that field)
|
||||
dbid = dbref(value, reqhash=False)
|
||||
if dbid:
|
||||
model = _GA(cls, "_meta").get_field(fname).model
|
||||
|
|
@ -266,21 +267,24 @@ class SharedMemoryModel(Model, metaclass=SharedMemoryModelBase):
|
|||
pk = cls._meta.pks[0]
|
||||
else:
|
||||
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)
|
||||
if len(args) > pk_position:
|
||||
# if it's in the args, we can get it easily by index
|
||||
result = args[pk_position]
|
||||
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
|
||||
# a ForeignKey.
|
||||
# retrieve the pk value. Note that we use attname instead of name, to handle the case
|
||||
# where the pk is a a ForeignKey.
|
||||
result = kwargs[pk.attname]
|
||||
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]
|
||||
|
||||
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()
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ log_typemsg(). This is for historical, back-compatible reasons.
|
|||
|
||||
import os
|
||||
import time
|
||||
import glob
|
||||
from datetime import datetime
|
||||
from traceback import format_exc
|
||||
from twisted.python import log, logfile
|
||||
|
|
@ -47,6 +46,7 @@ def timeformat(when=None):
|
|||
|
||||
Returns:
|
||||
timestring (str): A formatted string of the given 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__1
|
||||
server.log.2020_01_29__2
|
||||
|
||||
"""
|
||||
suffix = ""
|
||||
copy_suffix = 0
|
||||
|
|
@ -146,7 +147,10 @@ class WeeklyLogFile(logfile.DailyLogFile):
|
|||
return suffix
|
||||
|
||||
def write(self, data):
|
||||
"Write data to log file"
|
||||
"""
|
||||
Write data to log file
|
||||
|
||||
"""
|
||||
logfile.BaseLogFile.write(self, data)
|
||||
self.lastDate = max(self.lastDate, self.toDate())
|
||||
self.size += len(data)
|
||||
|
|
@ -155,6 +159,7 @@ class WeeklyLogFile(logfile.DailyLogFile):
|
|||
class PortalLogObserver(log.FileLogObserver):
|
||||
"""
|
||||
Reformat logging
|
||||
|
||||
"""
|
||||
|
||||
timeFormat = None
|
||||
|
|
@ -289,6 +294,7 @@ def log_info(infomsg):
|
|||
Prints any generic debugging/informative info that should appear in the log.
|
||||
|
||||
infomsg: (string) The message to be logged.
|
||||
|
||||
"""
|
||||
try:
|
||||
infomsg = str(infomsg)
|
||||
|
|
@ -307,6 +313,7 @@ def log_dep(depmsg):
|
|||
|
||||
Args:
|
||||
depmsg (str): The deprecation message to log.
|
||||
|
||||
"""
|
||||
try:
|
||||
depmsg = str(depmsg)
|
||||
|
|
@ -325,6 +332,7 @@ def log_sec(secmsg):
|
|||
|
||||
Args:
|
||||
secmsg (str): The security message to log.
|
||||
|
||||
"""
|
||||
try:
|
||||
secmsg = str(secmsg)
|
||||
|
|
@ -346,6 +354,7 @@ class EvenniaLogFile(logfile.LogFile):
|
|||
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
|
||||
to preserve a continuous chat history for channel log files.
|
||||
|
||||
"""
|
||||
|
||||
# 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
|
||||
the previous log to the start of the new one.
|
||||
|
||||
"""
|
||||
append_tail = (num_lines_to_append
|
||||
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,
|
||||
which is used in tail_log_function.
|
||||
|
||||
Args:
|
||||
*args: Same args as file.seek
|
||||
**kwargs: Same kwargs as file.seek
|
||||
|
||||
"""
|
||||
return self._file.seek(*args, **kwargs)
|
||||
|
||||
|
|
@ -387,12 +399,14 @@ class EvenniaLogFile(logfile.LogFile):
|
|||
"""
|
||||
Convenience method for accessing our _file attribute's readlines method,
|
||||
which is used in tail_log_function.
|
||||
|
||||
Args:
|
||||
*args: same args as file.readlines
|
||||
**kwargs: same kwargs as file.readlines
|
||||
|
||||
Returns:
|
||||
lines (list): lines from our _file attribute.
|
||||
|
||||
"""
|
||||
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()
|
||||
block_count -= 1
|
||||
# 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:
|
||||
callback(lines_found)
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from evennia.utils.utils import string_partial_matching
|
||||
from evennia.utils.containers import OPTION_CLASSES
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
|
|
@ -134,7 +135,7 @@ class OptionHandler:
|
|||
"""
|
||||
if key not in self.options_dict:
|
||||
if raise_error:
|
||||
raise KeyError("Option not found!")
|
||||
raise KeyError(_("Option not found!"))
|
||||
return default
|
||||
# get the options or load/recache it
|
||||
op_found = self.options.get(key) or self._load_option(key)
|
||||
|
|
@ -155,12 +156,14 @@ class OptionHandler:
|
|||
|
||||
"""
|
||||
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)
|
||||
if not match:
|
||||
raise ValueError("Option not found!")
|
||||
raise ValueError(_("Option not found!"))
|
||||
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]
|
||||
op = self.get(match, return_obj=True)
|
||||
op.set(value, **kwargs)
|
||||
|
|
|
|||
|
|
@ -61,8 +61,7 @@ except OperationalError:
|
|||
from evennia.scripts.models import ScriptDB
|
||||
from evennia.comms.models import Msg, ChannelDB
|
||||
from evennia.help.models import HelpEntry
|
||||
from evennia.typeclasses.tags import Tag
|
||||
|
||||
from evennia.typeclasses.tags import Tag # noqa
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Search manager-wrappers
|
||||
|
|
@ -243,7 +242,7 @@ def search_script_attribute(
|
|||
def search_channel_attribute(
|
||||
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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ be of use when designing your own game.
|
|||
import os
|
||||
import gc
|
||||
import sys
|
||||
import copy
|
||||
import types
|
||||
import math
|
||||
import re
|
||||
|
|
@ -35,6 +34,7 @@ from django.utils.translation import gettext as _
|
|||
from django.apps import apps
|
||||
from django.core.validators import validate_email as django_validate_email
|
||||
from django.core.exceptions import ValidationError as DjangoValidationError
|
||||
|
||||
from evennia.utils import logger
|
||||
|
||||
_MULTIMATCH_TEMPLATE = settings.SEARCH_MULTIMATCH_TEMPLATE
|
||||
|
|
@ -204,7 +204,7 @@ def dedent(text, baseline_index=None, indent=None):
|
|||
baseline = lines[baseline_index]
|
||||
spaceremove = len(baseline) - len(baseline.lstrip(" "))
|
||||
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 = []
|
||||
istart = 0
|
||||
for irows in nrows:
|
||||
cols.append(onecol[istart : istart + irows])
|
||||
cols.append(onecol[istart: istart + irows])
|
||||
istart = istart + irows
|
||||
for col in cols:
|
||||
if len(col) < height:
|
||||
|
|
@ -1029,8 +1029,6 @@ def uses_database(name="sqlite3"):
|
|||
return engine == "django.db.backends.%s" % name
|
||||
|
||||
|
||||
|
||||
|
||||
def delay(timedelay, callback, *args, **kwargs):
|
||||
"""
|
||||
Delay the calling of a callback (function).
|
||||
|
|
@ -1238,8 +1236,8 @@ def check_evennia_dependencies():
|
|||
except ImportError:
|
||||
errstring += (
|
||||
"\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 can get it from http://twistedmatrix.com/trac/wiki/TwistedWords."
|
||||
"\n Linux Debian/Ubuntu users should install package 'python-twisted-words', "
|
||||
"\n others can get it from http://twistedmatrix.com/trac/wiki/TwistedWords."
|
||||
)
|
||||
not_error = False
|
||||
errstring = errstring.strip()
|
||||
|
|
@ -1911,7 +1909,7 @@ def format_grid(elements, width=78, sep=" ", verbatim_elements=None):
|
|||
|
||||
wl = wls[ie]
|
||||
lrow = len(row)
|
||||
debug = row.replace(" ", ".")
|
||||
# debug = row.replace(" ", ".")
|
||||
|
||||
if lrow + wl > width:
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_evennia_pids():
|
||||
"""
|
||||
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.
|
||||
for _ in range(10):
|
||||
for inum in range(10):
|
||||
gpath = os.getcwd()
|
||||
if "server" in os.listdir(gpath):
|
||||
if os.path.isfile(os.path.join("server", "conf", "settings.py")):
|
||||
|
|
|
|||
|
|
@ -22,18 +22,20 @@ def text(entry, option_key="Text", **kwargs):
|
|||
try:
|
||||
return str(entry)
|
||||
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):
|
||||
"""
|
||||
The color should be just a color character, so 'r' if red color is desired.
|
||||
|
||||
"""
|
||||
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")
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -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]}"
|
||||
else:
|
||||
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:
|
||||
local = _dt.datetime.strptime(entry, "%b %d %H:%M %Y")
|
||||
except 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)
|
||||
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.
|
||||
|
||||
Args:
|
||||
entry (string): This is a string from user-input. The intended format is, for example: "5d 2w 90s" for
|
||||
'five days, two weeks, and ninety seconds.' Invalid sections are ignored.
|
||||
entry (string): This is a string from user-input. The intended format is, for example:
|
||||
"5d 2w 90s" for 'five days, two weeks, and ninety seconds.' Invalid sections are
|
||||
ignored.
|
||||
option_key (str): Name to display this query as.
|
||||
|
||||
Returns:
|
||||
|
|
@ -129,7 +136,10 @@ def duration(entry, option_key="Duration", **kwargs):
|
|||
elif _re.match(r"^[\d]+y$", interval):
|
||||
days += int(interval.rstrip("y")) * 365
|
||||
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)
|
||||
|
||||
|
|
@ -137,45 +147,56 @@ def duration(entry, option_key="Duration", **kwargs):
|
|||
def future(entry, option_key="Future Datetime", from_tz=None, **kwargs):
|
||||
time = datetime(entry, option_key, from_tz=from_tz)
|
||||
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
|
||||
|
||||
|
||||
def signed_integer(entry, option_key="Signed Integer", **kwargs):
|
||||
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:
|
||||
num = int(entry)
|
||||
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
|
||||
|
||||
|
||||
def positive_integer(entry, option_key="Positive Integer", **kwargs):
|
||||
num = signed_integer(entry, option_key)
|
||||
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
|
||||
|
||||
|
||||
def unsigned_integer(entry, option_key="Unsigned Integer", **kwargs):
|
||||
num = signed_integer(entry, option_key)
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
Args:
|
||||
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?
|
||||
|
||||
Returns:
|
||||
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):
|
||||
raise ValueError(error)
|
||||
entry = entry.upper()
|
||||
|
|
@ -196,39 +217,42 @@ def timezone(entry, option_key="Timezone", **kwargs):
|
|||
|
||||
Returns:
|
||||
A PYTZ timezone.
|
||||
|
||||
"""
|
||||
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)
|
||||
if len(found) > 1:
|
||||
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:
|
||||
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):
|
||||
if not entry:
|
||||
raise ValueError("Email address field empty!")
|
||||
raise ValueError(_("Email address field empty!"))
|
||||
valid = validate_email_address(entry)
|
||||
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
|
||||
|
||||
|
||||
def lock(entry, option_key="locks", access_options=None, **kwargs):
|
||||
entry = entry.strip()
|
||||
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(";"):
|
||||
access_type, lockfunc = locksetting.split(":", 1)
|
||||
if not access_type:
|
||||
raise ValueError("Must enter an access type!")
|
||||
raise ValueError(_("Must enter an access type!"))
|
||||
if 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:
|
||||
raise ValueError("Lock func not entered.")
|
||||
raise ValueError(_("Lock func not entered."))
|
||||
return entry
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue