Merge branch 'develop' into global-scripts-raise
This commit is contained in:
commit
d1981bd15b
62 changed files with 1995 additions and 818 deletions
8
.github/ISSUE_TEMPLATE/bug-report-develop.md
vendored
8
.github/ISSUE_TEMPLATE/bug-report-develop.md
vendored
|
|
@ -8,7 +8,7 @@ assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
#### Describe the bug
|
#### Describe the bug
|
||||||
(This is for bugs in the develop-branch only. Make sure you test with the latest version.)
|
<!--(This is for bugs in the develop-branch only. Make sure you test with the latest version.)-->
|
||||||
|
|
||||||
#### To Reproduce
|
#### To Reproduce
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
|
|
@ -18,10 +18,10 @@ Steps to reproduce the behavior:
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
#### Expected behavior
|
#### Expected behavior
|
||||||
(Replace with a clear and concise description of what you expected to happen.)
|
<!--(Replace with a clear and concise description of what you expected to happen.-->)
|
||||||
|
|
||||||
#### Develop-branch commit
|
#### Develop-branch commit
|
||||||
(The commit-hash. If unsure, run `evennia -v` or get the first few lines of the `about` command in-game.)
|
<!--(The commit-hash. If unsure, run `evennia -v` or get the first few lines of the `about` command in-game.)-->
|
||||||
|
|
||||||
#### Additional context
|
#### Additional context
|
||||||
(Replace with any other context about the problem, or ideas on how to solve.)
|
<!--(Replace with any other context about the problem, or ideas on how to solve.)-->
|
||||||
|
|
|
||||||
8
.github/ISSUE_TEMPLATE/bug-report.md
vendored
8
.github/ISSUE_TEMPLATE/bug-report.md
vendored
|
|
@ -8,7 +8,7 @@ assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
#### Describe the bug
|
#### Describe the bug
|
||||||
(Replace with a clear and concise description of what the bug is.)
|
<!--(Replace with a clear and concise description of what the bug is.)-->
|
||||||
|
|
||||||
#### To Reproduce
|
#### To Reproduce
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
|
|
@ -18,10 +18,10 @@ Steps to reproduce the behavior:
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
#### Expected behavior
|
#### Expected behavior
|
||||||
(Replace with a clear and concise description of what you expected to happen.)
|
<!--(Replace with a clear and concise description of what you expected to happen.)-->
|
||||||
|
|
||||||
#### Environment, Evennia version, OS etc
|
#### Environment, Evennia version, OS etc
|
||||||
(Replace with info. If unsure, run `evennia -v` or get the first few lines of the `about` command in-game.)
|
<!--(Replace with info. If unsure, run `evennia -v` or get the first few lines of the `about` command in-game.)-->
|
||||||
|
|
||||||
#### Additional context
|
#### Additional context
|
||||||
(Replace with any other context about the problem, or ideas on how to solve.)
|
<!--(Replace with any other context about the problem, or ideas on how to solve.)-->
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
#### Existing page / new
|
#### Existing page / new
|
||||||
(Link to existing documentation page or proposed name of new page)
|
<!--(Link to existing documentation page or proposed name of new page)-->
|
||||||
|
|
||||||
#### Documentation issue
|
#### Documentation issue
|
||||||
(Replace with the description of what the issue is or motivate a changes/additions)
|
<!--(Replace with the description of what the issue is or motivate a changes/additions)-->
|
||||||
|
|
||||||
#### Suggested change
|
#### Suggested change
|
||||||
(Enter the suggested change here)
|
<!--(Enter the suggested change here)-->
|
||||||
|
|
|
||||||
8
.github/ISSUE_TEMPLATE/feature-request.md
vendored
8
.github/ISSUE_TEMPLATE/feature-request.md
vendored
|
|
@ -8,13 +8,13 @@ assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
#### Is your feature request related to a problem? Please describe.
|
#### Is your feature request related to a problem? Please describe.
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
<!--A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]-->
|
||||||
|
|
||||||
#### Describe the solution you'd like
|
#### Describe the solution you'd like
|
||||||
A clear and concise description of what you want to happen.
|
<!--A clear and concise description of what you want to happen.-->
|
||||||
|
|
||||||
#### Describe alternatives you've considered
|
#### Describe alternatives you've considered
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
<!--A clear and concise description of any alternative solutions or features you've considered.-->
|
||||||
|
|
||||||
#### Additional context
|
#### Additional context
|
||||||
Add any other context or screenshots about the feature request here.
|
<!--Add any other context or screenshots about the feature request here.-->
|
||||||
|
|
|
||||||
28
.github/workflows/github_action_test_suite.yml
vendored
28
.github/workflows/github_action_test_suite.yml
vendored
|
|
@ -107,7 +107,6 @@ jobs:
|
||||||
pip install psycopg2-binary==2.8.6 # fix issue for Django 2.2
|
pip install psycopg2-binary==2.8.6 # fix issue for Django 2.2
|
||||||
pip install mysqlclient
|
pip install mysqlclient
|
||||||
pip install coveralls
|
pip install coveralls
|
||||||
pip install codacy-coverage
|
|
||||||
pip install tblib
|
pip install tblib
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
|
||||||
|
|
@ -122,6 +121,8 @@ jobs:
|
||||||
evennia migrate
|
evennia migrate
|
||||||
evennia collectstatic --noinput
|
evennia collectstatic --noinput
|
||||||
|
|
||||||
|
# OBS - it's important to not run the coverage tests with --parallel, it messes up the coverage
|
||||||
|
# calculation!
|
||||||
- name: Run test suite with coverage
|
- name: Run test suite with coverage
|
||||||
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10'
|
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10'
|
||||||
working-directory: testing_mygame
|
working-directory: testing_mygame
|
||||||
|
|
@ -131,12 +132,13 @@ jobs:
|
||||||
--omit=*/migrations/*,*/urls.py,*/test*.py,*.sh,*.txt,*.md,*.pyc,*.service \
|
--omit=*/migrations/*,*/urls.py,*/test*.py,*.sh,*.txt,*.md,*.pyc,*.service \
|
||||||
../bin/unix/evennia test \
|
../bin/unix/evennia test \
|
||||||
--settings=settings \
|
--settings=settings \
|
||||||
--keepdb \
|
|
||||||
--parallel 4 \
|
|
||||||
--timing \
|
--timing \
|
||||||
evennia
|
evennia
|
||||||
coverage xml
|
coverage xml
|
||||||
|
coverage --version
|
||||||
|
coverage report | grep TOTAL
|
||||||
|
|
||||||
|
# For other runs, run tests in parallel
|
||||||
- name: Run test suite
|
- name: Run test suite
|
||||||
if: matrix.TESTING_DB != 'sqlite3' || matrix.python-version != '3.10'
|
if: matrix.TESTING_DB != 'sqlite3' || matrix.python-version != '3.10'
|
||||||
working-directory: testing_mygame
|
working-directory: testing_mygame
|
||||||
|
|
@ -148,7 +150,7 @@ jobs:
|
||||||
--timing \
|
--timing \
|
||||||
evennia
|
evennia
|
||||||
|
|
||||||
# we only want to run coverall/codacy once, so we only do it for one of the matrix combinations
|
# we only want to run coverall once, so we only do it for one of the matrix combinations
|
||||||
# it's also not critical if pushing to either service fails (happens for PRs since env is not
|
# it's also not critical if pushing to either service fails (happens for PRs since env is not
|
||||||
# available outside of the evennia org)
|
# available outside of the evennia org)
|
||||||
- name: Send data to Coveralls
|
- name: Send data to Coveralls
|
||||||
|
|
@ -160,27 +162,19 @@ jobs:
|
||||||
cd testing_mygame
|
cd testing_mygame
|
||||||
coveralls
|
coveralls
|
||||||
|
|
||||||
- name: Send data to Codacy
|
|
||||||
if: ${{ matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10' }}
|
|
||||||
continue-on-error: true
|
|
||||||
uses: codacy/codacy-coverage-reporter-action@master
|
|
||||||
with:
|
|
||||||
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
|
||||||
coverage-reports: ./testing_mygame/coverage.xml
|
|
||||||
|
|
||||||
# docker setup and push
|
# docker setup and push
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10'
|
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10'
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10'
|
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10'
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
-
|
-
|
||||||
name: Login to DockerHub
|
name: Login to DockerHub
|
||||||
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
|
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop')
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
@ -188,7 +182,7 @@ jobs:
|
||||||
name: Build and push for master
|
name: Build and push for master
|
||||||
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.9' && github.ref == 'refs/heads/master'
|
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.9' && github.ref == 'refs/heads/master'
|
||||||
id: docker_build_master
|
id: docker_build_master
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: evennia/evennia:latest
|
tags: evennia/evennia:latest
|
||||||
|
|
@ -196,7 +190,7 @@ jobs:
|
||||||
name: Build and push for develop
|
name: Build and push for develop
|
||||||
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10' && github.ref == 'refs/heads/develop'
|
if: matrix.TESTING_DB == 'sqlite3' && matrix.python-version == '3.10' && github.ref == 'refs/heads/develop'
|
||||||
id: docker_build_develop
|
id: docker_build_develop
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: evennia/evennia:develop
|
tags: evennia/evennia:develop
|
||||||
|
|
|
||||||
|
|
@ -205,6 +205,8 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10
|
||||||
- Make setting `MAX_NR_CHARACTERS` interact better with the new settings above.
|
- Make setting `MAX_NR_CHARACTERS` interact better with the new settings above.
|
||||||
- Allow `$search` funcparser func to search tags and to accept kwargs for more
|
- Allow `$search` funcparser func to search tags and to accept kwargs for more
|
||||||
powerful searches passed into the regular search functions.
|
powerful searches passed into the regular search functions.
|
||||||
|
- `spawner.spawn` and linked methods now has a kwarg `protfunc_raise_errors`
|
||||||
|
(default True) to disable strict errors on malformed/not-found protfuncs
|
||||||
|
|
||||||
## Evennia 0.9.5
|
## Evennia 0.9.5
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,17 @@
|
||||||
|
|
||||||
## Our Pledge
|
## Our Pledge
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, disability, ethnicity, gender identity and expression, level of
|
||||||
|
experience, nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
## Our Standards
|
## Our Standards
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment include:
|
Examples of behavior that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
* Using welcoming and inclusive language
|
||||||
* Being respectful of differing viewpoints and experiences
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
|
@ -16,31 +22,54 @@ Examples of behavior that contributes to creating a positive environment include
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
* Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
* Publishing others' private information, such as a physical or electronic
|
||||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
## Our Responsibilities
|
## Our Responsibilities
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behavior and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, or to ban temporarily or permanently any
|
||||||
|
contributor for other behaviors that they deem inappropriate, threatening,
|
||||||
|
offensive, or harmful.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting griatch AT gmail DOT com or Griatch in the #evennia channel on irc.freenode.net. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported by contacting griatch AT gmail DOT com or Griatch in the #evennia
|
||||||
|
channel on irc.freenode.net. The project team will review and investigate all
|
||||||
|
complaints, and will respond in a way that it deems appropriate to the
|
||||||
|
circumstances. The project team is obligated to maintain confidentiality with
|
||||||
|
regard to the reporter of an incident. Further details of specific enforcement
|
||||||
|
policies may be posted separately.
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
[homepage]: http://contributor-covenant.org
|
[homepage]: http://contributor-covenant.org
|
||||||
[version]: http://contributor-covenant.org/version/1/4/
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,17 @@
|
||||||
# Contributing to Evennia
|
# Contributing to Evennia
|
||||||
|
|
||||||
Evennia utilizes GitHub for issue tracking and contributions:
|
There are many ways you can contribute to Evennia development:
|
||||||
|
|
||||||
- Reporting Issues issues/bugs and making feature requests can be done [in the issue tracker](https://github.com/evennia/evennia/issues).
|
- You can help a lot by being active in the community. You can spread
|
||||||
- Evennia's documentation is a [wiki](https://github.com/evennia/evennia/wiki) that everyone can contribute to. Further
|
the word - by writing, talking, blogging etc about Evennia. Let
|
||||||
instructions and details about contributing are found [here](https://github.com/evennia/evennia/wiki/Contributing).
|
others know text-based gaming is still a thing!
|
||||||
|
- You can help by reporting any issues/bugs you find, and tell us of your
|
||||||
|
feature requests [as an issue on github][issues]. This is also where you
|
||||||
|
report typos or errors in the [the Evennia documentation][docs].
|
||||||
|
- To help fixing (or expand) our docs, check out [how to contribute to docs][contribute-docs].
|
||||||
|
- To contribute to Evennia itself, check out how to [help with code][helping-code].
|
||||||
|
|
||||||
|
[issues]: https://github.com/evennia/evennia/issues/new/choose
|
||||||
|
[docs]: https://www.evennia.com/docs/1.0-dev/index.html
|
||||||
|
[contribute-docs]: https://www.evennia.com/docs/1.0-dev/Contributing-Docs.html
|
||||||
|
[helping-code]: https://www.evennia.com/docs/1.0-dev/Contributing.html#helping-with-code
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@
|
||||||
# Evennia installation
|
# Evennia installation
|
||||||
|
|
||||||
You can find the latest updated installation instructions and
|
You can find the latest updated installation instructions and
|
||||||
requirements [here](https://github.com/evennia/evennia/wiki/Getting-Started).
|
requirements
|
||||||
|
[here](https://www.evennia.com/docs/1.0-dev/Setup/Installation.html)
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,8 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10
|
||||||
- Add new setting `MAX_NR_SIMULTANEUS_PUPPETS` - how many puppets the account
|
- Add new setting `MAX_NR_SIMULTANEUS_PUPPETS` - how many puppets the account
|
||||||
can run at the same time. Used to limit multi-playing.
|
can run at the same time. Used to limit multi-playing.
|
||||||
- Make setting `MAX_NR_CHARACTERS` interact better with the new settings above.
|
- Make setting `MAX_NR_CHARACTERS` interact better with the new settings above.
|
||||||
|
- Allow `$search` funcparser func to search tags and to accept kwargs for more
|
||||||
|
powerful searches passed into the regular search functions.
|
||||||
|
|
||||||
## Evennia 0.9.5
|
## Evennia 0.9.5
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -377,7 +377,7 @@ Here's how to tell Evennia to manage the script in settings:
|
||||||
|
|
||||||
GLOBAL_SCRIPTS = {
|
GLOBAL_SCRIPTS = {
|
||||||
"my_script": {
|
"my_script": {
|
||||||
"typeclass": "scripts.Weather",
|
"typeclass": "typeclasses.scripts.Weather",
|
||||||
"repeats": -1,
|
"repeats": -1,
|
||||||
"interval": 50,
|
"interval": 50,
|
||||||
"desc": "Weather script"
|
"desc": "Weather script"
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ updated after Sept 2022 will be missing some translations.
|
||||||
+---------------+----------------------+--------------+
|
+---------------+----------------------+--------------+
|
||||||
| fr | French | Mar 2022 |
|
| fr | French | Mar 2022 |
|
||||||
+---------------+----------------------+--------------+
|
+---------------+----------------------+--------------+
|
||||||
| it | Italian | Feb 2015 |
|
| it | Italian | Oct 2022 |
|
||||||
+---------------+----------------------+--------------+
|
+---------------+----------------------+--------------+
|
||||||
| ko | Korean (simplified) | Sep 2019 |
|
| ko | Korean (simplified) | Sep 2019 |
|
||||||
+---------------+----------------------+--------------+
|
+---------------+----------------------+--------------+
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Custom gameime
|
# Custom gametime
|
||||||
|
|
||||||
Contrib by vlgeoff, 2017 - based on Griatch's core original
|
Contrib by vlgeoff, 2017 - based on Griatch's core original
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
Contrib by Griatch 2022
|
Contrib by Griatch 2022
|
||||||
|
|
||||||
|
|
||||||
|
```{warning}
|
||||||
|
NOTE - this tutorial is WIP and NOT complete! It was put on hold to focus on
|
||||||
|
releasing Evennia 1.0. You will still learn things from it, but don't expect
|
||||||
|
perfection.
|
||||||
|
```
|
||||||
|
|
||||||
A complete example MUD using Evennia. This is the final result of what is
|
A complete example MUD using Evennia. This is the final result of what is
|
||||||
implemented if you follow the Getting-Started tutorial. It's recommended
|
implemented if you follow the Getting-Started tutorial. It's recommended
|
||||||
that you follow the tutorial step by step and write your own code. But if
|
that you follow the tutorial step by step and write your own code. But if
|
||||||
|
|
|
||||||
73
docs/source/Contribs/Contrib-Git-Integration.md
Normal file
73
docs/source/Contribs/Contrib-Git-Integration.md
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
# In-game Git Integration
|
||||||
|
|
||||||
|
Contribution by helpme (2022)
|
||||||
|
|
||||||
|
A module to integrate a stripped-down version of git within the game, allowing developers to view their git status, change branches, and pull updated code of both their local mygame repo and Evennia core. After a successful pull or checkout, the git command will reload the game: Manual restarts may be required to to apply certain changes that would impact persistent scripts etc.
|
||||||
|
|
||||||
|
Once the contrib is set up, integrating remote changes is as simple as entering the following into your game:
|
||||||
|
|
||||||
|
```
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
The repositories you want to work with, be it only your local mygame repo, only Evennia core, or both, must be git directories for the command to function. If you are only interested in using this to get upstream Evennia changes, only the Evennia repository needs to be a git repository. [Get started with version control here.](https://www.evennia.com/docs/1.0-dev/Coding/Version-Control.html)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This package requires the dependency "gitpython", a python library used to interact with git repositories. To install, it's easiest to install Evennia's extra requirements:
|
||||||
|
|
||||||
|
- Activate your `virtualenv`
|
||||||
|
- `cd` to the root of the Evennia repository. There should be an `requirements_extra.txt` file here.
|
||||||
|
- `pip install -r requirements_extra.txt`
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This utility adds a simple assortment of 'git' commands. Import the module into your commands and add it to your command set to make it available.
|
||||||
|
|
||||||
|
Specifically, in `mygame/commands/default_cmdsets.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
...
|
||||||
|
from evennia.contrib.utils.git_integration import GitCmdSet # <---
|
||||||
|
|
||||||
|
class CharacterCmdset(default_cmds.Character_CmdSet):
|
||||||
|
...
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
...
|
||||||
|
self.add(GitCmdSet) # <---
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `reload` to make the git command available.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This utility will only work if the directory you wish to work with is a git directory. If they are not, you will be prompted to initiate your directory as a git repository using the following commands in your terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
git init
|
||||||
|
git remote add origin 'link to your repository'
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the git commands are only available to those with Developer permissions and higher. You can change this by overriding the command and setting its locks from "cmd:pperm(Developer)" to the lock of your choice.
|
||||||
|
|
||||||
|
The supported commands are:
|
||||||
|
* git status: An overview of your git repository, which files have been changed locally, and the commit you're on.
|
||||||
|
* git branch: What branches are available for you to check out.
|
||||||
|
* git checkout 'branch': Checkout a branch.
|
||||||
|
* git pull: Pull the latest code from your current branch.
|
||||||
|
|
||||||
|
* All of these commands are also available with 'evennia', to serve the same functionality related to your Evennia directory. So:
|
||||||
|
* git evennia status
|
||||||
|
* git evennia branch
|
||||||
|
* git evennia checkout 'branch'
|
||||||
|
* git evennia pull: Pull the latest Evennia code.
|
||||||
|
|
||||||
|
## Settings Used
|
||||||
|
|
||||||
|
The utility uses the existing GAME_DIR and EVENNIA_DIR settings from settings.py. You should not need to alter these if you have a standard directory setup, they ought to exist without any setup required from you.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
<small>This document page is generated from `evennia/contrib/utils/git_integration/README.md`. Changes to this
|
||||||
|
file will be overwritten, so edit that file rather than this one.</small>
|
||||||
|
|
@ -53,28 +53,31 @@ Exits: northeast and east
|
||||||
(then go back to your mygame/ folder)
|
(then go back to your mygame/ folder)
|
||||||
|
|
||||||
This will install all optional requirements of Evennia.
|
This will install all optional requirements of Evennia.
|
||||||
2. Import and add the `evennia.contrib.commands.XYZGridCmdSet` to the
|
2. Import and [add] the `evennia.contrib.grid.xyzgrid.commands.XYZGridCmdSet` to the
|
||||||
`CharacterCmdset` cmdset in `mygame/commands.default_cmds.py`. Reload
|
`CharacterCmdset` cmdset in `mygame/commands.default_cmds.py`. Reload
|
||||||
the server. This makes the `map`, `goto/path` and the modified `teleport` and
|
the server. This makes the `map`, `goto/path` and the modified `teleport` and
|
||||||
`open` commands available in-game.
|
`open` commands available in-game.
|
||||||
|
|
||||||
|
[add]: ../Components/Command-Sets
|
||||||
|
|
||||||
3. Edit `mygame/server/conf/settings.py` and add
|
3. Edit `mygame/server/conf/settings.py` and add
|
||||||
|
|
||||||
EXTRA_LAUNCHER_COMMANDS['xyzgrid'] = 'evennia.contrib.launchcmd.xyzcommand'
|
EXTRA_LAUNCHER_COMMANDS['xyzgrid'] = 'evennia.contrib.grid.xyzgrid.launchcmd.xyzcommand'
|
||||||
|
PROTOTYPE_MODULES += ['evennia.contrib.grid.xyzgrid.prototypes']
|
||||||
and
|
|
||||||
|
|
||||||
PROTOTYPE_MODULES += [’evennia.contrib.grid.xyzgrid.prototypes’]
|
|
||||||
|
|
||||||
This will add the new ability to enter `evennia xyzgrid <option>` on the
|
This will add the new ability to enter `evennia xyzgrid <option>` on the
|
||||||
command line. It will also make the `xyz_room` and `xyz_exit` prototypes
|
command line. It will also make the `xyz_room` and `xyz_exit` prototypes
|
||||||
available for use as prototype-parents when spawning the grid.
|
available for use as prototype-parents when spawning the grid.
|
||||||
|
|
||||||
4. Run `evennia xyzgrid help` for available options.
|
4. Run `evennia xyzgrid help` for available options.
|
||||||
|
|
||||||
5. (Optional): By default, the xyzgrid will only spawn module-based
|
5. (Optional): By default, the xyzgrid will only spawn module-based
|
||||||
[prototypes](../Components/Prototypes.md). This is an optimization and usually makes sense
|
[prototypes]. This is an optimization and usually makes sense
|
||||||
since the grid is entirely defined outside the game anyway. If you want to
|
since the grid is entirely defined outside the game anyway. If you want to
|
||||||
also make use of in-game (db-) created prototypes, add
|
also make use of in-game (db-) created prototypes, add
|
||||||
`XYZGRID_USE_DB_PROTOTYPES = True` to settings.
|
`XYZGRID_USE_DB_PROTOTYPES = True` to settings.
|
||||||
|
|
||||||
|
[prototypes]: ../Components/Prototypes
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -609,11 +609,12 @@ character make small verbal observations at irregular intervals.
|
||||||
|
|
||||||
_Contrib by Griatch 2022_
|
_Contrib by Griatch 2022_
|
||||||
|
|
||||||
A complete example MUD using Evennia. This is the final result of what is
|
|
||||||
implemented if you follow the Getting-Started tutorial. It's recommended
|
```{warning}
|
||||||
that you follow the tutorial step by step and write your own code. But if
|
NOTE - this tutorial is WIP and NOT complete! It was put on hold to focus on
|
||||||
you prefer you can also pick apart or use this as a starting point for your
|
releasing Evennia 1.0. You will still learn things from it, but don't expect
|
||||||
own game.
|
perfection.
|
||||||
|
```
|
||||||
|
|
||||||
[Read the documentation](./Contrib-Evadventure.md) - [Browse the Code](evennia.contrib.tutorials.evadventure)
|
[Read the documentation](./Contrib-Evadventure.md) - [Browse the Code](evennia.contrib.tutorials.evadventure)
|
||||||
|
|
||||||
|
|
@ -680,6 +681,7 @@ and more._
|
||||||
|
|
||||||
Contrib-Auditing.md
|
Contrib-Auditing.md
|
||||||
Contrib-Fieldfill.md
|
Contrib-Fieldfill.md
|
||||||
|
Contrib-Git-Integration.md
|
||||||
Contrib-Name-Generator.md
|
Contrib-Name-Generator.md
|
||||||
Contrib-Random-String-Generator.md
|
Contrib-Random-String-Generator.md
|
||||||
Contrib-Tree-Select.md
|
Contrib-Tree-Select.md
|
||||||
|
|
@ -714,6 +716,16 @@ to any callable of your choice.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Contrib: `git_integration`
|
||||||
|
|
||||||
|
_Contribution by helpme (2022)_
|
||||||
|
|
||||||
|
A module to integrate a stripped-down version of git within the game, allowing developers to view their git status, change branches, and pull updated code of both their local mygame repo and Evennia core. After a successful pull or checkout, the git command will reload the game: Manual restarts may be required to to apply certain changes that would impact persistent scripts etc.
|
||||||
|
|
||||||
|
[Read the documentation](./Contrib-Git-Integration.md) - [Browse the Code](evennia.contrib.utils.git_integration)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Contrib: `name_generator`
|
### Contrib: `name_generator`
|
||||||
|
|
||||||
_Contribution by InspectorCaracal (2022)_
|
_Contribution by InspectorCaracal (2022)_
|
||||||
|
|
|
||||||
|
|
@ -230,12 +230,12 @@ displaying them differently. See next section.
|
||||||
As pointed out earlier, the `@time` command is meant to be used with a standard calendar, not a
|
As pointed out earlier, the `@time` command is meant to be used with a standard calendar, not a
|
||||||
custom one. We can easily create a new command though. We'll call it `time`, as is often the case
|
custom one. We can easily create a new command though. We'll call it `time`, as is often the case
|
||||||
on other MU*. Here's an example of how we could write it (for the example, you can create a file
|
on other MU*. Here's an example of how we could write it (for the example, you can create a file
|
||||||
`showtime.py` in your `commands` directory and paste this code in it):
|
`gametime.py` in your `commands` directory and paste this code in it):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# in a file mygame/commands/gametime.py
|
# in a file mygame/commands/gametime.py
|
||||||
|
|
||||||
from evennia.contrib import custom_gametime
|
from evennia.contrib.base_systems import custom_gametime
|
||||||
|
|
||||||
from commands.command import Command
|
from commands.command import Command
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,12 @@ If you are converting an existing game from a previous version, [see here](./Ins
|
||||||
For the impatient. If you have trouble with a step, you should jump on to the
|
For the impatient. If you have trouble with a step, you should jump on to the
|
||||||
more detailed instructions for your platform.
|
more detailed instructions for your platform.
|
||||||
|
|
||||||
|
```{warning}
|
||||||
|
Currently, these instructions will install 'latest' (stable) Evennia, which is
|
||||||
|
the 0.9.5 version. To install 1.0-dev, you need to add a step `git checkout develop` between steps
|
||||||
|
3 and 4 below.
|
||||||
|
```
|
||||||
|
|
||||||
1. Install Python, GIT and python-virtualenv. Start a Console/Terminal.
|
1. Install Python, GIT and python-virtualenv. Start a Console/Terminal.
|
||||||
2. `cd` to some place you want to do your development (like a folder
|
2. `cd` to some place you want to do your development (like a folder
|
||||||
`/home/anna/muddev/` on Linux or a folder in your personal user directory on Windows).
|
`/home/anna/muddev/` on Linux or a folder in your personal user directory on Windows).
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,8 @@ HTTP_LOG_FILE = os.path.join(LOG_DIR, "http_requests.log")
|
||||||
LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log")
|
LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log")
|
||||||
# Number of lines to append to rotating channel logs when they rotate
|
# Number of lines to append to rotating channel logs when they rotate
|
||||||
CHANNEL_LOG_NUM_TAIL_LINES = 20
|
CHANNEL_LOG_NUM_TAIL_LINES = 20
|
||||||
# Max size (in bytes) of channel log files before they rotate
|
# Max size (in bytes) of channel log files before they rotate.
|
||||||
|
# Minimum is 1000 (1kB) but should usually be larger.
|
||||||
CHANNEL_LOG_ROTATE_SIZE = 1000000
|
CHANNEL_LOG_ROTATE_SIZE = 1000000
|
||||||
# Unused by default, but used by e.g. the MapSystem contrib. A place for storing
|
# Unused by default, but used by e.g. the MapSystem contrib. A place for storing
|
||||||
# semi-permanent data and avoid it being rebuilt over and over. It is created
|
# semi-permanent data and avoid it being rebuilt over and over. It is created
|
||||||
|
|
@ -605,7 +606,7 @@ OPTIONS_ACCOUNT_DEFAULT = {
|
||||||
"column_names_color": ("Table column header text.", "Color", "w"),
|
"column_names_color": ("Table column header text.", "Color", "w"),
|
||||||
"help_category_color": ("Help category names.", "Color", "n"),
|
"help_category_color": ("Help category names.", "Color", "n"),
|
||||||
"help_entry_color": ("Help entry names.", "Color", "n"),
|
"help_entry_color": ("Help entry names.", "Color", "n"),
|
||||||
"timezone": ("Timezone for dates. @tz for a list.", "Timezone", "UTC"),
|
"timezone": ("Timezone for dates.", "Timezone", "UTC"),
|
||||||
}
|
}
|
||||||
# Modules holding Option classes, responsible for serializing the option and
|
# Modules holding Option classes, responsible for serializing the option and
|
||||||
# calling validator functions on it. Same-named functions in modules added
|
# calling validator functions on it. Same-named functions in modules added
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
```{eval-rst}
|
||||||
|
evennia.contrib.utils.git\_integration.git\_integration
|
||||||
|
==============================================================
|
||||||
|
|
||||||
|
.. automodule:: evennia.contrib.utils.git_integration.git_integration
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
```
|
||||||
18
docs/source/api/evennia.contrib.utils.git_integration.md
Normal file
18
docs/source/api/evennia.contrib.utils.git_integration.md
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
```{eval-rst}
|
||||||
|
evennia.contrib.utils.git\_integration
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
.. automodule:: evennia.contrib.utils.git_integration
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 6
|
||||||
|
|
||||||
|
evennia.contrib.utils.git_integration.git_integration
|
||||||
|
evennia.contrib.utils.git_integration.tests
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
```{eval-rst}
|
||||||
|
evennia.contrib.utils.git\_integration.tests
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
.. automodule:: evennia.contrib.utils.git_integration.tests
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -13,6 +13,7 @@ evennia.contrib.utils
|
||||||
|
|
||||||
evennia.contrib.utils.auditing
|
evennia.contrib.utils.auditing
|
||||||
evennia.contrib.utils.fieldfill
|
evennia.contrib.utils.fieldfill
|
||||||
|
evennia.contrib.utils.git_integration
|
||||||
evennia.contrib.utils.name_generator
|
evennia.contrib.utils.name_generator
|
||||||
evennia.contrib.utils.random_string_generator
|
evennia.contrib.utils.random_string_generator
|
||||||
evennia.contrib.utils.tree_select
|
evennia.contrib.utils.tree_select
|
||||||
|
|
|
||||||
|
|
@ -211,7 +211,7 @@ class AccountDBManager(TypedObjectManager, UserManager):
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
typeclass (str): The typeclass to use for the account.
|
typeclass (str): The typeclass to use for the account.
|
||||||
is_superuser (bool): Wether or not this account is to be a superuser
|
is_superuser (bool): Whether or not this account is to be a superuser
|
||||||
locks (str): Lockstring.
|
locks (str): Lockstring.
|
||||||
permission (list): List of permission strings.
|
permission (list): List of permission strings.
|
||||||
tags (list): List of Tags on form `(key, category[, data])`
|
tags (list): List of Tags on form `(key, category[, data])`
|
||||||
|
|
|
||||||
|
|
@ -748,7 +748,7 @@ def cmdhandler(
|
||||||
)
|
)
|
||||||
if suggestions:
|
if suggestions:
|
||||||
sysarg += _(" Maybe you meant {command}?").format(
|
sysarg += _(" Maybe you meant {command}?").format(
|
||||||
command=utils.list_to_string(suggestions, _("or"), addquote=True)
|
command=utils.list_to_string(suggestions, endsep=_("or"), addquote=True)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sysarg += _(' Type "help" for help.')
|
sysarg += _(' Type "help" for help.')
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
||||||
# check if this Character already exists. Note that we are only
|
# check if this Character already exists. Note that we are only
|
||||||
# searching the base character typeclass here, not any child
|
# searching the base character typeclass here, not any child
|
||||||
# classes.
|
# classes.
|
||||||
self.msg("|rA character named '|w%s|r' already exists.|n" % key)
|
self.msg(f"|rA character named '|w{key}|r' already exists.|n")
|
||||||
return
|
return
|
||||||
|
|
||||||
# create the character
|
# create the character
|
||||||
|
|
@ -190,12 +190,10 @@ class CmdCharCreate(COMMAND_DEFAULT_CLASS):
|
||||||
elif not new_character.db.desc:
|
elif not new_character.db.desc:
|
||||||
new_character.db.desc = "This is a character."
|
new_character.db.desc = "This is a character."
|
||||||
self.msg(
|
self.msg(
|
||||||
"Created new character %s. Use |wic %s|n to enter the game as this character."
|
f"Created new character {new_character.key}. Use |wic {new_character.key}|n to enter the game as this character."
|
||||||
% (new_character.key, new_character.key)
|
|
||||||
)
|
)
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Character Created: %s (Caller: %s, IP: %s)."
|
f"Character Created: {new_character} (Caller: {account}, IP: {self.session.address})."
|
||||||
% (new_character, account, self.session.address)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -248,10 +246,9 @@ class CmdCharDelete(COMMAND_DEFAULT_CLASS):
|
||||||
pc for pc in caller.db._playable_characters if pc != delobj
|
pc for pc in caller.db._playable_characters if pc != delobj
|
||||||
]
|
]
|
||||||
delobj.delete()
|
delobj.delete()
|
||||||
self.msg("Character '%s' was permanently deleted." % key)
|
self.msg(f"Character '{key}' was permanently deleted.")
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Character Deleted: %s (Caller: %s, IP: %s)."
|
f"Character Deleted: {key} (Caller: {account}, IP: {self.session.address})."
|
||||||
% (key, account, self.session.address)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.msg("Deletion was aborted.")
|
self.msg("Deletion was aborted.")
|
||||||
|
|
@ -372,14 +369,12 @@ class CmdIC(COMMAND_DEFAULT_CLASS):
|
||||||
account.puppet_object(session, new_character)
|
account.puppet_object(session, new_character)
|
||||||
account.db._last_puppet = new_character
|
account.db._last_puppet = new_character
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Puppet Success: (Caller: %s, Target: %s, IP: %s)."
|
f"Puppet Success: (Caller: {account}, Target: {new_character}, IP: {self.session.address})."
|
||||||
% (account, new_character, self.session.address)
|
|
||||||
)
|
)
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
self.msg("|rYou cannot become |C%s|n: %s" % (new_character.name, exc))
|
self.msg(f"|rYou cannot become |C{new_character.name}|n: {exc}")
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Puppet Failed: %s (Caller: %s, Target: %s, IP: %s)."
|
f"Puppet Failed: %s (Caller: {account}, Target: {new_character}, IP: {self.session.address})."
|
||||||
% (exc, account, new_character, self.session.address)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -432,7 +427,7 @@ class CmdOOC(MuxAccountLookCommand):
|
||||||
self.msg(account.at_look(target=self.playable, session=session))
|
self.msg(account.at_look(target=self.playable, session=session))
|
||||||
|
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
self.msg("|rCould not unpuppet from |c%s|n: %s" % (old_char, exc))
|
self.msg(f"|rCould not unpuppet from |c{old_char}|n: {exc}")
|
||||||
|
|
||||||
|
|
||||||
class CmdSessions(COMMAND_DEFAULT_CLASS):
|
class CmdSessions(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -469,7 +464,7 @@ class CmdSessions(COMMAND_DEFAULT_CLASS):
|
||||||
char and str(char) or "None",
|
char and str(char) or "None",
|
||||||
char and str(char.location) or "N/A",
|
char and str(char.location) or "N/A",
|
||||||
)
|
)
|
||||||
self.msg("|wYour current session(s):|n\n%s" % table)
|
self.msg(f"|wYour current session(s):|n\n{table}")
|
||||||
|
|
||||||
|
|
||||||
class CmdWho(COMMAND_DEFAULT_CLASS):
|
class CmdWho(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -640,7 +635,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
row.append("%s%s" % (saved, changed))
|
row.append("%s%s" % (saved, changed))
|
||||||
table.add_row(*row)
|
table.add_row(*row)
|
||||||
self.msg("|wClient settings (%s):|n\n%s|n" % (self.session.protocol_key, table))
|
self.msg(f"|wClient settings ({self.session.protocol_key}):|n\n{table}|n")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -655,7 +650,7 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
try:
|
try:
|
||||||
codecs_lookup(new_encoding)
|
codecs_lookup(new_encoding)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
raise RuntimeError("The encoding '|w%s|n' is invalid. " % new_encoding)
|
raise RuntimeError(f"The encoding '|w{new_encoding}|n' is invalid. ")
|
||||||
return val
|
return val
|
||||||
|
|
||||||
def validate_size(new_size):
|
def validate_size(new_size):
|
||||||
|
|
@ -670,16 +665,13 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
old_val = flags.get(new_name, False)
|
old_val = flags.get(new_name, False)
|
||||||
new_val = validator(new_val)
|
new_val = validator(new_val)
|
||||||
if old_val == new_val:
|
if old_val == new_val:
|
||||||
self.msg("Option |w%s|n was kept as '|w%s|n'." % (new_name, old_val))
|
self.msg(f"Option |w{new_name}|n was kept as '|w{old_val}|n'.")
|
||||||
else:
|
else:
|
||||||
flags[new_name] = new_val
|
flags[new_name] = new_val
|
||||||
self.msg(
|
self.msg(f"Option |w{new_name}|n was changed from '|w{old_val}|n' to '|w{new_val}|n'.")
|
||||||
"Option |w%s|n was changed from '|w%s|n' to '|w%s|n'."
|
|
||||||
% (new_name, old_val, new_val)
|
|
||||||
)
|
|
||||||
return {new_name: new_val}
|
return {new_name: new_val}
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.msg("|rCould not set option |w%s|r:|n %s" % (new_name, err))
|
self.msg(f"|rCould not set option |w{new_name}|r:|n {err}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
validators = {
|
validators = {
|
||||||
|
|
@ -719,12 +711,12 @@ class CmdOption(COMMAND_DEFAULT_CLASS):
|
||||||
saved_options.update(optiondict)
|
saved_options.update(optiondict)
|
||||||
self.account.attributes.add("_saved_protocol_flags", saved_options)
|
self.account.attributes.add("_saved_protocol_flags", saved_options)
|
||||||
for key in optiondict:
|
for key in optiondict:
|
||||||
self.msg("|gSaved option %s.|n" % key)
|
self.msg(f"|gSaved option {key}.|n")
|
||||||
if "clear" in self.switches:
|
if "clear" in self.switches:
|
||||||
# clear this save
|
# clear this save
|
||||||
for key in optiondict:
|
for key in optiondict:
|
||||||
self.account.attributes.get("_saved_protocol_flags", {}).pop(key, None)
|
self.account.attributes.get("_saved_protocol_flags", {}).pop(key, None)
|
||||||
self.msg("|gCleared saved %s." % key)
|
self.msg(f"|gCleared saved {key}.")
|
||||||
self.session.update_flags(**optiondict)
|
self.session.update_flags(**optiondict)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -767,10 +759,7 @@ class CmdPassword(COMMAND_DEFAULT_CLASS):
|
||||||
account.set_password(newpass)
|
account.set_password(newpass)
|
||||||
account.save()
|
account.save()
|
||||||
self.msg("Password changed.")
|
self.msg("Password changed.")
|
||||||
logger.log_sec(
|
logger.log_sec(f"Password Changed: {account} (Caller: {account}, IP: {self.session.address}).")
|
||||||
"Password Changed: %s (Caller: %s, IP: %s)."
|
|
||||||
% (account, account, self.session.address)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CmdQuit(COMMAND_DEFAULT_CLASS):
|
class CmdQuit(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -997,27 +986,27 @@ class CmdQuell(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
if self.cmdstring in ("unquell", "unquell"):
|
if self.cmdstring in ("unquell", "unquell"):
|
||||||
if not account.attributes.get("_quell"):
|
if not account.attributes.get("_quell"):
|
||||||
self.msg("Already using normal Account permissions %s." % permstr)
|
self.msg(f"Already using normal Account permissions {permstr}.")
|
||||||
else:
|
else:
|
||||||
account.attributes.remove("_quell")
|
account.attributes.remove("_quell")
|
||||||
self.msg("Account permissions %s restored." % permstr)
|
self.msg(f"Account permissions {permstr} restored.")
|
||||||
else:
|
else:
|
||||||
if account.attributes.get("_quell"):
|
if account.attributes.get("_quell"):
|
||||||
self.msg("Already quelling Account %s permissions." % permstr)
|
self.msg(f"Already quelling Account {permstr} permissions.")
|
||||||
return
|
return
|
||||||
account.attributes.add("_quell", True)
|
account.attributes.add("_quell", True)
|
||||||
puppet = self.session.puppet if self.session else None
|
puppet = self.session.puppet if self.session else None
|
||||||
if puppet:
|
if puppet:
|
||||||
cpermstr = "(%s)" % ", ".join(puppet.permissions.all())
|
cpermstr = "(%s)" % ", ".join(puppet.permissions.all())
|
||||||
cpermstr = "Quelling to current puppet's permissions %s." % cpermstr
|
cpermstr = f"Quelling to current puppet's permissions {cpermstr}."
|
||||||
cpermstr += (
|
cpermstr += (
|
||||||
"\n(Note: If this is higher than Account permissions %s,"
|
f"\n(Note: If this is higher than Account permissions {permstr},"
|
||||||
" the lowest of the two will be used.)" % permstr
|
" the lowest of the two will be used.)"
|
||||||
)
|
)
|
||||||
cpermstr += "\nUse unquell to return to normal permission usage."
|
cpermstr += "\nUse unquell to return to normal permission usage."
|
||||||
self.msg(cpermstr)
|
self.msg(cpermstr)
|
||||||
else:
|
else:
|
||||||
self.msg("Quelling Account permissions%s. Use unquell to get them back." % permstr)
|
self.msg(f"Quelling Account permissions{permstr}. Use unquell to get them back.")
|
||||||
self._recache_locks(account)
|
self._recache_locks(account)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1058,4 +1047,4 @@ class CmdStyle(COMMAND_DEFAULT_CLASS):
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.msg(str(e))
|
self.msg(str(e))
|
||||||
return
|
return
|
||||||
self.msg("Style %s set to %s" % (self.lhs, result))
|
self.msg(f"Style {self.lhs} set to {result}")
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,11 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
# Boot by account object
|
# Boot by account object
|
||||||
pobj = search.account_search(args)
|
pobj = search.account_search(args)
|
||||||
if not pobj:
|
if not pobj:
|
||||||
caller.msg("Account %s was not found." % args)
|
caller.msg(f"Account {args} was not found.")
|
||||||
return
|
return
|
||||||
pobj = pobj[0]
|
pobj = pobj[0]
|
||||||
if not pobj.access(caller, "boot"):
|
if not pobj.access(caller, "boot"):
|
||||||
string = "You don't have the permission to boot %s." % (pobj.key,)
|
caller.msg(f"You don't have the permission to boot {pobj.key}.")
|
||||||
caller.msg(string)
|
|
||||||
return
|
return
|
||||||
# we have a bootable object with a connected user
|
# we have a bootable object with a connected user
|
||||||
matches = SESSIONS.sessions_from_account(pobj)
|
matches = SESSIONS.sessions_from_account(pobj)
|
||||||
|
|
@ -96,9 +95,9 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
feedback = None
|
feedback = None
|
||||||
if "quiet" not in self.switches:
|
if "quiet" not in self.switches:
|
||||||
feedback = "You have been disconnected by %s.\n" % caller.name
|
feedback = f"You have been disconnected by {caller.name}.\n"
|
||||||
if reason:
|
if reason:
|
||||||
feedback += "\nReason given: %s" % reason
|
feedback += f"\nReason given: {reason}"
|
||||||
|
|
||||||
for session in boot_list:
|
for session in boot_list:
|
||||||
session.msg(feedback)
|
session.msg(feedback)
|
||||||
|
|
@ -106,8 +105,7 @@ class CmdBoot(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if pobj and boot_list:
|
if pobj and boot_list:
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Booted: %s (Reason: %s, Caller: %s, IP: %s)."
|
f"Booted: {pobj} (Reason: {reason}, Caller: {caller}, IP: {self.session.address})."
|
||||||
% (pobj, reason, caller, self.session.address)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -130,7 +128,7 @@ def list_bans(cmd, banlist):
|
||||||
table = cmd.styled_table("|wid", "|wname/ip", "|wdate", "|wreason")
|
table = cmd.styled_table("|wid", "|wname/ip", "|wdate", "|wreason")
|
||||||
for inum, ban in enumerate(banlist):
|
for inum, ban in enumerate(banlist):
|
||||||
table.add_row(str(inum + 1), ban[0] and ban[0] or ban[1], ban[3], ban[4])
|
table.add_row(str(inum + 1), ban[0] and ban[0] or ban[1], ban[3], ban[4])
|
||||||
return "|wActive bans:|n\n%s" % table
|
return f"|wActive bans:|n\n{table}"
|
||||||
|
|
||||||
|
|
||||||
class CmdBan(COMMAND_DEFAULT_CLASS):
|
class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -227,7 +225,7 @@ class CmdBan(COMMAND_DEFAULT_CLASS):
|
||||||
ServerConfig.objects.conf("server_bans", banlist)
|
ServerConfig.objects.conf("server_bans", banlist)
|
||||||
self.caller.msg(f"{typ}-ban '|w{ban}|n' was added. Use |wunban|n to reinstate.")
|
self.caller.msg(f"{typ}-ban '|w{ban}|n' was added. Use |wunban|n to reinstate.")
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Banned {typ}: {ban.strip()} (Caller: {self.caller}, IP: {self.session.address})."
|
f"Banned {typ}: {ban.strip()} (Caller: {self.caller}, IP: {self.session.address})."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -267,7 +265,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
|
||||||
if not banlist:
|
if not banlist:
|
||||||
self.caller.msg("There are no bans to clear.")
|
self.caller.msg("There are no bans to clear.")
|
||||||
elif not (0 < num < len(banlist) + 1):
|
elif not (0 < num < len(banlist) + 1):
|
||||||
self.caller.msg("Ban id |w%s|x was not found." % self.args)
|
self.caller.msg(f"Ban id |w{self.args}|n was not found.")
|
||||||
else:
|
else:
|
||||||
# all is ok, ask, then clear ban
|
# all is ok, ask, then clear ban
|
||||||
ban = banlist[num - 1]
|
ban = banlist[num - 1]
|
||||||
|
|
@ -282,7 +280,7 @@ class CmdUnban(COMMAND_DEFAULT_CLASS):
|
||||||
ServerConfig.objects.conf("server_bans", banlist)
|
ServerConfig.objects.conf("server_bans", banlist)
|
||||||
self.caller.msg(f"Cleared ban {num}: '{value}'")
|
self.caller.msg(f"Cleared ban {num}: '{value}'")
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Unbanned: {value.strip()} (Caller: {self.caller}, IP: {self.session.address})."
|
f"Unbanned: {value.strip()} (Caller: {self.caller}, IP: {self.session.address})."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -351,20 +349,20 @@ class CmdEmit(COMMAND_DEFAULT_CLASS):
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
if rooms_only and obj.location is not None:
|
if rooms_only and obj.location is not None:
|
||||||
caller.msg("%s is not a room. Ignored." % objname)
|
caller.msg(f"{objname} is not a room. Ignored.")
|
||||||
continue
|
continue
|
||||||
if accounts_only and not obj.has_account:
|
if accounts_only and not obj.has_account:
|
||||||
caller.msg("%s has no active account. Ignored." % objname)
|
caller.msg(f"{objname} has no active account. Ignored.")
|
||||||
continue
|
continue
|
||||||
if obj.access(caller, "tell"):
|
if obj.access(caller, "tell"):
|
||||||
obj.msg(message)
|
obj.msg(message)
|
||||||
if send_to_contents and hasattr(obj, "msg_contents"):
|
if send_to_contents and hasattr(obj, "msg_contents"):
|
||||||
obj.msg_contents(message)
|
obj.msg_contents(message)
|
||||||
caller.msg("Emitted to %s and contents:\n%s" % (objname, message))
|
caller.msg(f"Emitted to {objname} and contents:\n{message}")
|
||||||
else:
|
else:
|
||||||
caller.msg("Emitted to %s:\n%s" % (objname, message))
|
caller.msg(f"Emitted to {objname}:\n{message}")
|
||||||
else:
|
else:
|
||||||
caller.msg("You are not allowed to emit to %s." % objname)
|
caller.msg(f"You are not allowed to emit to {objname}.")
|
||||||
|
|
||||||
|
|
||||||
class CmdNewPassword(COMMAND_DEFAULT_CLASS):
|
class CmdNewPassword(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -407,11 +405,11 @@ class CmdNewPassword(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
account.set_password(newpass)
|
account.set_password(newpass)
|
||||||
account.save()
|
account.save()
|
||||||
self.msg("%s - new password set to '%s'." % (account.name, newpass))
|
self.msg(f"{account.name} - new password set to '{newpass}'.")
|
||||||
if account.character != caller:
|
if account.character != caller:
|
||||||
account.msg("%s has changed your password to '%s'." % (caller.name, newpass))
|
account.msg(f"{caller.name} has changed your password to '{newpass}'.")
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Password Changed: %s (Caller: %s, IP: %s)." % (account, caller, self.session.address)
|
f"Password Changed: {account} (Caller: {caller}, IP: {self.session.address})."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -464,7 +462,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg("You are not allowed to examine this object.")
|
caller.msg("You are not allowed to examine this object.")
|
||||||
return
|
return
|
||||||
|
|
||||||
string = "Permissions on |w%s|n: " % obj.key
|
string = f"Permissions on |{obj.key}|n: "
|
||||||
if not obj.permissions.all():
|
if not obj.permissions.all():
|
||||||
string += "<None>"
|
string += "<None>"
|
||||||
else:
|
else:
|
||||||
|
|
@ -482,10 +480,8 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
# we supplied an argument on the form obj = perm
|
# we supplied an argument on the form obj = perm
|
||||||
locktype = "edit" if accountmode else "control"
|
locktype = "edit" if accountmode else "control"
|
||||||
if not obj.access(caller, locktype):
|
if not obj.access(caller, locktype):
|
||||||
caller.msg(
|
accountstr = 'account' if accountmode else 'object'
|
||||||
"You are not allowed to edit this %s's permissions."
|
caller.msg(f"You are not allowed to edit this {accountstr}'s permissions.")
|
||||||
% ("account" if accountmode else "object")
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
caller_result = []
|
caller_result = []
|
||||||
|
|
@ -496,18 +492,17 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
obj.permissions.remove(perm)
|
obj.permissions.remove(perm)
|
||||||
if obj.permissions.get(perm):
|
if obj.permissions.get(perm):
|
||||||
caller_result.append(
|
caller_result.append(
|
||||||
"\nPermissions %s could not be removed from %s." % (perm, obj.name)
|
f"\nPermissions {perm} could not be removed from {obj.name}."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
caller_result.append(
|
caller_result.append(
|
||||||
"\nPermission %s removed from %s (if they existed)." % (perm, obj.name)
|
f"\nPermission {perm} removed from {obj.name} (if they existed)."
|
||||||
)
|
)
|
||||||
target_result.append(
|
target_result.append(
|
||||||
"\n%s revokes the permission(s) %s from you." % (caller.name, perm)
|
f"\n{caller.name} revokes the permission(s) {perm} from you."
|
||||||
)
|
)
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Permissions Deleted: %s, %s (Caller: %s, IP: %s)."
|
f"Permissions Deleted: {perm}, {obj} (Caller: {caller}, IP: {self.session.address})."
|
||||||
% (perm, obj, caller, self.session.address)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# add a new permission
|
# add a new permission
|
||||||
|
|
@ -518,7 +513,7 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
# don't allow to set a permission higher in the hierarchy than
|
# don't allow to set a permission higher in the hierarchy than
|
||||||
# the one the caller has (to prevent self-escalation)
|
# the one the caller has (to prevent self-escalation)
|
||||||
if perm.lower() in PERMISSION_HIERARCHY and not obj.locks.check_lockstring(
|
if perm.lower() in PERMISSION_HIERARCHY and not obj.locks.check_lockstring(
|
||||||
caller, "dummy:perm(%s)" % perm
|
caller, f"dummy:perm({perm})"
|
||||||
):
|
):
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"You cannot assign a permission higher than the one you have yourself."
|
"You cannot assign a permission higher than the one you have yourself."
|
||||||
|
|
@ -527,21 +522,19 @@ class CmdPerm(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
if perm in permissions:
|
if perm in permissions:
|
||||||
caller_result.append(
|
caller_result.append(
|
||||||
"\nPermission '%s' is already defined on %s." % (perm, obj.name)
|
f"\nPermission '{perm}' is already defined on {obj.name}."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
obj.permissions.add(perm)
|
obj.permissions.add(perm)
|
||||||
plystring = "the Account" if accountmode else "the Object/Character"
|
plystring = "the Account" if accountmode else "the Object/Character"
|
||||||
caller_result.append(
|
caller_result.append(
|
||||||
"\nPermission '%s' given to %s (%s)." % (perm, obj.name, plystring)
|
f"\nPermission '{perm}' given to {obj.name} ({plystring})."
|
||||||
)
|
)
|
||||||
target_result.append(
|
target_result.append(
|
||||||
"\n%s gives you (%s, %s) the permission '%s'."
|
f"\n{caller.name} gives you ({obj.name}, {plystring}) the permission '{perm}'."
|
||||||
% (caller.name, obj.name, plystring, perm)
|
|
||||||
)
|
)
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Permissions Added: %s, %s (Caller: %s, IP: %s)."
|
f"Permissions Added: {perm}, {obj} (Caller: {caller}, IP: {self.session.address})."
|
||||||
% (obj, perm, caller, self.session.address)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
caller.msg("".join(caller_result).strip())
|
caller.msg("".join(caller_result).strip())
|
||||||
|
|
@ -569,7 +562,7 @@ class CmdWall(COMMAND_DEFAULT_CLASS):
|
||||||
if not self.args:
|
if not self.args:
|
||||||
self.caller.msg("Usage: wall <message>")
|
self.caller.msg("Usage: wall <message>")
|
||||||
return
|
return
|
||||||
message = '%s shouts "%s"' % (self.caller.name, self.args)
|
message = f'{self.caller.name} shouts "{self.args}"'
|
||||||
self.msg("Announcing to all connected sessions ...")
|
self.msg("Announcing to all connected sessions ...")
|
||||||
SESSIONS.announce_all(message)
|
SESSIONS.announce_all(message)
|
||||||
|
|
||||||
|
|
@ -599,7 +592,7 @@ class CmdForce(COMMAND_DEFAULT_CLASS):
|
||||||
if not targ:
|
if not targ:
|
||||||
return
|
return
|
||||||
if not targ.access(self.caller, self.perm_used):
|
if not targ.access(self.caller, self.perm_used):
|
||||||
self.caller.msg("You don't have permission to force them to execute commands.")
|
self.caller.msg(f"You don't have permission to force {targ} to execute commands.")
|
||||||
return
|
return
|
||||||
targ.execute_cmd(self.rhs)
|
targ.execute_cmd(self.rhs)
|
||||||
self.caller.msg("You have forced %s to: %s" % (targ, self.rhs))
|
self.caller.msg(f"You have forced {targ} to: {self.rhs}")
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ class CmdSetObjAlias(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
caller.msg("No aliases exist for '%s'." % obj.get_display_name(caller))
|
caller.msg(f"No aliases exist for '{obj.get_display_name(caller)}'.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
|
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
|
||||||
|
|
@ -350,13 +350,9 @@ class CmdCopy(ObjManipCommand):
|
||||||
new_aliases=to_obj_aliases,
|
new_aliases=to_obj_aliases,
|
||||||
)
|
)
|
||||||
if copiedobj:
|
if copiedobj:
|
||||||
string = "Copied %s to '%s' (aliases: %s)." % (
|
string = f"Copied {from_obj_name} to '{to_obj_name}' (aliases: {to_obj_aliases})."
|
||||||
from_obj_name,
|
|
||||||
to_obj_name,
|
|
||||||
to_obj_aliases,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
string = "There was an error copying %s to '%s'." % (from_obj_name, to_obj_name)
|
string = f"There was an error copying {from_obj_name} to '{to_obj_name}'."
|
||||||
# we are done, echo to user
|
# we are done, echo to user
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
@ -415,7 +411,7 @@ class CmdCpAttr(ObjManipCommand):
|
||||||
required and verify an object has an attribute.
|
required and verify an object has an attribute.
|
||||||
"""
|
"""
|
||||||
if not obj.attributes.has(attr):
|
if not obj.attributes.has(attr):
|
||||||
self.caller.msg("%s doesn't have an attribute %s." % (obj.name, attr))
|
self.caller.msg(f"{obj.name} doesn't have an attribute {attr}.")
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
@ -479,7 +475,7 @@ class CmdCpAttr(ObjManipCommand):
|
||||||
to_obj_attrs = to_obj["attrs"]
|
to_obj_attrs = to_obj["attrs"]
|
||||||
to_obj = caller.search(to_obj_name)
|
to_obj = caller.search(to_obj_name)
|
||||||
if not to_obj:
|
if not to_obj:
|
||||||
result.append("\nCould not find object '%s'" % to_obj_name)
|
result.append(f"\nCould not find object '{to_obj_name}'")
|
||||||
continue
|
continue
|
||||||
for inum, from_attr in enumerate(from_obj_attrs):
|
for inum, from_attr in enumerate(from_obj_attrs):
|
||||||
try:
|
try:
|
||||||
|
|
@ -495,13 +491,11 @@ class CmdCpAttr(ObjManipCommand):
|
||||||
if clear and not (from_obj == to_obj and from_attr == to_attr):
|
if clear and not (from_obj == to_obj and from_attr == to_attr):
|
||||||
from_obj.attributes.remove(from_attr)
|
from_obj.attributes.remove(from_attr)
|
||||||
result.append(
|
result.append(
|
||||||
"\nMoved %s.%s -> %s.%s. (value: %s)"
|
f"\nMoved {from_obj.name}.{from_attr} -> {to_obj_name}.{to_attr}. (value: {repr(value)})"
|
||||||
% (from_obj.name, from_attr, to_obj_name, to_attr, repr(value))
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result.append(
|
result.append(
|
||||||
"\nCopied %s.%s -> %s.%s. (value: %s)"
|
f"\nCopied {from_obj.name}.{from_attr} -> {to_obj.name}.{to_attr}. (value: {repr(value)})"
|
||||||
% (from_obj.name, from_attr, to_obj_name, to_attr, repr(value))
|
|
||||||
)
|
)
|
||||||
caller.msg("".join(result))
|
caller.msg("".join(result))
|
||||||
|
|
||||||
|
|
@ -615,11 +609,9 @@ class CmdCreate(ObjManipCommand):
|
||||||
if not obj:
|
if not obj:
|
||||||
continue
|
continue
|
||||||
if aliases:
|
if aliases:
|
||||||
string = "You create a new %s: %s (aliases: %s)."
|
string = f"You create a new {obj.typename}: {obj.name} (aliases: {', '.join(aliases)})."
|
||||||
string = string % (obj.typename, obj.name, ", ".join(aliases))
|
|
||||||
else:
|
else:
|
||||||
string = "You create a new %s: %s."
|
string = f"You create a new {obj.typename}: {obj.name}."
|
||||||
string = string % (obj.typename, obj.name)
|
|
||||||
# set a default desc
|
# set a default desc
|
||||||
if not obj.db.desc:
|
if not obj.db.desc:
|
||||||
obj.db.desc = "You see nothing special."
|
obj.db.desc = "You see nothing special."
|
||||||
|
|
@ -681,7 +673,7 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
||||||
self.caller.msg("You don't have permission to edit the description of %s." % obj.key)
|
self.caller.msg(f"You don't have permission to edit the description of {obj.key}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.caller.db.evmenu_target = obj
|
self.caller.db.evmenu_target = obj
|
||||||
|
|
@ -721,9 +713,9 @@ class CmdDesc(COMMAND_DEFAULT_CLASS):
|
||||||
desc = self.args
|
desc = self.args
|
||||||
if obj.access(self.caller, "control") or obj.access(self.caller, "edit"):
|
if obj.access(self.caller, "control") or obj.access(self.caller, "edit"):
|
||||||
obj.db.desc = desc
|
obj.db.desc = desc
|
||||||
caller.msg("The description was set on %s." % obj.get_display_name(caller))
|
caller.msg(f"The description was set on {obj.get_display_name(caller)}.")
|
||||||
else:
|
else:
|
||||||
caller.msg("You don't have permission to edit the description of %s." % obj.key)
|
caller.msg(f"You don't have permission to edit the description of {obj.key}.")
|
||||||
|
|
||||||
|
|
||||||
class CmdDestroy(COMMAND_DEFAULT_CLASS):
|
class CmdDestroy(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
@ -771,21 +763,20 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
|
||||||
# helper function for deleting a single object
|
# helper function for deleting a single object
|
||||||
string = ""
|
string = ""
|
||||||
if not obj.pk:
|
if not obj.pk:
|
||||||
string = "\nObject %s was already deleted." % obj.db_key
|
string = f"\nObject {obj.db_key} was already deleted."
|
||||||
else:
|
else:
|
||||||
objname = obj.name
|
objname = obj.name
|
||||||
if not (obj.access(caller, "control") or obj.access(caller, "delete")):
|
if not (obj.access(caller, "control") or obj.access(caller, "delete")):
|
||||||
return "\nYou don't have permission to delete %s." % objname
|
return f"\nYou don't have permission to delete {objname}."
|
||||||
if obj.account and "override" not in self.switches:
|
if obj.account and "override" not in self.switches:
|
||||||
return (
|
return (
|
||||||
"\nObject %s is controlled by an active account. Use /override to delete anyway."
|
f"\nObject {objname} is controlled by an active account. Use /override to delete anyway."
|
||||||
% objname
|
|
||||||
)
|
)
|
||||||
if obj.dbid == int(settings.DEFAULT_HOME.lstrip("#")):
|
if obj.dbid == int(settings.DEFAULT_HOME.lstrip("#")):
|
||||||
return (
|
return (
|
||||||
"\nYou are trying to delete |c%s|n, which is set as DEFAULT_HOME. "
|
f"\nYou are trying to delete |c{objname}|n, which is set as DEFAULT_HOME. "
|
||||||
"Re-point settings.DEFAULT_HOME to another "
|
"Re-point settings.DEFAULT_HOME to another "
|
||||||
"object before continuing." % objname
|
"object before continuing."
|
||||||
)
|
)
|
||||||
|
|
||||||
had_exits = hasattr(obj, "exits") and obj.exits
|
had_exits = hasattr(obj, "exits") and obj.exits
|
||||||
|
|
@ -798,15 +789,14 @@ class CmdDestroy(COMMAND_DEFAULT_CLASS):
|
||||||
okay = obj.delete()
|
okay = obj.delete()
|
||||||
if not okay:
|
if not okay:
|
||||||
string += (
|
string += (
|
||||||
"\nERROR: %s not deleted, probably because delete() returned False."
|
f"\nERROR: {objname} not deleted, probably because delete() returned False."
|
||||||
% objname
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
string += "\n%s was destroyed." % objname
|
string += f"\n{objname} was destroyed."
|
||||||
if had_exits:
|
if had_exits:
|
||||||
string += " Exits to and from %s were destroyed as well." % objname
|
string += f" Exits to and from {objname} were destroyed as well."
|
||||||
if had_objs:
|
if had_objs:
|
||||||
string += " Objects inside %s were moved to their homes." % objname
|
string += f" Objects inside {objname} were moved to their homes."
|
||||||
return string
|
return string
|
||||||
|
|
||||||
objs = []
|
objs = []
|
||||||
|
|
@ -936,12 +926,7 @@ class CmdDig(ObjManipCommand):
|
||||||
alias_string = ""
|
alias_string = ""
|
||||||
if new_room.aliases.all():
|
if new_room.aliases.all():
|
||||||
alias_string = " (%s)" % ", ".join(new_room.aliases.all())
|
alias_string = " (%s)" % ", ".join(new_room.aliases.all())
|
||||||
room_string = "Created room %s(%s)%s of type %s." % (
|
room_string = f"Created room {new_room}({new_room.dbref}){alias_string} of type {typeclass}."
|
||||||
new_room,
|
|
||||||
new_room.dbref,
|
|
||||||
alias_string,
|
|
||||||
typeclass,
|
|
||||||
)
|
|
||||||
|
|
||||||
# create exit to room
|
# create exit to room
|
||||||
|
|
||||||
|
|
@ -972,14 +957,7 @@ class CmdDig(ObjManipCommand):
|
||||||
alias_string = ""
|
alias_string = ""
|
||||||
if new_to_exit.aliases.all():
|
if new_to_exit.aliases.all():
|
||||||
alias_string = " (%s)" % ", ".join(new_to_exit.aliases.all())
|
alias_string = " (%s)" % ", ".join(new_to_exit.aliases.all())
|
||||||
exit_to_string = "\nCreated Exit from %s to %s: %s(%s)%s."
|
exit_to_string = "\nCreated Exit from {location.name} to {new_room.name}: {new_to_exit}({new_to_exit.dbref}){alias_string}."
|
||||||
exit_to_string = exit_to_string % (
|
|
||||||
location.name,
|
|
||||||
new_room.name,
|
|
||||||
new_to_exit,
|
|
||||||
new_to_exit.dbref,
|
|
||||||
alias_string,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create exit back from new room
|
# Create exit back from new room
|
||||||
|
|
||||||
|
|
@ -1006,15 +984,8 @@ class CmdDig(ObjManipCommand):
|
||||||
alias_string = ""
|
alias_string = ""
|
||||||
if new_back_exit.aliases.all():
|
if new_back_exit.aliases.all():
|
||||||
alias_string = " (%s)" % ", ".join(new_back_exit.aliases.all())
|
alias_string = " (%s)" % ", ".join(new_back_exit.aliases.all())
|
||||||
exit_back_string = "\nCreated Exit back from %s to %s: %s(%s)%s."
|
exit_back_string = f"\nCreated Exit back from {new_room.name} to {location.name}: {new_back_exit}({new_back_exit.dbref}){alias_string}."
|
||||||
exit_back_string = exit_back_string % (
|
caller.msg(f"{room_string}{exit_to_string}{exit_back_string}")
|
||||||
new_room.name,
|
|
||||||
location.name,
|
|
||||||
new_back_exit,
|
|
||||||
new_back_exit.dbref,
|
|
||||||
alias_string,
|
|
||||||
)
|
|
||||||
caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string))
|
|
||||||
if new_room and "teleport" in self.switches:
|
if new_room and "teleport" in self.switches:
|
||||||
caller.move_to(new_room, move_type="teleport")
|
caller.move_to(new_room, move_type="teleport")
|
||||||
|
|
||||||
|
|
@ -1112,10 +1083,10 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
|
||||||
telswitch = "/teleport"
|
telswitch = "/teleport"
|
||||||
backstring = ""
|
backstring = ""
|
||||||
if "oneway" not in self.switches:
|
if "oneway" not in self.switches:
|
||||||
backstring = ", %s;%s" % (backname, backshort)
|
backstring = f", {backname};{backshort}"
|
||||||
|
|
||||||
# build the string we will use to call dig
|
# build the string we will use to call dig
|
||||||
digstring = "dig%s %s = %s;%s%s" % (telswitch, roomname, exitname, exitshort, backstring)
|
digstring = f"dig{telswitch} {roomname} = {exitname};{exitshort}{backstring}"
|
||||||
self.execute_cmd(digstring)
|
self.execute_cmd(digstring)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1182,10 +1153,7 @@ class CmdLink(COMMAND_DEFAULT_CLASS):
|
||||||
string = note % (obj.name, obj.dbref)
|
string = note % (obj.name, obj.dbref)
|
||||||
if "twoway" in self.switches:
|
if "twoway" in self.switches:
|
||||||
if not (target.location and obj.location):
|
if not (target.location and obj.location):
|
||||||
string = "To create a two-way link, %s and %s must both have a location" % (
|
string = f"To create a two-way link, {obj} and {target} must both have a location"
|
||||||
obj,
|
|
||||||
target,
|
|
||||||
)
|
|
||||||
string += " (i.e. they cannot be rooms, but should be exits)."
|
string += " (i.e. they cannot be rooms, but should be exits)."
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
@ -1193,15 +1161,10 @@ class CmdLink(COMMAND_DEFAULT_CLASS):
|
||||||
string += note % (target.name, target.dbref)
|
string += note % (target.name, target.dbref)
|
||||||
obj.destination = target.location
|
obj.destination = target.location
|
||||||
target.destination = obj.location
|
target.destination = obj.location
|
||||||
string += "\nLink created %s (in %s) <-> %s (in %s) (two-way)." % (
|
string += f"\nLink created {obj.name} (in {obj.location}) <-> {target.name} (in {target.location}) (two-way)."
|
||||||
obj.name,
|
|
||||||
obj.location,
|
|
||||||
target.name,
|
|
||||||
target.location,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
obj.destination = target
|
obj.destination = target
|
||||||
string += "\nLink created %s -> %s (one way)." % (obj.name, target)
|
string += f"\nLink created {obj.name} -> {target} (one way)."
|
||||||
|
|
||||||
elif self.rhs is None:
|
elif self.rhs is None:
|
||||||
# this means that no = was given (otherwise rhs
|
# this means that no = was given (otherwise rhs
|
||||||
|
|
@ -1209,18 +1172,18 @@ class CmdLink(COMMAND_DEFAULT_CLASS):
|
||||||
# the home/destination on object
|
# the home/destination on object
|
||||||
dest = obj.destination
|
dest = obj.destination
|
||||||
if dest:
|
if dest:
|
||||||
string = "%s is an exit to %s." % (obj.name, dest.name)
|
string = f"{obj.name} is an exit to {dest.name}."
|
||||||
else:
|
else:
|
||||||
string = "%s is not an exit. Its home location is %s." % (obj.name, obj.home)
|
string = f"{obj.name} is not an exit. Its home location is {obj.home}."
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# We gave the command link 'obj = ' which means we want to
|
# We gave the command link 'obj = ' which means we want to
|
||||||
# clear destination.
|
# clear destination.
|
||||||
if obj.destination:
|
if obj.destination:
|
||||||
obj.destination = None
|
obj.destination = None
|
||||||
string = "Former exit %s no longer links anywhere." % obj.name
|
string = f"Former exit {obj.name} no longer links anywhere."
|
||||||
else:
|
else:
|
||||||
string = "%s had no destination to unlink." % obj.name
|
string = f"{obj.name} had no destination to unlink."
|
||||||
# give feedback
|
# give feedback
|
||||||
caller.msg(string.strip())
|
caller.msg(string.strip())
|
||||||
|
|
||||||
|
|
@ -1297,7 +1260,7 @@ class CmdSetHome(CmdLink):
|
||||||
if not home:
|
if not home:
|
||||||
string = "This object has no home location set!"
|
string = "This object has no home location set!"
|
||||||
else:
|
else:
|
||||||
string = "%s's current home is %s(%s)." % (obj, home, home.dbref)
|
string = f"{obj}'s current home is {home}({home.dbref})."
|
||||||
else:
|
else:
|
||||||
# set a home location
|
# set a home location
|
||||||
new_home = self.caller.search(self.rhs, global_search=True)
|
new_home = self.caller.search(self.rhs, global_search=True)
|
||||||
|
|
@ -1306,15 +1269,9 @@ class CmdSetHome(CmdLink):
|
||||||
old_home = obj.home
|
old_home = obj.home
|
||||||
obj.home = new_home
|
obj.home = new_home
|
||||||
if old_home:
|
if old_home:
|
||||||
string = "Home location of %s was changed from %s(%s) to %s(%s)." % (
|
string = f"Home location of {obj} was changed from {old_home}({old_home.dbref} to {new_home}({new_home.dbref})."
|
||||||
obj,
|
|
||||||
old_home,
|
|
||||||
old_home.dbref,
|
|
||||||
new_home,
|
|
||||||
new_home.dbref,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
string = "Home location of %s was set to %s(%s)." % (obj, new_home, new_home.dbref)
|
string = f"Home location of {obj} was set to {new_home}({new_home.dbref})."
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1343,7 +1300,7 @@ class CmdListCmdSets(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
obj = caller
|
obj = caller
|
||||||
string = "%s" % obj.cmdset
|
string = f"{obj.cmdset}"
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1387,11 +1344,11 @@ class CmdName(ObjManipCommand):
|
||||||
caller.msg("No name defined!")
|
caller.msg("No name defined!")
|
||||||
return
|
return
|
||||||
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
|
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
|
||||||
caller.msg("You don't have right to edit this account %s." % obj)
|
caller.msg(f"You don't have right to edit this account {obj}.")
|
||||||
return
|
return
|
||||||
obj.username = newname
|
obj.username = newname
|
||||||
obj.save()
|
obj.save()
|
||||||
caller.msg("Account's name changed to '%s'." % newname)
|
caller.msg(f"Account's name changed to '{newname}'.")
|
||||||
return
|
return
|
||||||
# object search, also with *
|
# object search, also with *
|
||||||
obj = caller.search(objname)
|
obj = caller.search(objname)
|
||||||
|
|
@ -1407,7 +1364,7 @@ class CmdName(ObjManipCommand):
|
||||||
caller.msg("No names or aliases defined!")
|
caller.msg("No names or aliases defined!")
|
||||||
return
|
return
|
||||||
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
|
if not (obj.access(caller, "control") or obj.access(caller, "edit")):
|
||||||
caller.msg("You don't have the right to edit %s." % obj)
|
caller.msg(f"You don't have the right to edit {obj}.")
|
||||||
return
|
return
|
||||||
# change the name and set aliases:
|
# change the name and set aliases:
|
||||||
if newname:
|
if newname:
|
||||||
|
|
@ -1419,7 +1376,7 @@ class CmdName(ObjManipCommand):
|
||||||
# fix for exits - we need their exit-command to change name too
|
# fix for exits - we need their exit-command to change name too
|
||||||
if obj.destination:
|
if obj.destination:
|
||||||
obj.flush_from_cache(force=True)
|
obj.flush_from_cache(force=True)
|
||||||
caller.msg("Object's name changed to '%s'%s." % (newname, astring))
|
caller.msg(f"Object's name changed to '{newname}'{astring}.")
|
||||||
|
|
||||||
|
|
||||||
class CmdOpen(ObjManipCommand):
|
class CmdOpen(ObjManipCommand):
|
||||||
|
|
@ -1463,25 +1420,20 @@ class CmdOpen(ObjManipCommand):
|
||||||
exit_obj = exit_obj[0]
|
exit_obj = exit_obj[0]
|
||||||
if not exit_obj.destination:
|
if not exit_obj.destination:
|
||||||
# we are trying to link a non-exit
|
# we are trying to link a non-exit
|
||||||
string = "'%s' already exists and is not an exit!\nIf you want to convert it "
|
caller.msg(f"'{exit_name}' already exists and is not an exit!\nIf you want to convert it "
|
||||||
string += (
|
"to an exit, you must assign an object to the 'destination' property first."
|
||||||
"to an exit, you must assign an object to the 'destination' property first."
|
)
|
||||||
)
|
|
||||||
caller.msg(string % exit_name)
|
|
||||||
return None
|
return None
|
||||||
# we are re-linking an old exit.
|
# we are re-linking an old exit.
|
||||||
old_destination = exit_obj.destination
|
old_destination = exit_obj.destination
|
||||||
if old_destination:
|
if old_destination:
|
||||||
string = "Exit %s already exists." % exit_name
|
string = f"Exit {exit_name} already exists."
|
||||||
if old_destination.id != destination.id:
|
if old_destination.id != destination.id:
|
||||||
# reroute the old exit.
|
# reroute the old exit.
|
||||||
exit_obj.destination = destination
|
exit_obj.destination = destination
|
||||||
if exit_aliases:
|
if exit_aliases:
|
||||||
[exit_obj.aliases.add(alias) for alias in exit_aliases]
|
[exit_obj.aliases.add(alias) for alias in exit_aliases]
|
||||||
string += " Rerouted its old destination '%s' to '%s' and changed aliases." % (
|
string += f" Rerouted its old destination '{old_destination.name}' to '{destination.name}' and changed aliases."
|
||||||
old_destination.name,
|
|
||||||
destination.name,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
string += " It already points to the correct place."
|
string += " It already points to the correct place."
|
||||||
|
|
||||||
|
|
@ -1506,14 +1458,9 @@ class CmdOpen(ObjManipCommand):
|
||||||
if not exit_aliases
|
if not exit_aliases
|
||||||
else " (aliases: %s)" % (", ".join([str(e) for e in exit_aliases]))
|
else " (aliases: %s)" % (", ".join([str(e) for e in exit_aliases]))
|
||||||
)
|
)
|
||||||
string = "Created new Exit '%s' from %s to %s%s." % (
|
string = f"Created new Exit '{exit_name}' from {location.name} to {destination.name}{string}."
|
||||||
exit_name,
|
|
||||||
location.name,
|
|
||||||
destination.name,
|
|
||||||
string,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
string = "Error: Exit '%s' not created." % exit_name
|
string = f"Error: Exit '{exit.name}' not created."
|
||||||
# emit results
|
# emit results
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return exit_obj
|
return exit_obj
|
||||||
|
|
@ -1596,13 +1543,13 @@ def _convert_from_string(cmd, strobj):
|
||||||
# treat as string
|
# treat as string
|
||||||
strobj = utils.to_str(strobj)
|
strobj = utils.to_str(strobj)
|
||||||
string = (
|
string = (
|
||||||
'|RNote: name "|r%s|R" was converted to a string. '
|
f'|RNote: name "|r{strobj}|R" was converted to a string. '
|
||||||
"Make sure this is acceptable." % strobj
|
"Make sure this is acceptable."
|
||||||
)
|
)
|
||||||
cmd.caller.msg(string)
|
cmd.caller.msg(string)
|
||||||
return strobj
|
return strobj
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
string = "|RUnknown error in evaluating Attribute: {}".format(err)
|
string = f"|RUnknown error in evaluating Attribute: {err}"
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1853,7 +1800,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
def save(caller, buf):
|
def save(caller, buf):
|
||||||
"""Called when editor saves its buffer."""
|
"""Called when editor saves its buffer."""
|
||||||
obj.attributes.add(attr, buf)
|
obj.attributes.add(attr, buf)
|
||||||
caller.msg("Saved Attribute %s." % attr)
|
caller.msg(f"Saved Attribute {attr}.")
|
||||||
|
|
||||||
# check non-strings before activating editor
|
# check non-strings before activating editor
|
||||||
try:
|
try:
|
||||||
|
|
@ -1931,7 +1878,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
if "edit" in self.switches:
|
if "edit" in self.switches:
|
||||||
# edit in the line editor
|
# edit in the line editor
|
||||||
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
||||||
caller.msg("You don't have permission to edit %s." % obj.key)
|
caller.msg(f"You don't have permission to edit {obj.key}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if len(attrs) > 1:
|
if len(attrs) > 1:
|
||||||
|
|
@ -1963,7 +1910,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
else:
|
else:
|
||||||
# deleting the attribute(s)
|
# deleting the attribute(s)
|
||||||
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
||||||
caller.msg("You don't have permission to edit %s." % obj.key)
|
caller.msg(f"You don't have permission to edit {obj.key}.")
|
||||||
return
|
return
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
if not self.check_attr(obj, attr, category):
|
if not self.check_attr(obj, attr, category):
|
||||||
|
|
@ -1982,7 +1929,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
)
|
)
|
||||||
|
|
||||||
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
if not (obj.access(self.caller, "control") or obj.access(self.caller, "edit")):
|
||||||
caller.msg("You don't have permission to edit %s." % obj.key)
|
caller.msg(f"You don't have permission to edit {obj.key}.")
|
||||||
return
|
return
|
||||||
for attr in attrs:
|
for attr in attrs:
|
||||||
if not self.check_attr(obj, attr, category):
|
if not self.check_attr(obj, attr, category):
|
||||||
|
|
@ -1997,7 +1944,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
or parsed_value.access(self.caller, "edit")
|
or parsed_value.access(self.caller, "edit")
|
||||||
):
|
):
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"You don't have permission to set " f"object with identifier '{value}'."
|
f"You don't have permission to set object with identifier '{value}'."
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
value = parsed_value
|
value = parsed_value
|
||||||
|
|
@ -2166,21 +2113,17 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif not matches:
|
elif not matches:
|
||||||
caller.msg("No object or typeclass path found to match '{}'".format(oquery))
|
caller.msg(f"No object or typeclass path found to match '{oquery}'")
|
||||||
else:
|
else:
|
||||||
# one match found
|
# one match found
|
||||||
caller.msg(
|
caller.msg(f"Docstring for typeclass '{oquery}': \n{matches[0][1].__doc__}")
|
||||||
"Docstring for typeclass '{}':\n{}".format(oquery, matches[0][1].__doc__)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# do the search again to get the error handling in case of multi-match
|
# do the search again to get the error handling in case of multi-match
|
||||||
obj = caller.search(oquery)
|
obj = caller.search(oquery)
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"{}'s current typeclass is '{}.{}'".format(
|
f"{obj.name}'s current typeclass is '{obj.__class__.__module__}.{obj.__class__.__name__}'"
|
||||||
obj.name, obj.__class__.__module__, obj.__class__.__name__
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -2211,14 +2154,13 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
prototype = prototype[0]
|
prototype = prototype[0]
|
||||||
else:
|
else:
|
||||||
# no match
|
# no match
|
||||||
caller.msg("No prototype '{}' was found.".format(key))
|
caller.msg(f"No prototype '{key}' was found.")
|
||||||
return
|
return
|
||||||
new_typeclass = prototype["typeclass"]
|
new_typeclass = prototype["typeclass"]
|
||||||
self.switches.append("force")
|
self.switches.append("force")
|
||||||
|
|
||||||
if "show" in self.switches or "examine" in self.switches:
|
if "show" in self.switches or "examine" in self.switches:
|
||||||
string = "%s's current typeclass is %s." % (obj.name, obj.__class__)
|
caller.msg(f"{obj.name}'s current typeclass is '{obj.__class__}'")
|
||||||
caller.msg(string)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.cmdstring in ("swap", "@swap"):
|
if self.cmdstring in ("swap", "@swap"):
|
||||||
|
|
@ -2267,8 +2209,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
diff, _ = spawner.prototype_diff_from_object(prototype, obj)
|
diff, _ = spawner.prototype_diff_from_object(prototype, obj)
|
||||||
txt = spawner.format_diff(diff)
|
txt = spawner.format_diff(diff)
|
||||||
prompt = (
|
prompt = (
|
||||||
"Applying prototype '%s' over '%s' will cause the follow changes:\n%s\n"
|
f"Applying prototype '{prototype['key']}' over '{obj.name}' will cause the follow changes:\n{txt}\n"
|
||||||
% (prototype["key"], obj.name, txt)
|
|
||||||
)
|
)
|
||||||
if not reset:
|
if not reset:
|
||||||
prompt += "\n|yWARNING:|n Use the /reset switch to apply the prototype over a blank state."
|
prompt += "\n|yWARNING:|n Use the /reset switch to apply the prototype over a blank state."
|
||||||
|
|
@ -2289,16 +2230,12 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
prototype_success = modified > 0
|
prototype_success = modified > 0
|
||||||
if not prototype_success:
|
if not prototype_success:
|
||||||
caller.msg("Prototype %s failed to apply." % prototype["key"])
|
caller.msg(f"Prototype {prototype['key']} failed to apply.")
|
||||||
|
|
||||||
if is_same:
|
if is_same:
|
||||||
string = "%s updated its existing typeclass (%s).\n" % (obj.name, obj.path)
|
string = f"{obj.name} updated its existing typeclass ({obj.path}).\n"
|
||||||
else:
|
else:
|
||||||
string = "%s changed typeclass from %s to %s.\n" % (
|
string = f"{obj.name} changed typeclass from {old_typeclass_path} to {obj.typeclass_path}.\n"
|
||||||
obj.name,
|
|
||||||
old_typeclass_path,
|
|
||||||
obj.typeclass_path,
|
|
||||||
)
|
|
||||||
if update:
|
if update:
|
||||||
string += "Only the at_object_creation hook was run (update mode)."
|
string += "Only the at_object_creation hook was run (update mode)."
|
||||||
else:
|
else:
|
||||||
|
|
@ -2309,8 +2246,7 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
string += " Attributes set before swap were not removed\n(use `swap` or `type/reset` to clear all)."
|
string += " Attributes set before swap were not removed\n(use `swap` or `type/reset` to clear all)."
|
||||||
if "prototype" in self.switches and prototype_success:
|
if "prototype" in self.switches and prototype_success:
|
||||||
string += (
|
string += (
|
||||||
" Prototype '%s' was successfully applied over the object type."
|
f" Prototype '{prototype['key']}' was successfully applied over the object type."
|
||||||
% prototype["key"]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
@ -2359,12 +2295,11 @@ class CmdWipe(ObjManipCommand):
|
||||||
if not attrs:
|
if not attrs:
|
||||||
# wipe everything
|
# wipe everything
|
||||||
obj.attributes.clear()
|
obj.attributes.clear()
|
||||||
string = "Wiped all attributes on %s." % obj.name
|
string = f"Wiped all attributes on {obj.name}."
|
||||||
else:
|
else:
|
||||||
for attrname in attrs:
|
for attrname in attrs:
|
||||||
obj.attributes.remove(attrname)
|
obj.attributes.remove(attrname)
|
||||||
string = "Wiped attributes %s on %s."
|
string = f"Wiped attributes {','.join(attrs)} on {obj.name}."
|
||||||
string = string % (",".join(attrs), obj.name)
|
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2445,7 +2380,7 @@ class CmdLock(ObjManipCommand):
|
||||||
else:
|
else:
|
||||||
string = lockdef
|
string = lockdef
|
||||||
else:
|
else:
|
||||||
string = "%s has no lock of access type '%s'." % (obj, access_type)
|
string = f"{obj} has no lock of access type '{access_type}'."
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -2454,9 +2389,9 @@ class CmdLock(ObjManipCommand):
|
||||||
if self.switches:
|
if self.switches:
|
||||||
swi = ", ".join(self.switches)
|
swi = ", ".join(self.switches)
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"Switch(es) |w%s|n can not be used with a "
|
f"Switch(es) |w{swi}|n can not be used with a "
|
||||||
"lock assignment. Use e.g. "
|
"lock assignment. Use e.g. "
|
||||||
"|wlock/del objname/locktype|n instead." % swi
|
"|wlock/del objname/locktype|n instead."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -2484,7 +2419,7 @@ class CmdLock(ObjManipCommand):
|
||||||
# update on them unless their cmdsets are rebuilt.
|
# update on them unless their cmdsets are rebuilt.
|
||||||
obj.at_init()
|
obj.at_init()
|
||||||
if ok:
|
if ok:
|
||||||
caller.msg("Added lock '%s' to %s." % (lockdef, obj))
|
caller.msg(f"Added lock '{lockdef}' to {obj}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# if we get here, we are just viewing all locks on obj
|
# if we get here, we are just viewing all locks on obj
|
||||||
|
|
@ -3167,14 +3102,12 @@ class CmdFind(COMMAND_DEFAULT_CLASS):
|
||||||
if not result:
|
if not result:
|
||||||
string += "\n |RNo match found.|n"
|
string += "\n |RNo match found.|n"
|
||||||
elif not low <= int(result[0].id) <= high:
|
elif not low <= int(result[0].id) <= high:
|
||||||
string += "\n |RNo match found for '%s' in #dbref interval.|n" % searchstring
|
string += f"\n |RNo match found for '{searchstring}' in #dbref interval.|n"
|
||||||
else:
|
else:
|
||||||
result = result[0]
|
result = result[0]
|
||||||
string += "\n|g %s - %s|n" % (result.get_display_name(caller), result.path)
|
string += f"\n|g {result.get_display_name(caller)} - {result.path}|n"
|
||||||
if "loc" in self.switches and not is_account and result.location:
|
if "loc" in self.switches and not is_account and result.location:
|
||||||
string += " (|wlocation|n: |g{}|n)".format(
|
string += f" (|wlocation|n: |g{result.location.get_display_name(caller)}|n)"
|
||||||
result.location.get_display_name(caller)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# Not an account/dbref search but a wider search; build a queryset.
|
# Not an account/dbref search but a wider search; build a queryset.
|
||||||
# Searches for key and aliases
|
# Searches for key and aliases
|
||||||
|
|
@ -3675,14 +3608,13 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
if obj_to_teleport.has_account:
|
if obj_to_teleport.has_account:
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"Cannot teleport a puppeted object "
|
"Cannot teleport a puppeted object "
|
||||||
"(%s, puppeted by %s) to a None-location."
|
f"({obj_to_teleport.key}, puppeted by {obj_to_teleport.account}) to a None-location."
|
||||||
% (obj_to_teleport.key, obj_to_teleport.account)
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
caller.msg("Teleported %s -> None-location." % obj_to_teleport)
|
caller.msg(f"Teleported {obj_to_teleport} -> None-location.")
|
||||||
if obj_to_teleport.location and "quiet" not in self.switches:
|
if obj_to_teleport.location and "quiet" not in self.switches:
|
||||||
obj_to_teleport.location.msg_contents(
|
obj_to_teleport.location.msg_contents(
|
||||||
"%s teleported %s into nothingness." % (caller, obj_to_teleport), exclude=caller
|
f"{caller} teleported {obj_to_teleport} into nothingness.", exclude=caller
|
||||||
)
|
)
|
||||||
obj_to_teleport.location = None
|
obj_to_teleport.location = None
|
||||||
return
|
return
|
||||||
|
|
@ -3710,7 +3642,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
if obj_to_teleport.location and obj_to_teleport.location == destination:
|
if obj_to_teleport.location and obj_to_teleport.location == destination:
|
||||||
caller.msg("%s is already at %s." % (obj_to_teleport, destination))
|
caller.msg(f"{obj_to_teleport} is already at {destination}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# check any locks
|
# check any locks
|
||||||
|
|
@ -3886,7 +3818,7 @@ class CmdTag(COMMAND_DEFAULT_CLASS):
|
||||||
", ".join(sorted("'%s'%s" % (tags[i], categories[i]) for i in range(ntags))),
|
", ".join(sorted("'%s'%s" % (tags[i], categories[i]) for i in range(ntags))),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
string = "No tags attached to %s." % obj
|
string = f"No tags attached to {obj}."
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4049,7 +3981,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||||
"funcparser callables ($funcs) in the strings."
|
"funcparser callables ($funcs) in the strings."
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
string = "Expected {}, got {}.".format(expect, type(prototype))
|
string = f"Expected {expect}, got {type(prototype)}."
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -547,7 +547,7 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
if message:
|
if message:
|
||||||
channel.msg(message, senders=caller, bypass_mute=True)
|
channel.msg(message, senders=caller, bypass_mute=True)
|
||||||
channel.delete()
|
channel.delete()
|
||||||
logger.log_sec("Channel {} was deleted by {}".format(channel_key, caller))
|
logger.log_sec(f"Channel {channel_key} was deleted by {caller}")
|
||||||
|
|
||||||
def set_lock(self, channel, lockstring):
|
def set_lock(self, channel, lockstring):
|
||||||
"""
|
"""
|
||||||
|
|
@ -1390,9 +1390,9 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
||||||
self.msg("Who do you want page?")
|
self.msg("Who do you want page?")
|
||||||
return
|
return
|
||||||
|
|
||||||
header = "|wAccount|n |c%s|n |wpages:|n" % caller.key
|
header = f"|wAccount|n |c{caller.key}|n |wpages:|n"
|
||||||
if message.startswith(":"):
|
if message.startswith(":"):
|
||||||
message = "%s %s" % (caller.key, message.strip(":").strip())
|
message = f"{caller.key} {message.strip(':').strip()}"
|
||||||
|
|
||||||
# create the persistent message object
|
# create the persistent message object
|
||||||
create.create_message(caller, message, receivers=targets)
|
create.create_message(caller, message, receivers=targets)
|
||||||
|
|
@ -1468,7 +1468,7 @@ class CmdPage(COMMAND_DEFAULT_CLASS):
|
||||||
lastpages = "\n ".join(listing)
|
lastpages = "\n ".join(listing)
|
||||||
|
|
||||||
if lastpages:
|
if lastpages:
|
||||||
string = "Your latest pages:\n %s" % lastpages
|
string = f"Your latest pages:\n {lastpages}"
|
||||||
else:
|
else:
|
||||||
string = "You haven't paged anyone yet."
|
string = "You haven't paged anyone yet."
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
|
|
@ -1565,7 +1565,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
|
if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
|
||||||
botname = "ircbot-%s" % self.lhs
|
botname = f"ircbot-{self.lhs}"
|
||||||
matches = AccountDB.objects.filter(db_is_bot=True, username=botname)
|
matches = AccountDB.objects.filter(db_is_bot=True, username=botname)
|
||||||
dbref = utils.dbref(self.lhs)
|
dbref = utils.dbref(self.lhs)
|
||||||
if not matches and dbref:
|
if not matches and dbref:
|
||||||
|
|
@ -1592,7 +1592,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
irc_network, irc_port, irc_channel, irc_botname = [
|
irc_network, irc_port, irc_channel, irc_botname = [
|
||||||
part.strip() for part in self.rhs.split(None, 4)
|
part.strip() for part in self.rhs.split(None, 4)
|
||||||
]
|
]
|
||||||
irc_channel = "#%s" % irc_channel
|
irc_channel = f"#{irc_channel}"
|
||||||
except Exception:
|
except Exception:
|
||||||
string = "IRC bot definition '%s' is not valid." % self.rhs
|
string = "IRC bot definition '%s' is not valid." % self.rhs
|
||||||
self.msg(string)
|
self.msg(string)
|
||||||
|
|
@ -1601,7 +1601,7 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
botclass = None
|
botclass = None
|
||||||
if ":" in irc_botname:
|
if ":" in irc_botname:
|
||||||
irc_botname, botclass = [part.strip() for part in irc_botname.split(":", 2)]
|
irc_botname, botclass = [part.strip() for part in irc_botname.split(":", 2)]
|
||||||
botname = "ircbot-%s" % irc_botname
|
botname = f"ircbot-{irc_botname}"
|
||||||
# If path given, use custom bot otherwise use default.
|
# If path given, use custom bot otherwise use default.
|
||||||
botclass = botclass if botclass else bots.IRCBot
|
botclass = botclass if botclass else bots.IRCBot
|
||||||
irc_ssl = "ssl" in self.switches
|
irc_ssl = "ssl" in self.switches
|
||||||
|
|
@ -1612,13 +1612,13 @@ class CmdIRC2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
# re-use an existing bot
|
# re-use an existing bot
|
||||||
bot = bot[0]
|
bot = bot[0]
|
||||||
if not bot.is_bot:
|
if not bot.is_bot:
|
||||||
self.msg("Account '%s' already exists and is not a bot." % botname)
|
self.msg(f"Account '{botname}' already exists and is not a bot.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
bot = create.create_account(botname, None, None, typeclass=botclass)
|
bot = create.create_account(botname, None, None, typeclass=botclass)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
self.msg("|rError, could not create the bot:|n '%s'." % err)
|
self.msg(f"|rError, could not create the bot:|n '{err}'.")
|
||||||
return
|
return
|
||||||
bot.start(
|
bot.start(
|
||||||
ev_channel=channel,
|
ev_channel=channel,
|
||||||
|
|
@ -1681,26 +1681,21 @@ class CmdIRCStatus(COMMAND_DEFAULT_CLASS):
|
||||||
channel = ircbot.db.irc_channel
|
channel = ircbot.db.irc_channel
|
||||||
network = ircbot.db.irc_network
|
network = ircbot.db.irc_network
|
||||||
port = ircbot.db.irc_port
|
port = ircbot.db.irc_port
|
||||||
chtext = "IRC bot '%s' on channel %s (%s:%s)" % (
|
chtext = f"IRC bot '{ircbot.db.irc_botname}' on channel {channel} ({network}:{port})"
|
||||||
ircbot.db.irc_botname,
|
|
||||||
channel,
|
|
||||||
network,
|
|
||||||
port,
|
|
||||||
)
|
|
||||||
if option == "ping":
|
if option == "ping":
|
||||||
# check connection by sending outself a ping through the server.
|
# check connection by sending outself a ping through the server.
|
||||||
self.caller.msg("Pinging through %s." % chtext)
|
self.caller.msg(f"Pinging through {chtext}.")
|
||||||
ircbot.ping(self.caller)
|
ircbot.ping(self.caller)
|
||||||
elif option in ("users", "nicklist", "who"):
|
elif option in ("users", "nicklist", "who"):
|
||||||
# retrieve user list. The bot must handles the echo since it's
|
# retrieve user list. The bot must handles the echo since it's
|
||||||
# an asynchronous call.
|
# an asynchronous call.
|
||||||
self.caller.msg("Requesting nicklist from %s (%s:%s)." % (channel, network, port))
|
self.caller.msg(f"Requesting nicklist from {channel} ({network}:{port}).")
|
||||||
ircbot.get_nicklist(self.caller)
|
ircbot.get_nicklist(self.caller)
|
||||||
elif self.caller.locks.check_lockstring(
|
elif self.caller.locks.check_lockstring(
|
||||||
self.caller, "dummy:perm(ircstatus) or perm(Developer)"
|
self.caller, "dummy:perm(ircstatus) or perm(Developer)"
|
||||||
):
|
):
|
||||||
# reboot the client
|
# reboot the client
|
||||||
self.caller.msg("Forcing a disconnect + reconnect of %s." % chtext)
|
self.caller.msg(f"Forcing a disconnect + reconnect of {chtext}.")
|
||||||
ircbot.reconnect()
|
ircbot.reconnect()
|
||||||
else:
|
else:
|
||||||
self.caller.msg("You don't have permission to force-reload the IRC bot.")
|
self.caller.msg("You don't have permission to force-reload the IRC bot.")
|
||||||
|
|
@ -1782,7 +1777,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
|
if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
|
||||||
botname = "rssbot-%s" % self.lhs
|
botname = f"rssbot-{self.lhs}"
|
||||||
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)
|
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)
|
||||||
if not matches:
|
if not matches:
|
||||||
# try dbref match
|
# try dbref match
|
||||||
|
|
@ -1801,13 +1796,13 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
channel = self.lhs
|
channel = self.lhs
|
||||||
url = self.rhs
|
url = self.rhs
|
||||||
|
|
||||||
botname = "rssbot-%s" % url
|
botname = f"rssbot-{url}"
|
||||||
bot = AccountDB.objects.filter(username__iexact=botname)
|
bot = AccountDB.objects.filter(username__iexact=botname)
|
||||||
if bot:
|
if bot:
|
||||||
# re-use existing bot
|
# re-use existing bot
|
||||||
bot = bot[0]
|
bot = bot[0]
|
||||||
if not bot.is_bot:
|
if not bot.is_bot:
|
||||||
self.msg("Account '%s' already exists and is not a bot." % botname)
|
self.msg("Account '{botname}' already exists and is not a bot.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# create a new bot
|
# create a new bot
|
||||||
|
|
@ -1875,7 +1870,7 @@ class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
|
|
||||||
if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
|
if "disconnect" in self.switches or "remove" in self.switches or "delete" in self.switches:
|
||||||
botname = "grapevinebot-%s" % self.lhs
|
botname = f"grapevinebot-{self.lhs}"
|
||||||
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)
|
matches = AccountDB.objects.filter(db_is_bot=True, db_key=botname)
|
||||||
|
|
||||||
if not matches:
|
if not matches:
|
||||||
|
|
@ -1902,10 +1897,10 @@ class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
# re-use existing bot
|
# re-use existing bot
|
||||||
bot = bot[0]
|
bot = bot[0]
|
||||||
if not bot.is_bot:
|
if not bot.is_bot:
|
||||||
self.msg("Account '%s' already exists and is not a bot." % botname)
|
self.msg(f"Account '{botname}' already exists and is not a bot.")
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.msg("Reusing bot '%s' (%s)" % (botname, bot.dbref))
|
self.msg(f"Reusing bot '{botname}' ({bot.dbref})")
|
||||||
else:
|
else:
|
||||||
# create a new bot
|
# create a new bot
|
||||||
bot = create.create_account(botname, None, None, typeclass=bots.GrapevineBot)
|
bot = create.create_account(botname, None, None, typeclass=bots.GrapevineBot)
|
||||||
|
|
|
||||||
|
|
@ -221,9 +221,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
_, _, old_nickstring, old_replstring = oldnick.value
|
_, _, old_nickstring, old_replstring = oldnick.value
|
||||||
caller.nicks.remove(old_nickstring, category=nicktype)
|
caller.nicks.remove(old_nickstring, category=nicktype)
|
||||||
caller.msg(
|
caller.msg(
|
||||||
"%s removed: '|w%s|n' -> |w%s|n."
|
f"{nicktypestr} removed: '|w{old_nickstring}|n' -> |w{old_replstring}|n.")
|
||||||
% (nicktypestr, old_nickstring, old_replstring)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
caller.msg("No matching nicks to remove.")
|
caller.msg("No matching nicks to remove.")
|
||||||
return
|
return
|
||||||
|
|
@ -245,12 +243,12 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
_, _, nick, repl = nick.value
|
_, _, nick, repl = nick.value
|
||||||
if nick.startswith(self.lhs):
|
if nick.startswith(self.lhs):
|
||||||
strings.append(
|
strings.append(
|
||||||
"{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
|
f"{nicktype.capitalize()}-nick: '{nick}' -> '{repl}'"
|
||||||
)
|
)
|
||||||
if strings:
|
if strings:
|
||||||
caller.msg("\n".join(strings))
|
caller.msg("\n".join(strings))
|
||||||
else:
|
else:
|
||||||
caller.msg("No nicks found matching '{}'".format(self.lhs))
|
caller.msg(f"No nicks found matching '{self,lhs}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.rhs and self.lhs:
|
if not self.rhs and self.lhs:
|
||||||
|
|
@ -268,12 +266,12 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
_, _, nick, repl = nick.value
|
_, _, nick, repl = nick.value
|
||||||
if nick.startswith(self.lhs):
|
if nick.startswith(self.lhs):
|
||||||
strings.append(
|
strings.append(
|
||||||
"{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
|
f"{nicktype.capitalize()}-nick: '{nick}' -> '{repl}'"
|
||||||
)
|
)
|
||||||
if strings:
|
if strings:
|
||||||
caller.msg("\n".join(strings))
|
caller.msg("\n".join(strings))
|
||||||
else:
|
else:
|
||||||
caller.msg("No nicks found matching '{}'".format(self.lhs))
|
caller.msg(f"No nicks found matching '{self.lhs}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.rhs and self.lhs:
|
if not self.rhs and self.lhs:
|
||||||
|
|
@ -291,12 +289,12 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
_, _, nick, repl = nick.value
|
_, _, nick, repl = nick.value
|
||||||
if nick.startswith(self.lhs):
|
if nick.startswith(self.lhs):
|
||||||
strings.append(
|
strings.append(
|
||||||
"{}-nick: '{}' -> '{}'".format(nicktype.capitalize(), nick, repl)
|
f"{nicktype.capitalize()}-nick: '{nick}' -> '{repl}'"
|
||||||
)
|
)
|
||||||
if strings:
|
if strings:
|
||||||
caller.msg("\n".join(strings))
|
caller.msg("\n".join(strings))
|
||||||
else:
|
else:
|
||||||
caller.msg("No nicks found matching '{}'".format(self.lhs))
|
caller.msg(f"No nicks found matching '{self.lhs}'")
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.args or not self.lhs:
|
if not self.args or not self.lhs:
|
||||||
|
|
@ -316,7 +314,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
errstring = ""
|
errstring = ""
|
||||||
string = ""
|
string = ""
|
||||||
for nicktype in nicktypes:
|
for nicktype in nicktypes:
|
||||||
nicktypestr = "%s-nick" % nicktype.capitalize()
|
nicktypestr = f"{nicktype.capitalize()}-nick"
|
||||||
old_nickstring = None
|
old_nickstring = None
|
||||||
old_replstring = None
|
old_replstring = None
|
||||||
|
|
||||||
|
|
@ -328,19 +326,11 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
errstring = ""
|
errstring = ""
|
||||||
if oldnick:
|
if oldnick:
|
||||||
if replstring == old_replstring:
|
if replstring == old_replstring:
|
||||||
string += "\nIdentical %s already set." % nicktypestr.lower()
|
string += f"\nIdentical {nicktypestr.lower()} already set."
|
||||||
else:
|
else:
|
||||||
string += "\n%s '|w%s|n' updated to map to '|w%s|n'." % (
|
string += f"\n{nicktypestr} '|w{old_nickstring}|n' updated to map to '|w{replstring}|n'."
|
||||||
nicktypestr,
|
|
||||||
old_nickstring,
|
|
||||||
replstring,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
string += "\n%s '|w%s|n' mapped to '|w%s|n'." % (
|
string += f"\n{nicktypestr} '|w{nickstring}|n' mapped to '|w{replstring}|n'."
|
||||||
nicktypestr,
|
|
||||||
nickstring,
|
|
||||||
replstring,
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
caller.nicks.add(nickstring, replstring, category=nicktype)
|
caller.nicks.add(nickstring, replstring, category=nicktype)
|
||||||
except NickTemplateInvalid:
|
except NickTemplateInvalid:
|
||||||
|
|
@ -350,11 +340,7 @@ class CmdNick(COMMAND_DEFAULT_CLASS):
|
||||||
return
|
return
|
||||||
elif old_nickstring and old_replstring:
|
elif old_nickstring and old_replstring:
|
||||||
# just looking at the nick
|
# just looking at the nick
|
||||||
string += "\n%s '|w%s|n' maps to '|w%s|n'." % (
|
string += f"\n{nicktypestr} '|w{old_nickstring}|n' maps to '|w{old_replstring}|n'."
|
||||||
nicktypestr,
|
|
||||||
old_nickstring,
|
|
||||||
old_replstring,
|
|
||||||
)
|
|
||||||
errstring = ""
|
errstring = ""
|
||||||
string = errstring if errstring else string
|
string = errstring if errstring else string
|
||||||
caller.msg(_cy(string))
|
caller.msg(_cy(string))
|
||||||
|
|
@ -439,10 +425,8 @@ class CmdGet(COMMAND_DEFAULT_CLASS):
|
||||||
if not success:
|
if not success:
|
||||||
caller.msg("This can't be picked up.")
|
caller.msg("This can't be picked up.")
|
||||||
else:
|
else:
|
||||||
caller.msg("You pick up %s." % obj.name)
|
caller.msg(f"You pick up {obj.name}.")
|
||||||
caller.location.msg_contents(
|
caller.location.msg_contents(f"{caller.name} picks up {obj.name}.", exclude=caller)
|
||||||
"%s picks up %s." % (caller.name, obj.name), exclude=caller
|
|
||||||
)
|
|
||||||
# calling at_get hook method
|
# calling at_get hook method
|
||||||
obj.at_get(caller)
|
obj.at_get(caller)
|
||||||
|
|
||||||
|
|
@ -475,8 +459,8 @@ class CmdDrop(COMMAND_DEFAULT_CLASS):
|
||||||
obj = caller.search(
|
obj = caller.search(
|
||||||
self.args,
|
self.args,
|
||||||
location=caller,
|
location=caller,
|
||||||
nofound_string="You aren't carrying %s." % self.args,
|
nofound_string=f"You aren't carrying {self.args}.",
|
||||||
multimatch_string="You carry more than one %s:" % self.args,
|
multimatch_string=f"You carry more than one {self.args}:",
|
||||||
)
|
)
|
||||||
if not obj:
|
if not obj:
|
||||||
return
|
return
|
||||||
|
|
@ -490,7 +474,7 @@ class CmdDrop(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg("This couldn't be dropped.")
|
caller.msg("This couldn't be dropped.")
|
||||||
else:
|
else:
|
||||||
caller.msg("You drop %s." % (obj.name,))
|
caller.msg("You drop %s." % (obj.name,))
|
||||||
caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller)
|
caller.location.msg_contents(f"{caller.name} drops {obj.name}.", exclude=caller)
|
||||||
# Call the object script's at_drop() method.
|
# Call the object script's at_drop() method.
|
||||||
obj.at_drop(caller)
|
obj.at_drop(caller)
|
||||||
|
|
||||||
|
|
@ -521,17 +505,17 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
|
||||||
to_give = caller.search(
|
to_give = caller.search(
|
||||||
self.lhs,
|
self.lhs,
|
||||||
location=caller,
|
location=caller,
|
||||||
nofound_string="You aren't carrying %s." % self.lhs,
|
nofound_string=f"You aren't carrying {self.lhs}.",
|
||||||
multimatch_string="You carry more than one %s:" % self.lhs,
|
multimatch_string=f"You carry more than one {self.lhs}:",
|
||||||
)
|
)
|
||||||
target = caller.search(self.rhs)
|
target = caller.search(self.rhs)
|
||||||
if not (to_give and target):
|
if not (to_give and target):
|
||||||
return
|
return
|
||||||
if target == caller:
|
if target == caller:
|
||||||
caller.msg("You keep %s to yourself." % to_give.key)
|
caller.msg(f"You keep {to_give.key} to yourself.")
|
||||||
return
|
return
|
||||||
if not to_give.location == caller:
|
if not to_give.location == caller:
|
||||||
caller.msg("You are not holding %s." % to_give.key)
|
caller.msg(f"You are not holding {to_give.key}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# calling at_pre_give hook method
|
# calling at_pre_give hook method
|
||||||
|
|
@ -541,10 +525,10 @@ class CmdGive(COMMAND_DEFAULT_CLASS):
|
||||||
# give object
|
# give object
|
||||||
success = to_give.move_to(target, quiet=True, move_type="give")
|
success = to_give.move_to(target, quiet=True, move_type="give")
|
||||||
if not success:
|
if not success:
|
||||||
caller.msg("This could not be given.")
|
caller.msg(f"You could not give {to_give.key}.")
|
||||||
else:
|
else:
|
||||||
caller.msg("You give %s to %s." % (to_give.key, target.key))
|
caller.msg(f"You give {to_give.key} to {target.key}.")
|
||||||
target.msg("%s gives you %s." % (caller.key, to_give.key))
|
target.msg(f"{caller.key} gives you {to_give.key}.")
|
||||||
# Call the object script's at_give() method.
|
# Call the object script's at_give() method.
|
||||||
to_give.at_give(caller, target)
|
to_give.at_give(caller, target)
|
||||||
|
|
||||||
|
|
@ -702,7 +686,7 @@ class CmdPose(COMMAND_DEFAULT_CLASS):
|
||||||
msg = "What do you want to do?"
|
msg = "What do you want to do?"
|
||||||
self.caller.msg(msg)
|
self.caller.msg(msg)
|
||||||
else:
|
else:
|
||||||
msg = "%s%s" % (self.caller.name, self.args)
|
msg = f"{self.caller.name}{self.args}"
|
||||||
self.caller.location.msg_contents(text=(msg, {"type": "pose"}), from_obj=self.caller)
|
self.caller.location.msg_contents(text=(msg, {"type": "pose"}), from_obj=self.caller)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -737,7 +721,7 @@ class CmdAccess(COMMAND_DEFAULT_CLASS):
|
||||||
pperms = ", ".join(caller.account.permissions.all())
|
pperms = ", ".join(caller.account.permissions.all())
|
||||||
|
|
||||||
string += "\n|wYour access|n:"
|
string += "\n|wYour access|n:"
|
||||||
string += "\nCharacter |c%s|n: %s" % (caller.key, cperms)
|
string += f"\nCharacter |c{caller.key}|n: {cperms}"
|
||||||
if hasattr(caller, "account"):
|
if hasattr(caller, "account"):
|
||||||
string += "\nAccount |c%s|n: %s" % (caller.account.key, pperms)
|
string += f"\nAccount |c{caller.account.key}|n: {pperms}"
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
@ -962,7 +962,7 @@ class CmdSetHelp(CmdHelp):
|
||||||
if "append" in switches or "merge" in switches or "extend" in switches:
|
if "append" in switches or "merge" in switches or "extend" in switches:
|
||||||
# merge/append operations
|
# merge/append operations
|
||||||
if not old_entry:
|
if not old_entry:
|
||||||
self.msg("Could not find topic '%s'. You must give an exact name." % topicstr)
|
self.msg(f"Could not find topic '{topicstr}'. You must give an exact name.")
|
||||||
return
|
return
|
||||||
if not self.rhs:
|
if not self.rhs:
|
||||||
self.msg("You must supply text to append/merge.")
|
self.msg("You must supply text to append/merge.")
|
||||||
|
|
@ -972,16 +972,16 @@ class CmdSetHelp(CmdHelp):
|
||||||
else:
|
else:
|
||||||
old_entry.entrytext += "\n%s" % self.rhs
|
old_entry.entrytext += "\n%s" % self.rhs
|
||||||
old_entry.aliases.add(aliases)
|
old_entry.aliases.add(aliases)
|
||||||
self.msg("Entry updated:\n%s%s" % (old_entry.entrytext, aliastxt))
|
self.msg(f"Entry updated:\n{old_entry.entrytext}{aliastxt}")
|
||||||
return
|
return
|
||||||
|
|
||||||
if "delete" in switches or "del" in switches:
|
if "delete" in switches or "del" in switches:
|
||||||
# delete the help entry
|
# delete the help entry
|
||||||
if not old_entry:
|
if not old_entry:
|
||||||
self.msg("Could not find topic '%s'%s." % (topicstr, aliastxt))
|
self.msg(f"Could not find topic '{topicstr}'{aliastxt}.")
|
||||||
return
|
return
|
||||||
old_entry.delete()
|
old_entry.delete()
|
||||||
self.msg("Deleted help entry '%s'%s." % (topicstr, aliastxt))
|
self.msg(f"Deleted help entry '{topicstr}'{aliastxt}.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# at this point it means we want to add a new help entry.
|
# at this point it means we want to add a new help entry.
|
||||||
|
|
@ -998,7 +998,7 @@ class CmdSetHelp(CmdHelp):
|
||||||
old_entry.locks.add(lockstring)
|
old_entry.locks.add(lockstring)
|
||||||
old_entry.aliases.add(aliases)
|
old_entry.aliases.add(aliases)
|
||||||
old_entry.save()
|
old_entry.save()
|
||||||
self.msg("Overwrote the old topic '%s'%s." % (topicstr, aliastxt))
|
self.msg(f"Overwrote the old topic '{topicstr}'{aliastxt}.")
|
||||||
else:
|
else:
|
||||||
self.msg(
|
self.msg(
|
||||||
f"Topic '{topicstr}'{aliastxt} already exists. Use /edit to open in editor, or "
|
f"Topic '{topicstr}'{aliastxt} already exists. Use /edit to open in editor, or "
|
||||||
|
|
|
||||||
|
|
@ -216,30 +216,30 @@ Command {self} has no defined `func()` - showing on-command variables: No child
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
# a simple test command to show the available properties
|
# a simple test command to show the available properties
|
||||||
string = "-" * 50
|
string = "-" * 50
|
||||||
string += "\n|w%s|n - Command variables from evennia:\n" % self.key
|
string += f"\n|w{self.key}|n - Command variables from evennia:\n"
|
||||||
string += "-" * 50
|
string += "-" * 50
|
||||||
string += "\nname of cmd (self.key): |w%s|n\n" % self.key
|
string += f"\nname of cmd (self.key): |w{self.key}|n\n"
|
||||||
string += "cmd aliases (self.aliases): |w%s|n\n" % self.aliases
|
string += f"cmd aliases (self.aliases): |w{self.aliases}|n\n"
|
||||||
string += "cmd locks (self.locks): |w%s|n\n" % self.locks
|
string += f"cmd locks (self.locks): |w{self.locks}|n\n"
|
||||||
string += "help category (self.help_category): |w%s|n\n" % self.help_category
|
string += f"help category (self.help_category): |w{self.help_category}|n\n"
|
||||||
string += "object calling (self.caller): |w%s|n\n" % self.caller
|
string += f"object calling (self.caller): |w{self.caller}|n\n"
|
||||||
string += "object storing cmdset (self.obj): |w%s|n\n" % self.obj
|
string += f"object storing cmdset (self.obj): |w{self.obj}|n\n"
|
||||||
string += "command string given (self.cmdstring): |w%s|n\n" % self.cmdstring
|
string += f"command string given (self.cmdstring): |w{self.cmdstring}|n\n"
|
||||||
# show cmdset.key instead of cmdset to shorten output
|
# show cmdset.key instead of cmdset to shorten output
|
||||||
string += utils.fill("current cmdset (self.cmdset): |w%s|n\n" % self.cmdset)
|
string += utils.fill(f"current cmdset (self.cmdset): |w{self.cmdset}|n\n")
|
||||||
string += "\n" + "-" * 50
|
string += "\n" + "-" * 50
|
||||||
string += "\nVariables from MuxCommand baseclass\n"
|
string += "\nVariables from MuxCommand baseclass\n"
|
||||||
string += "-" * 50
|
string += "-" * 50
|
||||||
string += "\nraw argument (self.raw): |w%s|n \n" % self.raw
|
string += f"\nraw argument (self.raw): |w{self.raw}|n \n"
|
||||||
string += "cmd args (self.args): |w%s|n\n" % self.args
|
string += f"cmd args (self.args): |w{self.args}|n\n"
|
||||||
string += "cmd switches (self.switches): |w%s|n\n" % self.switches
|
string += f"cmd switches (self.switches): |w{self.switches}|n\n"
|
||||||
string += "cmd options (self.switch_options): |w%s|n\n" % self.switch_options
|
string += f"cmd options (self.switch_options): |w{self.switch_options}|n\n"
|
||||||
string += "cmd parse left/right using (self.rhs_split): |w%s|n\n" % self.rhs_split
|
string += f"cmd parse left/right using (self.rhs_split): |w{self.rhs_split}|n\n"
|
||||||
string += "space-separated arg list (self.arglist): |w%s|n\n" % self.arglist
|
string += f"space-separated arg list (self.arglist): |w{self.arglist}|n\n"
|
||||||
string += "lhs, left-hand side of '=' (self.lhs): |w%s|n\n" % self.lhs
|
string += f"lhs, left-hand side of '=' (self.lhs): |w{self.lhs}|n\n"
|
||||||
string += "lhs, comma separated (self.lhslist): |w%s|n\n" % self.lhslist
|
string += f"lhs, comma separated (self.lhslist): |w{self.lhslist}|n\n"
|
||||||
string += "rhs, right-hand side of '=' (self.rhs): |w%s|n\n" % self.rhs
|
string += f"rhs, right-hand side of '=' (self.rhs): |w{self.rhs}|n\n"
|
||||||
string += "rhs, comma separated (self.rhslist): |w%s|n\n" % self.rhslist
|
string += f"rhs, comma separated (self.rhslist): |w{self.rhslist}|n\n"
|
||||||
string += "-" * 50
|
string += "-" * 50
|
||||||
self.caller.msg(string)
|
self.caller.msg(string)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ class CmdReload(COMMAND_DEFAULT_CLASS):
|
||||||
if self.args:
|
if self.args:
|
||||||
reason = "(Reason: %s) " % self.args.rstrip(".")
|
reason = "(Reason: %s) " % self.args.rstrip(".")
|
||||||
if _BROADCAST_SERVER_RESTART_MESSAGES:
|
if _BROADCAST_SERVER_RESTART_MESSAGES:
|
||||||
SESSIONS.announce_all(" Server restart initiated %s..." % reason)
|
SESSIONS.announce_all(f" Server restart initiated {reason}...")
|
||||||
SESSIONS.portal_restart_server()
|
SESSIONS.portal_restart_server()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -135,7 +135,7 @@ class CmdShutdown(COMMAND_DEFAULT_CLASS):
|
||||||
announcement = "\nServer is being SHUT DOWN!\n"
|
announcement = "\nServer is being SHUT DOWN!\n"
|
||||||
if self.args:
|
if self.args:
|
||||||
announcement += "%s\n" % self.args
|
announcement += "%s\n" % self.args
|
||||||
logger.log_info("Server shutdown by %s." % self.caller.name)
|
logger.log_info(f"Server shutdown by {self.caller.name}.")
|
||||||
SESSIONS.announce_all(announcement)
|
SESSIONS.announce_all(announcement)
|
||||||
SESSIONS.portal_shutdown()
|
SESSIONS.portal_shutdown()
|
||||||
|
|
||||||
|
|
@ -482,13 +482,12 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
# Boot the account then delete it.
|
# Boot the account then delete it.
|
||||||
self.msg("Informing and disconnecting account ...")
|
self.msg("Informing and disconnecting account ...")
|
||||||
string = "\nYour account '%s' is being *permanently* deleted.\n" % username
|
string = f"\nYour account '{username}' is being *permanently* deleted.\n"
|
||||||
if reason:
|
if reason:
|
||||||
string += " Reason given:\n '%s'" % reason
|
string += " Reason given:\n '%s'" % reason
|
||||||
account.msg(string)
|
account.msg(string)
|
||||||
logger.log_sec(
|
logger.log_sec(
|
||||||
"Account Deleted: %s (Reason: %s, Caller: %s, IP: %s)."
|
f"Account Deleted: {account} (Reason: {reason}, Caller: {caller}, IP: {self.session.address})."
|
||||||
% (account, reason, caller, self.session.address)
|
|
||||||
)
|
)
|
||||||
account.delete()
|
account.delete()
|
||||||
self.msg("Account %s was successfully deleted." % username)
|
self.msg("Account %s was successfully deleted." % username)
|
||||||
|
|
@ -519,8 +518,8 @@ class CmdAccounts(COMMAND_DEFAULT_CLASS):
|
||||||
utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path
|
utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.path
|
||||||
)
|
)
|
||||||
|
|
||||||
string = "\n|wAccount typeclass distribution:|n\n%s" % typetable
|
string = f"\n|wAccount typeclass distribution:|n\n{typetable}"
|
||||||
string += "\n|wLast %s Accounts created:|n\n%s" % (min(naccounts, nlim), latesttable)
|
string += f"\n|wLast {min(naccounts, nlim)} Accounts created:|n\n{latesttable}"
|
||||||
caller.msg(string)
|
caller.msg(string)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -610,7 +609,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
||||||
if delmode:
|
if delmode:
|
||||||
service.stopService()
|
service.stopService()
|
||||||
service_collection.removeService(service)
|
service_collection.removeService(service)
|
||||||
caller.msg("|gStopped and removed service '%s'.|n" % self.args)
|
caller.msg(f"|gStopped and removed service '{self.args}'.|n")
|
||||||
else:
|
else:
|
||||||
caller.msg(f"Stopping service '{self.args}'...")
|
caller.msg(f"Stopping service '{self.args}'...")
|
||||||
try:
|
try:
|
||||||
|
|
@ -621,7 +620,7 @@ class CmdService(COMMAND_DEFAULT_CLASS):
|
||||||
"If there are remaining problems, try reloading "
|
"If there are remaining problems, try reloading "
|
||||||
"or rebooting the server."
|
"or rebooting the server."
|
||||||
)
|
)
|
||||||
caller.msg("|g... Stopped service '%s'.|n" % self.args)
|
caller.msg(f"|g... Stopped service '{self.args}'.|n")
|
||||||
return
|
return
|
||||||
|
|
||||||
if switches[0] == "start":
|
if switches[0] == "start":
|
||||||
|
|
|
||||||
|
|
@ -53,28 +53,31 @@ Exits: northeast and east
|
||||||
(then go back to your mygame/ folder)
|
(then go back to your mygame/ folder)
|
||||||
|
|
||||||
This will install all optional requirements of Evennia.
|
This will install all optional requirements of Evennia.
|
||||||
2. Import and add the `evennia.contrib.commands.XYZGridCmdSet` to the
|
2. Import and [add] the `evennia.contrib.grid.xyzgrid.commands.XYZGridCmdSet` to the
|
||||||
`CharacterCmdset` cmdset in `mygame/commands.default_cmds.py`. Reload
|
`CharacterCmdset` cmdset in `mygame/commands.default_cmds.py`. Reload
|
||||||
the server. This makes the `map`, `goto/path` and the modified `teleport` and
|
the server. This makes the `map`, `goto/path` and the modified `teleport` and
|
||||||
`open` commands available in-game.
|
`open` commands available in-game.
|
||||||
|
|
||||||
|
[add]: docs/source/Command-Sets.md#defining-command-sets
|
||||||
|
|
||||||
3. Edit `mygame/server/conf/settings.py` and add
|
3. Edit `mygame/server/conf/settings.py` and add
|
||||||
|
|
||||||
EXTRA_LAUNCHER_COMMANDS['xyzgrid'] = 'evennia.contrib.launchcmd.xyzcommand'
|
EXTRA_LAUNCHER_COMMANDS['xyzgrid'] = 'evennia.contrib.grid.xyzgrid.launchcmd.xyzcommand'
|
||||||
|
PROTOTYPE_MODULES += ['evennia.contrib.grid.xyzgrid.prototypes']
|
||||||
and
|
|
||||||
|
|
||||||
PROTOTYPE_MODULES += [’evennia.contrib.grid.xyzgrid.prototypes’]
|
|
||||||
|
|
||||||
This will add the new ability to enter `evennia xyzgrid <option>` on the
|
This will add the new ability to enter `evennia xyzgrid <option>` on the
|
||||||
command line. It will also make the `xyz_room` and `xyz_exit` prototypes
|
command line. It will also make the `xyz_room` and `xyz_exit` prototypes
|
||||||
available for use as prototype-parents when spawning the grid.
|
available for use as prototype-parents when spawning the grid.
|
||||||
|
|
||||||
4. Run `evennia xyzgrid help` for available options.
|
4. Run `evennia xyzgrid help` for available options.
|
||||||
|
|
||||||
5. (Optional): By default, the xyzgrid will only spawn module-based
|
5. (Optional): By default, the xyzgrid will only spawn module-based
|
||||||
[prototypes](../Components/Prototypes.md). This is an optimization and usually makes sense
|
[prototypes]. This is an optimization and usually makes sense
|
||||||
since the grid is entirely defined outside the game anyway. If you want to
|
since the grid is entirely defined outside the game anyway. If you want to
|
||||||
also make use of in-game (db-) created prototypes, add
|
also make use of in-game (db-) created prototypes, add
|
||||||
`XYZGRID_USE_DB_PROTOTYPES = True` to settings.
|
`XYZGRID_USE_DB_PROTOTYPES = True` to settings.
|
||||||
|
|
||||||
|
[prototypes]: ../Components/Prototypes.md
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
Tests for the XYZgrid system.
|
Tests for the XYZgrid system.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from random import randint
|
from random import randint
|
||||||
from parameterized import parameterized
|
from parameterized import parameterized
|
||||||
|
|
@ -1415,3 +1416,78 @@ class TestBuildExampleGrid(BaseEvenniaTest):
|
||||||
self.assertTrue(room2a.db.desc.startswith("This is the entrance to"))
|
self.assertTrue(room2a.db.desc.startswith("This is the entrance to"))
|
||||||
self.assertEqual(room2b.key, "North-west corner of the atrium")
|
self.assertEqual(room2b.key, "North-west corner of the atrium")
|
||||||
self.assertTrue(room2b.db.desc.startswith("Sunlight sifts down"))
|
self.assertTrue(room2b.db.desc.startswith("Sunlight sifts down"))
|
||||||
|
|
||||||
|
|
||||||
|
mock_room_callbacks = mock.MagicMock()
|
||||||
|
mock_exit_callbacks = mock.MagicMock()
|
||||||
|
|
||||||
|
class TestXyzRoom(xyzroom.XYZRoom):
|
||||||
|
def at_object_creation(self):
|
||||||
|
mock_room_callbacks.at_object_creation()
|
||||||
|
|
||||||
|
class TestXyzExit(xyzroom.XYZExit):
|
||||||
|
def at_object_creation(self):
|
||||||
|
mock_exit_callbacks.at_object_creation()
|
||||||
|
|
||||||
|
MAP_DATA = {
|
||||||
|
"map": """
|
||||||
|
|
||||||
|
+ 0 1
|
||||||
|
|
||||||
|
0 #-#
|
||||||
|
|
||||||
|
+ 0 1
|
||||||
|
|
||||||
|
""",
|
||||||
|
"zcoord": "map1",
|
||||||
|
"prototypes": {
|
||||||
|
("*", "*"): {
|
||||||
|
"key": "room",
|
||||||
|
"desc": "A room.",
|
||||||
|
"prototype_parent": "xyz_room",
|
||||||
|
},
|
||||||
|
("*", "*", "*"): {
|
||||||
|
"desc": "A passage.",
|
||||||
|
"prototype_parent": "xyz_exit",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"map_visual_range": 1,
|
||||||
|
"map_mode": "scan",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestCallbacks(BaseEvenniaTest):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
mock_room_callbacks.reset_mock()
|
||||||
|
mock_exit_callbacks.reset_mock()
|
||||||
|
|
||||||
|
def setup_grid(self, map_data):
|
||||||
|
self.grid, err = xyzgrid.XYZGrid.create("testgrid")
|
||||||
|
|
||||||
|
def _log(msg):
|
||||||
|
print(msg)
|
||||||
|
self.grid.log = _log
|
||||||
|
|
||||||
|
self.map_data = map_data
|
||||||
|
self.grid.add_maps(map_data)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super().tearDown()
|
||||||
|
self.grid.delete()
|
||||||
|
|
||||||
|
def test_typeclassed_xyzroom_and_xyzexit_with_at_object_creation_are_called(self):
|
||||||
|
map_data = dict(MAP_DATA)
|
||||||
|
for prototype_key, prototype_value in map_data["prototypes"].items():
|
||||||
|
if len(prototype_key) == 2:
|
||||||
|
prototype_value["typeclass"] = "evennia.contrib.grid.xyzgrid.tests.TestXyzRoom"
|
||||||
|
if len(prototype_key) == 3:
|
||||||
|
prototype_value["typeclass"] = "evennia.contrib.grid.xyzgrid.tests.TestXyzExit"
|
||||||
|
self.setup_grid(map_data)
|
||||||
|
|
||||||
|
self.grid.spawn()
|
||||||
|
|
||||||
|
# Two rooms and 2 exits, Each one should have gotten one `at_object_creation` callback.
|
||||||
|
self.assertEqual(mock_room_callbacks.at_object_creation.mock_calls, [mock.call(), mock.call()])
|
||||||
|
self.assertEqual(mock_exit_callbacks.at_object_creation.mock_calls, [mock.call(), mock.call()])
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ from collections import defaultdict
|
||||||
|
|
||||||
from django.core import exceptions as django_exceptions
|
from django.core import exceptions as django_exceptions
|
||||||
from evennia.prototypes import spawner
|
from evennia.prototypes import spawner
|
||||||
|
from evennia.utils.utils import class_from_module
|
||||||
|
|
||||||
from .utils import MAPSCAN, REVERSE_DIRECTIONS, MapParserError, BIGVAL
|
from .utils import MAPSCAN, REVERSE_DIRECTIONS, MapParserError, BIGVAL, MapError
|
||||||
|
|
||||||
NodeTypeclass = None
|
NodeTypeclass = None
|
||||||
ExitTypeclass = None
|
ExitTypeclass = None
|
||||||
|
|
@ -316,13 +317,14 @@ class MapNode:
|
||||||
try:
|
try:
|
||||||
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
nodeobj = NodeTypeclass.objects.get_xyz(xyz=xyz)
|
||||||
except django_exceptions.ObjectDoesNotExist:
|
except django_exceptions.ObjectDoesNotExist:
|
||||||
# create a new entity with proper coordinates etc
|
# create a new entity, using the specified typeclass (if there's one) and
|
||||||
tclass = self.prototype["typeclass"]
|
# with proper coordinates etc
|
||||||
tclass = (
|
typeclass = self.prototype.get("typeclass")
|
||||||
f" ({tclass})" if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZRoom" else ""
|
if typeclass is None:
|
||||||
)
|
raise MapError(f"The prototype {self.prototype} for this node has no 'typeclass' key.", self)
|
||||||
self.log(f" spawning room at xyz={xyz}{tclass}")
|
self.log(f" spawning room at xyz={xyz} ({typeclass})")
|
||||||
nodeobj, err = NodeTypeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz)
|
Typeclass = class_from_module(typeclass)
|
||||||
|
nodeobj, err = Typeclass.create(self.prototype.get("key", "An empty room"), xyz=xyz)
|
||||||
if err:
|
if err:
|
||||||
raise RuntimeError(err)
|
raise RuntimeError(err)
|
||||||
else:
|
else:
|
||||||
|
|
@ -400,7 +402,14 @@ class MapNode:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
exitnode = self.links[direction]
|
exitnode = self.links[direction]
|
||||||
exi, err = ExitTypeclass.create(
|
prot = maplinks[key.lower()][3].prototype
|
||||||
|
typeclass = prot.get("typeclass")
|
||||||
|
if typeclass is None:
|
||||||
|
raise MapError(f"The prototype {self.prototype} for this node has no 'typeclass' key.", self)
|
||||||
|
self.log(f" spawning/updating exit xyz={xyz}, direction={key} ({typeclass})")
|
||||||
|
|
||||||
|
Typeclass = class_from_module(typeclass)
|
||||||
|
exi, err = Typeclass.create(
|
||||||
key,
|
key,
|
||||||
xyz=xyz,
|
xyz=xyz,
|
||||||
xyz_destination=exitnode.get_spawn_xyz(),
|
xyz_destination=exitnode.get_spawn_xyz(),
|
||||||
|
|
@ -408,15 +417,8 @@ class MapNode:
|
||||||
)
|
)
|
||||||
if err:
|
if err:
|
||||||
raise RuntimeError(err)
|
raise RuntimeError(err)
|
||||||
|
|
||||||
linkobjs[key.lower()] = exi
|
linkobjs[key.lower()] = exi
|
||||||
prot = maplinks[key.lower()][3].prototype
|
|
||||||
tclass = prot["typeclass"]
|
|
||||||
tclass = (
|
|
||||||
f" ({tclass})"
|
|
||||||
if tclass != "evennia.contrib.grid.xyzgrid.xyzroom.XYZExit"
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
self.log(f" spawning/updating exit xyz={xyz}, direction={key}{tclass}")
|
|
||||||
|
|
||||||
# apply prototypes to catch any changes
|
# apply prototypes to catch any changes
|
||||||
for key, linkobj in linkobjs.items():
|
for key, linkobj in linkobjs.items():
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ used as stand-alone XYZ-coordinate-aware rooms.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from evennia.objects.objects import DefaultRoom, DefaultExit
|
|
||||||
from evennia.objects.manager import ObjectManager
|
from evennia.objects.manager import ObjectManager
|
||||||
|
from evennia.objects.objects import DefaultExit, DefaultRoom
|
||||||
|
|
||||||
# name of all tag categories. Note that the Z-coordinate is
|
# name of all tag categories. Note that the Z-coordinate is
|
||||||
# the `map_name` of the XYZgrid
|
# the `map_name` of the XYZgrid
|
||||||
|
|
@ -23,6 +24,8 @@ MAP_ZDEST_TAG_CATEGORY = "exit_dest_z_coordinate"
|
||||||
|
|
||||||
GET_XYZGRID = None
|
GET_XYZGRID = None
|
||||||
|
|
||||||
|
CLIENT_DEFAULT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||||
|
|
||||||
|
|
||||||
class XYZManager(ObjectManager):
|
class XYZManager(ObjectManager):
|
||||||
"""
|
"""
|
||||||
|
|
@ -229,7 +232,7 @@ class XYZExitManager(XYZManager):
|
||||||
f"{key}={val}" for key, val in kwargs.items()
|
f"{key}={val}" for key, val in kwargs.items()
|
||||||
)
|
)
|
||||||
raise self.model.DoesNotExist(
|
raise self.model.DoesNotExist(
|
||||||
f"{self.model.__name__} " f"matching query {inp} does not exist."
|
f"{self.model.__name__} matching query {inp} does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -458,7 +461,8 @@ class XYZRoom(DefaultRoom):
|
||||||
xymap.options.get("map_separator_char", self.map_separator_char),
|
xymap.options.get("map_separator_char", self.map_separator_char),
|
||||||
)
|
)
|
||||||
|
|
||||||
client_width, _ = looker.sessions.get()[0].get_client_size()
|
sessions = looker.sessions.get()
|
||||||
|
client_width, _ = sessions[0].get_client_size() if sessions else CLIENT_DEFAULT_WIDTH
|
||||||
|
|
||||||
map_width = xymap.max_x
|
map_width = xymap.max_x
|
||||||
|
|
||||||
|
|
|
||||||
68
evennia/contrib/utils/git_integration/README.md
Normal file
68
evennia/contrib/utils/git_integration/README.md
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
# In-game Git Integration
|
||||||
|
|
||||||
|
Contribution by helpme (2022)
|
||||||
|
|
||||||
|
A module to integrate a stripped-down version of git within the game, allowing developers to view their git status, change branches, and pull updated code of both their local mygame repo and Evennia core. After a successful pull or checkout, the git command will reload the game: Manual restarts may be required to to apply certain changes that would impact persistent scripts etc.
|
||||||
|
|
||||||
|
Once the contrib is set up, integrating remote changes is as simple as entering the following into your game:
|
||||||
|
|
||||||
|
```
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
The repositories you want to work with, be it only your local mygame repo, only Evennia core, or both, must be git directories for the command to function. If you are only interested in using this to get upstream Evennia changes, only the Evennia repository needs to be a git repository. [Get started with version control here.](https://www.evennia.com/docs/1.0-dev/Coding/Version-Control.html)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This package requires the dependency "gitpython", a python library used to interact with git repositories. To install, it's easiest to install Evennia's extra requirements:
|
||||||
|
|
||||||
|
- Activate your `virtualenv`
|
||||||
|
- `cd` to the root of the Evennia repository. There should be an `requirements_extra.txt` file here.
|
||||||
|
- `pip install -r requirements_extra.txt`
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This utility adds a simple assortment of 'git' commands. Import the module into your commands and add it to your command set to make it available.
|
||||||
|
|
||||||
|
Specifically, in `mygame/commands/default_cmdsets.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
...
|
||||||
|
from evennia.contrib.utils.git_integration import GitCmdSet # <---
|
||||||
|
|
||||||
|
class CharacterCmdset(default_cmds.Character_CmdSet):
|
||||||
|
...
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
...
|
||||||
|
self.add(GitCmdSet) # <---
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `reload` to make the git command available.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This utility will only work if the directory you wish to work with is a git directory. If they are not, you will be prompted to initiate your directory as a git repository using the following commands in your terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
git init
|
||||||
|
git remote add origin 'link to your repository'
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the git commands are only available to those with Developer permissions and higher. You can change this by overriding the command and setting its locks from "cmd:pperm(Developer)" to the lock of your choice.
|
||||||
|
|
||||||
|
The supported commands are:
|
||||||
|
* git status: An overview of your git repository, which files have been changed locally, and the commit you're on.
|
||||||
|
* git branch: What branches are available for you to check out.
|
||||||
|
* git checkout 'branch': Checkout a branch.
|
||||||
|
* git pull: Pull the latest code from your current branch.
|
||||||
|
|
||||||
|
* All of these commands are also available with 'evennia', to serve the same functionality related to your Evennia directory. So:
|
||||||
|
* git evennia status
|
||||||
|
* git evennia branch
|
||||||
|
* git evennia checkout 'branch'
|
||||||
|
* git evennia pull: Pull the latest Evennia code.
|
||||||
|
|
||||||
|
## Settings Used
|
||||||
|
|
||||||
|
The utility uses the existing GAME_DIR and EVENNIA_DIR settings from settings.py. You should not need to alter these if you have a standard directory setup, they ought to exist without any setup required from you.
|
||||||
5
evennia/contrib/utils/git_integration/__init__.py
Normal file
5
evennia/contrib/utils/git_integration/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
"""
|
||||||
|
Git integration - helpme 2022
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .git_integration import GitCmdSet # noqa
|
||||||
196
evennia/contrib/utils/git_integration/git_integration.py
Normal file
196
evennia/contrib/utils/git_integration/git_integration.py
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from evennia import CmdSet, InterruptCommand
|
||||||
|
from evennia.utils.utils import list_to_string
|
||||||
|
from evennia.commands.default.muxcommand import MuxCommand
|
||||||
|
from evennia.server.sessionhandler import SESSIONS
|
||||||
|
|
||||||
|
import git
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class GitCommand(MuxCommand):
|
||||||
|
"""
|
||||||
|
The shared functionality between git/git evennia
|
||||||
|
"""
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
"""
|
||||||
|
Parse the arguments, set default arg to 'status' and check for existence of currently targeted repo
|
||||||
|
"""
|
||||||
|
super().parse()
|
||||||
|
|
||||||
|
if self.args:
|
||||||
|
split_args = self.args.strip().split(" ", 1)
|
||||||
|
self.action = split_args[0]
|
||||||
|
if len(split_args) > 1:
|
||||||
|
self.args = ''.join(split_args[1:])
|
||||||
|
else:
|
||||||
|
self.args = ''
|
||||||
|
else:
|
||||||
|
self.action = "status"
|
||||||
|
self.args = ""
|
||||||
|
|
||||||
|
self.err_msgs = ["|rInvalid Git Repository|n:",
|
||||||
|
"The {repo_type} repository is not recognized as a git directory.",
|
||||||
|
"In order to initialize it as a git directory, you will need to access your terminal and run the following commands from within your directory:",
|
||||||
|
" git init",
|
||||||
|
" git remote add origin {remote_link}"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.repo = git.Repo(self.directory, search_parent_directories=True)
|
||||||
|
except git.exc.InvalidGitRepositoryError:
|
||||||
|
err_msg = '\n'.join(self.err_msgs).format(repo_type=self.repo_type, remote_link=self.remote_link)
|
||||||
|
self.caller.msg(err_msg)
|
||||||
|
raise InterruptCommand
|
||||||
|
|
||||||
|
self.commit = self.repo.head.commit
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.branch = self.repo.active_branch.name
|
||||||
|
except TypeError as type_err:
|
||||||
|
self.caller.msg(type_err)
|
||||||
|
raise InterruptCommand
|
||||||
|
|
||||||
|
def short_sha(self, repo, hexsha):
|
||||||
|
"""
|
||||||
|
Utility: Get the short SHA of a commit.
|
||||||
|
"""
|
||||||
|
short_sha = repo.git.rev_parse(hexsha, short=True)
|
||||||
|
return short_sha
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
"""
|
||||||
|
Retrieves the status of the active git repository, displaying unstaged changes/untracked files.
|
||||||
|
"""
|
||||||
|
time_of_commit = datetime.datetime.fromtimestamp(self.commit.committed_date)
|
||||||
|
status_msg = '\n'.join([f"Branch: |w{self.branch}|n ({self.repo.git.rev_parse(self.commit.hexsha, short=True)}) ({time_of_commit})",
|
||||||
|
f"By {self.commit.author.email}: {self.commit.message}"])
|
||||||
|
|
||||||
|
changedFiles = { item.a_path for item in self.repo.index.diff(None) }
|
||||||
|
if changedFiles:
|
||||||
|
status_msg += f"Unstaged/uncommitted changes:|/ |g{'|/ '.join(changedFiles)}|n|/"
|
||||||
|
if len(self.repo.untracked_files) > 0:
|
||||||
|
status_msg += f"Untracked files:|/ |x{'|/ '.join(self.repo.untracked_files)}|n"
|
||||||
|
return status_msg
|
||||||
|
|
||||||
|
def get_branches(self):
|
||||||
|
"""
|
||||||
|
Display current and available branches.
|
||||||
|
"""
|
||||||
|
remote_refs = self.repo.remote().refs
|
||||||
|
branch_msg = f"Current branch: |w{self.branch}|n. Branches available: {list_to_string(remote_refs)}"
|
||||||
|
return branch_msg
|
||||||
|
|
||||||
|
def checkout(self):
|
||||||
|
"""
|
||||||
|
Check out a specific branch.
|
||||||
|
"""
|
||||||
|
remote_refs = self.repo.remote().refs
|
||||||
|
to_branch = self.args.strip().removeprefix('origin/') # Slightly hacky, but git tacks on the origin/
|
||||||
|
|
||||||
|
if to_branch not in remote_refs:
|
||||||
|
self.caller.msg(f"Branch '{to_branch}' not available.")
|
||||||
|
return False
|
||||||
|
elif to_branch == self.branch:
|
||||||
|
self.caller.msg(f"Already on |w{to_branch}|n. Maybe you want <git pull>?")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.repo.git.checkout(to_branch)
|
||||||
|
except git.exc.GitCommandError as err:
|
||||||
|
self.caller.msg("Couldn't checkout {} ({})".format(to_branch, err.stderr.strip()))
|
||||||
|
return False
|
||||||
|
self.msg(f"Checked out |w{to_branch}|n successfully. Server restart initiated.")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def pull(self):
|
||||||
|
"""
|
||||||
|
Attempt to pull new code.
|
||||||
|
"""
|
||||||
|
old_commit = self.commit
|
||||||
|
try:
|
||||||
|
self.repo.remotes.origin.pull()
|
||||||
|
except git.exc.GitCommandError as err:
|
||||||
|
self.caller.msg("Couldn't pull {} ({})".format(self.branch, err.stderr.strip()))
|
||||||
|
return False
|
||||||
|
if old_commit == self.repo.head.commit:
|
||||||
|
self.caller.msg("No new code to pull, no need to reset.\n")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.caller.msg(f"You have pulled new code. Server restart initiated.|/Head now at {self.repo.git.rev_parse(self.repo.head.commit.hexsha, short=True)}.|/Author: {self.repo.head.commit.author.name} ({self.repo.head.commit.author.email})|/{self.repo.head.commit.message.strip()}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"""
|
||||||
|
Provide basic Git functionality within the game.
|
||||||
|
"""
|
||||||
|
caller = self.caller
|
||||||
|
|
||||||
|
if self.action == "status":
|
||||||
|
caller.msg(self.get_status())
|
||||||
|
elif self.action == "branch" or (self.action == "checkout" and not self.args):
|
||||||
|
caller.msg(self.get_branches())
|
||||||
|
elif self.action == "checkout":
|
||||||
|
if self.checkout():
|
||||||
|
SESSIONS.portal_restart_server()
|
||||||
|
elif self.action == "pull":
|
||||||
|
if self.pull():
|
||||||
|
SESSIONS.portal_restart_server()
|
||||||
|
else:
|
||||||
|
caller.msg("You can only git status, git branch, git checkout, or git pull.")
|
||||||
|
return
|
||||||
|
|
||||||
|
class CmdGitEvennia(GitCommand):
|
||||||
|
"""
|
||||||
|
Pull the latest code from the evennia core or checkout a different branch.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
git evennia status - View an overview of the evennia repository status.
|
||||||
|
git evennia branch - View available branches in evennia.
|
||||||
|
git evennia checkout <branch> - Checkout a different branch in evennia.
|
||||||
|
git evennia pull - Pull the latest evennia code.
|
||||||
|
|
||||||
|
For updating your local mygame repository, the same commands are available with 'git'.
|
||||||
|
|
||||||
|
If there are any conflicts encountered, the command will abort. The command will reload your game after pulling new code automatically, but for some changes involving persistent scripts etc, you may need to manually restart.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "git evennia"
|
||||||
|
locks = "cmd:pperm(Developer)"
|
||||||
|
help_category = "System"
|
||||||
|
directory = settings.EVENNIA_DIR
|
||||||
|
repo_type = "Evennia"
|
||||||
|
remote_link = "https://github.com/evennia/evennia.git"
|
||||||
|
|
||||||
|
|
||||||
|
class CmdGit(GitCommand):
|
||||||
|
"""
|
||||||
|
Pull the latest code from your repository or checkout a different branch.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
git status - View an overview of your git repository.
|
||||||
|
git branch - View available branches.
|
||||||
|
git checkout main - Checkout the main branch of your code.
|
||||||
|
git pull - Pull the latest code from your current branch.
|
||||||
|
|
||||||
|
For updating evennia code, the same commands are available with 'git evennia'.
|
||||||
|
|
||||||
|
If there are any conflicts encountered, the command will abort. The command will reload your game after pulling new code automatically, but for changes involving persistent scripts etc, you may need to manually restart.
|
||||||
|
"""
|
||||||
|
|
||||||
|
key = "git"
|
||||||
|
locks = "cmd:pperm(Developer)"
|
||||||
|
help_category = "System"
|
||||||
|
directory = settings.GAME_DIR
|
||||||
|
repo_type = "game"
|
||||||
|
remote_link = "[your remote link]"
|
||||||
|
|
||||||
|
|
||||||
|
# CmdSet for easily install all commands
|
||||||
|
class GitCmdSet(CmdSet):
|
||||||
|
"""
|
||||||
|
The git command.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
self.add(CmdGit)
|
||||||
|
self.add(CmdGitEvennia)
|
||||||
69
evennia/contrib/utils/git_integration/tests.py
Normal file
69
evennia/contrib/utils/git_integration/tests.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
"""
|
||||||
|
Tests of git.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from evennia.utils.test_resources import EvenniaTest
|
||||||
|
from evennia.contrib.utils.git_integration.git_integration import GitCommand
|
||||||
|
from evennia.utils.utils import list_to_string
|
||||||
|
|
||||||
|
import git
|
||||||
|
import mock
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
class TestGitIntegration(EvenniaTest):
|
||||||
|
@mock.patch("git.Repo")
|
||||||
|
@mock.patch("git.Git")
|
||||||
|
@mock.patch("git.Actor")
|
||||||
|
def setUp(self, mock_git, mock_repo, mock_author):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
self.char1.msg = mock.Mock()
|
||||||
|
|
||||||
|
p = mock_git.return_value = False
|
||||||
|
type(mock_repo.clone_from.return_value).bare = p
|
||||||
|
mock_repo.index.add(["mock.txt"])
|
||||||
|
mock_git.Repo.side_effect = git.exc.InvalidGitRepositoryError
|
||||||
|
|
||||||
|
mock_author.name = "Faux Author"
|
||||||
|
mock_author.email = "a@email.com"
|
||||||
|
|
||||||
|
commit_date = datetime.datetime(2021, 2, 1)
|
||||||
|
|
||||||
|
mock_repo.index.commit(
|
||||||
|
"Initial skeleton",
|
||||||
|
author=mock_author,
|
||||||
|
committer=mock_author,
|
||||||
|
author_date=commit_date,
|
||||||
|
commit_date=commit_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
test_cmd_git = GitCommand()
|
||||||
|
self.repo = test_cmd_git.repo = mock_repo
|
||||||
|
self.commit = test_cmd_git.commit = mock_git.head.commit
|
||||||
|
self.branch = test_cmd_git.branch = mock_git.active_branch.name
|
||||||
|
test_cmd_git.caller = self.char1
|
||||||
|
test_cmd_git.args = "nonexistent_branch"
|
||||||
|
self.test_cmd_git = test_cmd_git
|
||||||
|
|
||||||
|
def test_git_status(self):
|
||||||
|
time_of_commit = datetime.datetime.fromtimestamp(self.test_cmd_git.commit.committed_date)
|
||||||
|
status_msg = '\n'.join([f"Branch: |w{self.test_cmd_git.branch}|n ({self.test_cmd_git.repo.git.rev_parse(self.test_cmd_git.commit.hexsha, short=True)}) ({time_of_commit})",
|
||||||
|
f"By {self.test_cmd_git.commit.author.email}: {self.test_cmd_git.commit.message}"])
|
||||||
|
self.assertEqual(status_msg, self.test_cmd_git.get_status())
|
||||||
|
|
||||||
|
def test_git_branch(self):
|
||||||
|
# View current branch
|
||||||
|
remote_refs = self.test_cmd_git.repo.remote().refs
|
||||||
|
branch_msg = f"Current branch: |w{self.test_cmd_git.branch}|n. Branches available: {list_to_string(remote_refs)}"
|
||||||
|
self.assertEqual(branch_msg, self.test_cmd_git.get_branches())
|
||||||
|
|
||||||
|
def test_git_checkout(self):
|
||||||
|
# Checkout no branch
|
||||||
|
self.test_cmd_git.checkout()
|
||||||
|
self.char1.msg.assert_called_with("Branch 'nonexistent_branch' not available.")
|
||||||
|
|
||||||
|
def test_git_pull(self):
|
||||||
|
self.test_cmd_git.pull()
|
||||||
|
self.char1.msg.assert_called_with(f"You have pulled new code. Server restart initiated.|/Head now at {self.repo.git.rev_parse(self.repo.head.commit.hexsha, short=True)}.|/Author: {self.repo.head.commit.author.name} ({self.repo.head.commit.author.email})|/{self.repo.head.commit.message.strip()}")
|
||||||
|
|
||||||
|
|
@ -689,13 +689,13 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
exclude (list, optional): A list of objects not to send to.
|
exclude (list, optional): A list of objects not to send to.
|
||||||
from_obj (Object, optional): An object designated as the
|
from_obj (Object, optional): An object designated as the
|
||||||
"sender" of the message. See `DefaultObject.msg()` for
|
"sender" of the message. See `DefaultObject.msg()` for
|
||||||
more info.
|
more info. This will be used for `$You/you` if using funcparser inlines.
|
||||||
mapping (dict, optional): A mapping of formatting keys
|
mapping (dict, optional): A mapping of formatting keys
|
||||||
`{"key":<object>, "key2":<object2>,...}.
|
`{"key":<object>, "key2":<object2>,...}.
|
||||||
The keys must either match `{key}` or `$You(key)/$you(key)` markers
|
The keys must either match `{key}` or `$You(key)/$you(key)` markers
|
||||||
in the `text` string. If `<object>` doesn't have a `get_display_name`
|
in the `text` string. If `<object>` doesn't have a `get_display_name`
|
||||||
method, it will be returned as a string. If not set, a key `you` will
|
method, it will be returned as a string. Pass "you" to represent the caller,
|
||||||
be auto-added to point to `from_obj` if given, otherwise to `self`.
|
this can be skipped if `from_obj` is provided (that will then act as 'you').
|
||||||
raise_funcparse_errors (bool, optional): If set, a failing `$func()` will
|
raise_funcparse_errors (bool, optional): If set, a failing `$func()` will
|
||||||
lead to an outright error. If unset (default), the failing `$func()`
|
lead to an outright error. If unset (default), the failing `$func()`
|
||||||
will instead appear in output unparsed.
|
will instead appear in output unparsed.
|
||||||
|
|
@ -728,6 +728,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
char.location.msg_contents(
|
char.location.msg_contents(
|
||||||
"$You() $conj(attack) $you(defender).",
|
"$You() $conj(attack) $you(defender).",
|
||||||
|
from_obj=player1,
|
||||||
mapping={"defender": player2})
|
mapping={"defender": player2})
|
||||||
|
|
||||||
- player1 will see `You attack The Second girl.`
|
- player1 will see `You attack The Second girl.`
|
||||||
|
|
@ -738,7 +739,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
|
|
||||||
char.location.msg_contents(
|
char.location.msg_contents(
|
||||||
"{attacker} attacks {defender}.",
|
"{attacker} attacks {defender}.",
|
||||||
mapping={"attacker:player1, "defender":player2})
|
mapping={"attacker":player1, "defender":player2})
|
||||||
|
|
||||||
- player1 will see: 'Player1 attacks The Second girl.'
|
- player1 will see: 'Player1 attacks The Second girl.'
|
||||||
- player2 will see: 'The First girl attacks Player2'
|
- player2 will see: 'The First girl attacks Player2'
|
||||||
|
|
@ -762,7 +763,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
for receiver in contents:
|
for receiver in contents:
|
||||||
|
|
||||||
# actor-stance replacements
|
# actor-stance replacements
|
||||||
inmessage = _MSG_CONTENTS_PARSER.parse(
|
outmessage = _MSG_CONTENTS_PARSER.parse(
|
||||||
inmessage,
|
inmessage,
|
||||||
raise_errors=raise_funcparse_errors,
|
raise_errors=raise_funcparse_errors,
|
||||||
return_string=True,
|
return_string=True,
|
||||||
|
|
@ -772,7 +773,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# director-stance replacements
|
# director-stance replacements
|
||||||
outmessage = inmessage.format_map(
|
outmessage = outmessage.format_map(
|
||||||
{
|
{
|
||||||
key: obj.get_display_name(looker=receiver)
|
key: obj.get_display_name(looker=receiver)
|
||||||
if hasattr(obj, "get_display_name")
|
if hasattr(obj, "get_display_name")
|
||||||
|
|
@ -1690,8 +1691,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase):
|
||||||
access_type (str): The type of access that was requested.
|
access_type (str): The type of access that was requested.
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
Not used by default, added for possible expandability in a
|
Unused by default, added for possible expandability in a game.
|
||||||
game.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
@ -3133,3 +3133,20 @@ class DefaultExit(DefaultObject):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
traversing_object.msg(_("You cannot go there."))
|
traversing_object.msg(_("You cannot go there."))
|
||||||
|
|
||||||
|
def get_return_exit(self, return_all=False):
|
||||||
|
"""
|
||||||
|
Get the exits that pair with this one in its destination room
|
||||||
|
(i.e. returns to its location)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
return_all (bool): Whether to return available results as a
|
||||||
|
list or single matching exit.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
queryset or exit (Exit): The matching exit(s).
|
||||||
|
"""
|
||||||
|
query = ObjectDB.objects.filter(db_location=self.destination, db_destination=self.location)
|
||||||
|
if return_all:
|
||||||
|
return query
|
||||||
|
return query.first()
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,23 @@ class DefaultObjectTest(BaseEvenniaTest):
|
||||||
self.assertEqual(description, obj.db.desc)
|
self.assertEqual(description, obj.db.desc)
|
||||||
self.assertEqual(obj.db.creator_ip, self.ip)
|
self.assertEqual(obj.db.creator_ip, self.ip)
|
||||||
|
|
||||||
|
def test_exit_get_return_exit(self):
|
||||||
|
ex1, _ = DefaultExit.create("north", self.room1, self.room2, account=self.account)
|
||||||
|
single_return_exit = ex1.get_return_exit()
|
||||||
|
all_return_exit = ex1.get_return_exit(return_all=True)
|
||||||
|
self.assertEqual(single_return_exit, None)
|
||||||
|
self.assertEqual(len(all_return_exit), 0)
|
||||||
|
|
||||||
|
ex2, _ = DefaultExit.create("south", self.room2, self.room1, account=self.account)
|
||||||
|
single_return_exit = ex1.get_return_exit()
|
||||||
|
all_return_exit = ex1.get_return_exit(return_all=True)
|
||||||
|
self.assertEqual(single_return_exit, ex2)
|
||||||
|
self.assertEqual(len(all_return_exit), 1)
|
||||||
|
|
||||||
|
ex3, _ = DefaultExit.create("also_south", self.room2, self.room1, account=self.account)
|
||||||
|
all_return_exit = ex1.get_return_exit(return_all=True)
|
||||||
|
self.assertEqual(len(all_return_exit), 2)
|
||||||
|
|
||||||
def test_urls(self):
|
def test_urls(self):
|
||||||
"Make sure objects are returning URLs"
|
"Make sure objects are returning URLs"
|
||||||
self.assertTrue(self.char1.get_absolute_url())
|
self.assertTrue(self.char1.get_absolute_url())
|
||||||
|
|
|
||||||
|
|
@ -7,29 +7,29 @@ Handling storage of prototypes, both database-based ones (DBPrototypes) and thos
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Q
|
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from evennia.scripts.scripts import DefaultScript
|
from evennia.locks.lockhandler import check_lockstring, validate_lockstring
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
|
from evennia.scripts.scripts import DefaultScript
|
||||||
from evennia.typeclasses.attributes import Attribute
|
from evennia.typeclasses.attributes import Attribute
|
||||||
|
from evennia.utils import dbserialize, logger
|
||||||
from evennia.utils.create import create_script
|
from evennia.utils.create import create_script
|
||||||
from evennia.utils.evmore import EvMore
|
from evennia.utils.evmore import EvMore
|
||||||
|
from evennia.utils.evtable import EvTable
|
||||||
|
from evennia.utils.funcparser import FuncParser
|
||||||
from evennia.utils.utils import (
|
from evennia.utils.utils import (
|
||||||
all_from_module,
|
all_from_module,
|
||||||
variable_from_module,
|
|
||||||
make_iter,
|
|
||||||
is_iter,
|
|
||||||
dbid_to_obj,
|
|
||||||
justify,
|
|
||||||
class_from_module,
|
class_from_module,
|
||||||
|
dbid_to_obj,
|
||||||
|
is_iter,
|
||||||
|
justify,
|
||||||
|
make_iter,
|
||||||
|
variable_from_module,
|
||||||
)
|
)
|
||||||
from evennia.locks.lockhandler import validate_lockstring, check_lockstring
|
|
||||||
from evennia.utils import logger
|
|
||||||
from evennia.utils.funcparser import FuncParser
|
|
||||||
from evennia.utils import dbserialize
|
|
||||||
from evennia.utils.evtable import EvTable
|
|
||||||
|
|
||||||
_MODULE_PROTOTYPE_MODULES = {}
|
_MODULE_PROTOTYPE_MODULES = {}
|
||||||
_MODULE_PROTOTYPES = {}
|
_MODULE_PROTOTYPES = {}
|
||||||
|
|
@ -925,7 +925,13 @@ def validate_prototype(
|
||||||
|
|
||||||
|
|
||||||
def protfunc_parser(
|
def protfunc_parser(
|
||||||
value, available_functions=None, testing=False, stacktrace=False, caller=None, **kwargs
|
value,
|
||||||
|
available_functions=None,
|
||||||
|
testing=False,
|
||||||
|
stacktrace=False,
|
||||||
|
caller=None,
|
||||||
|
raise_errors=True,
|
||||||
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Parse a prototype value string for a protfunc and process it.
|
Parse a prototype value string for a protfunc and process it.
|
||||||
|
|
@ -939,6 +945,7 @@ def protfunc_parser(
|
||||||
available_functions (dict, optional): Mapping of name:protfunction to use for this parsing.
|
available_functions (dict, optional): Mapping of name:protfunction to use for this parsing.
|
||||||
If not set, use default sources.
|
If not set, use default sources.
|
||||||
stacktrace (bool, optional): If set, print the stack parsing process of the protfunc-parser.
|
stacktrace (bool, optional): If set, print the stack parsing process of the protfunc-parser.
|
||||||
|
raise_errors (bool, optional): Raise explicit errors from malformed/not found protfunc calls.
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
session (Session): Passed to protfunc. Session of the entity spawning the prototype.
|
session (Session): Passed to protfunc. Session of the entity spawning the prototype.
|
||||||
|
|
@ -957,7 +964,7 @@ def protfunc_parser(
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, str):
|
||||||
return value
|
return value
|
||||||
|
|
||||||
result = FUNC_PARSER.parse_to_any(value, raise_errors=True, caller=caller, **kwargs)
|
result = FUNC_PARSER.parse_to_any(value, raise_errors=raise_errors, caller=caller, **kwargs)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
@ -1100,7 +1107,9 @@ def check_permission(prototype_key, action, default=True):
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
def init_spawn_value(value, validator=None, caller=None, prototype=None):
|
def init_spawn_value(
|
||||||
|
value, validator=None, caller=None, prototype=None, protfunc_raise_errors=True
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Analyze the prototype value and produce a value useful at the point of spawning.
|
Analyze the prototype value and produce a value useful at the point of spawning.
|
||||||
|
|
||||||
|
|
@ -1128,7 +1137,9 @@ def init_spawn_value(value, validator=None, caller=None, prototype=None):
|
||||||
value = validator(value[0](*make_iter(args)))
|
value = validator(value[0](*make_iter(args)))
|
||||||
else:
|
else:
|
||||||
value = validator(value)
|
value = validator(value)
|
||||||
result = protfunc_parser(value, caller=caller, prototype=prototype)
|
result = protfunc_parser(
|
||||||
|
value, caller=caller, prototype=prototype, raise_errors=protfunc_raise_errors
|
||||||
|
)
|
||||||
if result != value:
|
if result != value:
|
||||||
return validator(result)
|
return validator(result)
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -137,21 +137,19 @@ import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
import evennia
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
import evennia
|
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.utils import logger
|
|
||||||
from evennia.utils.utils import make_iter, is_iter
|
|
||||||
from evennia.prototypes import prototypes as protlib
|
from evennia.prototypes import prototypes as protlib
|
||||||
from evennia.prototypes.prototypes import (
|
from evennia.prototypes.prototypes import (
|
||||||
|
PROTOTYPE_TAG_CATEGORY,
|
||||||
|
init_spawn_value,
|
||||||
value_to_obj,
|
value_to_obj,
|
||||||
value_to_obj_or_any,
|
value_to_obj_or_any,
|
||||||
init_spawn_value,
|
|
||||||
PROTOTYPE_TAG_CATEGORY,
|
|
||||||
)
|
)
|
||||||
|
from evennia.utils import logger
|
||||||
|
from evennia.utils.utils import is_iter, make_iter
|
||||||
|
|
||||||
_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
|
_CREATE_OBJECT_KWARGS = ("key", "location", "home", "destination")
|
||||||
_PROTOTYPE_META_NAMES = (
|
_PROTOTYPE_META_NAMES = (
|
||||||
|
|
@ -634,7 +632,7 @@ def format_diff(diff, minimal=True):
|
||||||
|
|
||||||
|
|
||||||
def batch_update_objects_with_prototype(
|
def batch_update_objects_with_prototype(
|
||||||
prototype, diff=None, objects=None, exact=False, caller=None
|
prototype, diff=None, objects=None, exact=False, caller=None, protfunc_raise_errors=True
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Update existing objects with the latest version of the prototype.
|
Update existing objects with the latest version of the prototype.
|
||||||
|
|
@ -653,6 +651,8 @@ def batch_update_objects_with_prototype(
|
||||||
objects will be removed if they exist. This will lead to a more accurate 1:1 correlation
|
objects will be removed if they exist. This will lead to a more accurate 1:1 correlation
|
||||||
between the object and the prototype but is usually impractical.
|
between the object and the prototype but is usually impractical.
|
||||||
caller (Object or Account, optional): This may be used by protfuncs to do permission checks.
|
caller (Object or Account, optional): This may be used by protfuncs to do permission checks.
|
||||||
|
protfunc_raise_errors (bool): Have protfuncs raise explicit errors if malformed/not found.
|
||||||
|
This is highly recommended.
|
||||||
Returns:
|
Returns:
|
||||||
changed (int): The number of objects that had changes applied to them.
|
changed (int): The number of objects that had changes applied to them.
|
||||||
|
|
||||||
|
|
@ -704,7 +704,13 @@ def batch_update_objects_with_prototype(
|
||||||
do_save = True
|
do_save = True
|
||||||
|
|
||||||
def _init(val, typ):
|
def _init(val, typ):
|
||||||
return init_spawn_value(val, str, caller=caller, prototype=new_prototype)
|
return init_spawn_value(
|
||||||
|
val,
|
||||||
|
str,
|
||||||
|
caller=caller,
|
||||||
|
prototype=new_prototype,
|
||||||
|
protfunc_raise_errors=protfunc_raise_errors,
|
||||||
|
)
|
||||||
|
|
||||||
if key == "key":
|
if key == "key":
|
||||||
obj.db_key = _init(val, str)
|
obj.db_key = _init(val, str)
|
||||||
|
|
@ -892,6 +898,8 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
custom `prototype_parents` are given to this function.
|
custom `prototype_parents` are given to this function.
|
||||||
only_validate (bool): Only run validation of prototype/parents
|
only_validate (bool): Only run validation of prototype/parents
|
||||||
(no object creation) and return the create-kwargs.
|
(no object creation) and return the create-kwargs.
|
||||||
|
protfunc_raise_errors (bool): Raise explicit exceptions on a malformed/not-found
|
||||||
|
protfunc. Defaults to True.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
object (Object, dict or list): Spawned object(s). If `only_validate` is given, return
|
object (Object, dict or list): Spawned object(s). If `only_validate` is given, return
|
||||||
|
|
@ -938,57 +946,55 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
# extract the keyword args we need to create the object itself. If we get a callable,
|
# extract the keyword args we need to create the object itself. If we get a callable,
|
||||||
# call that to get the value (don't catch errors)
|
# call that to get the value (don't catch errors)
|
||||||
create_kwargs = {}
|
create_kwargs = {}
|
||||||
|
init_spawn_kwargs = dict(
|
||||||
|
caller=caller,
|
||||||
|
prototype=prototype,
|
||||||
|
protfunc_raise_errors=kwargs.get("protfunc_raise_errors", True),
|
||||||
|
)
|
||||||
|
|
||||||
# we must always add a key, so if not given we use a shortened md5 hash. There is a (small)
|
# we must always add a key, so if not given we use a shortened md5 hash. There is a (small)
|
||||||
# chance this is not unique but it should usually not be a problem.
|
# chance this is not unique but it should usually not be a problem.
|
||||||
val = prot.pop(
|
val = prot.pop(
|
||||||
"key",
|
"key",
|
||||||
"Spawned-{}".format(hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:6]),
|
"Spawned-{}".format(hashlib.md5(bytes(str(time.time()), "utf-8")).hexdigest()[:6]),
|
||||||
)
|
)
|
||||||
create_kwargs["db_key"] = init_spawn_value(val, str, caller=caller, prototype=prototype)
|
create_kwargs["db_key"] = init_spawn_value(val, str, **init_spawn_kwargs)
|
||||||
|
|
||||||
val = prot.pop("location", None)
|
val = prot.pop("location", None)
|
||||||
create_kwargs["db_location"] = init_spawn_value(
|
create_kwargs["db_location"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
|
||||||
val, value_to_obj, caller=caller, prototype=prototype
|
|
||||||
)
|
|
||||||
|
|
||||||
val = prot.pop("home", None)
|
val = prot.pop("home", None)
|
||||||
if val:
|
if val:
|
||||||
create_kwargs["db_home"] = init_spawn_value(
|
create_kwargs["db_home"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
|
||||||
val, value_to_obj, caller=caller, prototype=prototype
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
create_kwargs["db_home"] = init_spawn_value(
|
create_kwargs["db_home"] = init_spawn_value(
|
||||||
settings.DEFAULT_HOME, value_to_obj, caller=caller, prototype=prototype
|
settings.DEFAULT_HOME, value_to_obj, **init_spawn_kwargs
|
||||||
)
|
)
|
||||||
except ObjectDB.DoesNotExist:
|
except ObjectDB.DoesNotExist:
|
||||||
# settings.DEFAULT_HOME not existing is common for unittests
|
# settings.DEFAULT_HOME not existing is common for unittests
|
||||||
pass
|
pass
|
||||||
|
|
||||||
val = prot.pop("destination", None)
|
val = prot.pop("destination", None)
|
||||||
create_kwargs["db_destination"] = init_spawn_value(
|
create_kwargs["db_destination"] = init_spawn_value(val, value_to_obj, **init_spawn_kwargs)
|
||||||
val, value_to_obj, caller=caller, prototype=prototype
|
|
||||||
)
|
|
||||||
|
|
||||||
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
val = prot.pop("typeclass", settings.BASE_OBJECT_TYPECLASS)
|
||||||
create_kwargs["db_typeclass_path"] = init_spawn_value(
|
create_kwargs["db_typeclass_path"] = init_spawn_value(val, str, **init_spawn_kwargs)
|
||||||
val, str, caller=caller, prototype=prototype
|
|
||||||
)
|
|
||||||
|
|
||||||
# extract calls to handlers
|
# extract calls to handlers
|
||||||
val = prot.pop("permissions", [])
|
val = prot.pop("permissions", [])
|
||||||
permission_string = init_spawn_value(val, make_iter, caller=caller, prototype=prototype)
|
permission_string = init_spawn_value(val, make_iter, **init_spawn_kwargs)
|
||||||
val = prot.pop("locks", "")
|
val = prot.pop("locks", "")
|
||||||
lock_string = init_spawn_value(val, str, caller=caller, prototype=prototype)
|
lock_string = init_spawn_value(val, str, **init_spawn_kwargs)
|
||||||
val = prot.pop("aliases", [])
|
val = prot.pop("aliases", [])
|
||||||
alias_string = init_spawn_value(val, make_iter, caller=caller, prototype=prototype)
|
alias_string = init_spawn_value(val, make_iter, **init_spawn_kwargs)
|
||||||
|
|
||||||
val = prot.pop("tags", [])
|
val = prot.pop("tags", [])
|
||||||
tags = []
|
tags = []
|
||||||
for (tag, category, *data) in val:
|
for (tag, category, *data) in val:
|
||||||
tags.append(
|
tags.append(
|
||||||
(
|
(
|
||||||
init_spawn_value(tag, str, caller=caller, prototype=prototype),
|
init_spawn_value(tag, str, **init_spawn_kwargs),
|
||||||
category,
|
category,
|
||||||
data[0] if data else None,
|
data[0] if data else None,
|
||||||
)
|
)
|
||||||
|
|
@ -1000,13 +1006,13 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY))
|
tags.append((prototype_key, PROTOTYPE_TAG_CATEGORY))
|
||||||
|
|
||||||
val = prot.pop("exec", "")
|
val = prot.pop("exec", "")
|
||||||
execs = init_spawn_value(val, make_iter, caller=caller, prototype=prototype)
|
execs = init_spawn_value(val, make_iter, **init_spawn_kwargs)
|
||||||
|
|
||||||
# extract ndb assignments
|
# extract ndb assignments
|
||||||
nattributes = dict(
|
nattributes = dict(
|
||||||
(
|
(
|
||||||
key.split("_", 1)[1],
|
key.split("_", 1)[1],
|
||||||
init_spawn_value(val, value_to_obj, caller=caller, prototype=prototype),
|
init_spawn_value(val, value_to_obj, **init_spawn_kwargs),
|
||||||
)
|
)
|
||||||
for key, val in prot.items()
|
for key, val in prot.items()
|
||||||
if key.startswith("ndb_")
|
if key.startswith("ndb_")
|
||||||
|
|
@ -1019,7 +1025,7 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
attributes.append(
|
attributes.append(
|
||||||
(
|
(
|
||||||
attrname,
|
attrname,
|
||||||
init_spawn_value(value, caller=caller, prototype=prototype),
|
init_spawn_value(value, **init_spawn_kwargs),
|
||||||
rest[0] if rest else None,
|
rest[0] if rest else None,
|
||||||
rest[1] if len(rest) > 1 else None,
|
rest[1] if len(rest) > 1 else None,
|
||||||
)
|
)
|
||||||
|
|
@ -1036,9 +1042,7 @@ def spawn(*prototypes, caller=None, **kwargs):
|
||||||
simple_attributes.append(
|
simple_attributes.append(
|
||||||
(
|
(
|
||||||
key,
|
key,
|
||||||
init_spawn_value(
|
init_spawn_value(value, value_to_obj_or_any, **init_spawn_kwargs),
|
||||||
value, value_to_obj_or_any, caller=caller, prototype=prototype
|
|
||||||
),
|
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,20 @@ Unit tests for the prototypes and spawner
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from random import randint, sample
|
|
||||||
import mock
|
|
||||||
import uuid
|
import uuid
|
||||||
|
from random import randint, sample
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
import mock
|
||||||
from anything import Something
|
from anything import Something
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
from evennia.prototypes import menus as olc_menus
|
||||||
|
from evennia.prototypes import protfuncs as protofuncs
|
||||||
|
from evennia.prototypes import prototypes as protlib
|
||||||
|
from evennia.prototypes import spawner
|
||||||
|
from evennia.prototypes.prototypes import _PROTOTYPE_TAG_META_CATEGORY
|
||||||
from evennia.utils.test_resources import BaseEvenniaTest
|
from evennia.utils.test_resources import BaseEvenniaTest
|
||||||
from evennia.utils.tests.test_evmenu import TestEvMenu
|
from evennia.utils.tests.test_evmenu import TestEvMenu
|
||||||
from evennia.prototypes import spawner, prototypes as protlib
|
|
||||||
from evennia.prototypes import menus as olc_menus
|
|
||||||
from evennia.prototypes import protfuncs as protofuncs, spawner
|
|
||||||
|
|
||||||
from evennia.prototypes.prototypes import _PROTOTYPE_TAG_META_CATEGORY
|
|
||||||
|
|
||||||
_PROTPARENTS = {
|
_PROTPARENTS = {
|
||||||
"NOBODY": {},
|
"NOBODY": {},
|
||||||
|
|
@ -43,6 +44,11 @@ _PROTPARENTS = {
|
||||||
"key": "goblin archwizard",
|
"key": "goblin archwizard",
|
||||||
"prototype_parent": ("GOBLIN_WIZARD", "ARCHWIZARD"),
|
"prototype_parent": ("GOBLIN_WIZARD", "ARCHWIZARD"),
|
||||||
},
|
},
|
||||||
|
"ISSUE2908": {
|
||||||
|
"typeclass": "evennia.objects.objects.DefaultObject",
|
||||||
|
"key": "testobject_isse2909",
|
||||||
|
"location": "$choice($objlist(",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -163,10 +169,12 @@ class TestUtils(BaseEvenniaTest):
|
||||||
"key": "Obj",
|
"key": "Obj",
|
||||||
"home": Something,
|
"home": Something,
|
||||||
"location": Something,
|
"location": Something,
|
||||||
"locks": "call:true();control:perm(Developer);delete:perm(Admin);"
|
"locks": (
|
||||||
"drop:holds();"
|
"call:true();control:perm(Developer);delete:perm(Admin);"
|
||||||
"edit:perm(Admin);examine:perm(Builder);get:all();"
|
"drop:holds();"
|
||||||
"puppet:pperm(Developer);tell:perm(Admin);view:all()",
|
"edit:perm(Admin);examine:perm(Builder);get:all();"
|
||||||
|
"puppet:pperm(Developer);tell:perm(Admin);view:all()"
|
||||||
|
),
|
||||||
"prototype_desc": "Built from Obj",
|
"prototype_desc": "Built from Obj",
|
||||||
"prototype_key": Something,
|
"prototype_key": Something,
|
||||||
"prototype_locks": "spawn:all();edit:all()",
|
"prototype_locks": "spawn:all();edit:all()",
|
||||||
|
|
@ -183,10 +191,12 @@ class TestUtils(BaseEvenniaTest):
|
||||||
"home": Something,
|
"home": Something,
|
||||||
"key": "Obj",
|
"key": "Obj",
|
||||||
"location": Something,
|
"location": Something,
|
||||||
"locks": "call:true();control:perm(Developer);delete:perm(Admin);"
|
"locks": (
|
||||||
"drop:holds();"
|
"call:true();control:perm(Developer);delete:perm(Admin);"
|
||||||
"edit:perm(Admin);examine:perm(Builder);get:all();"
|
"drop:holds();"
|
||||||
"puppet:pperm(Developer);tell:perm(Admin);view:all()",
|
"edit:perm(Admin);examine:perm(Builder);get:all();"
|
||||||
|
"puppet:pperm(Developer);tell:perm(Admin);view:all()"
|
||||||
|
),
|
||||||
"new": "new_val",
|
"new": "new_val",
|
||||||
"permissions": ["Builder"],
|
"permissions": ["Builder"],
|
||||||
"prototype_desc": "New version of prototype",
|
"prototype_desc": "New version of prototype",
|
||||||
|
|
@ -962,3 +972,24 @@ class TestPartialTagAttributes(BaseEvenniaTest):
|
||||||
def test_partial_spawn(self):
|
def test_partial_spawn(self):
|
||||||
obj = spawner.spawn(self.prot)
|
obj = spawner.spawn(self.prot)
|
||||||
self.assertEqual(obj[0].key, self.prot["key"])
|
self.assertEqual(obj[0].key, self.prot["key"])
|
||||||
|
|
||||||
|
|
||||||
|
class TestIssue2908(BaseEvenniaTest):
|
||||||
|
"""
|
||||||
|
Test spawning a prototype with a nested protfunc, as per issue #2908.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_spawn_with_protfunc(self):
|
||||||
|
|
||||||
|
self.room1.tags.add("beach", category="zone")
|
||||||
|
|
||||||
|
prot = {
|
||||||
|
"prototype_key": "rock",
|
||||||
|
"typeclass": "evennia.objects.objects.DefaultObject",
|
||||||
|
"key": "a rock",
|
||||||
|
"location": "$choice($objlist(beach,category=zone,type=tag))",
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = spawner.spawn(prot, caller=self.char1)
|
||||||
|
self.assertEqual(obj[0].location, self.room1)
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ def create_objects():
|
||||||
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()"
|
"examine:perm(Developer);edit:false();delete:false();boot:false();msg:all();puppet:false()"
|
||||||
)
|
)
|
||||||
# we set this low so that quelling is more useful
|
# we set this low so that quelling is more useful
|
||||||
superuser_character.permissions.add("Player")
|
superuser_character.permissions.add("Developer")
|
||||||
superuser_character.save()
|
superuser_character.save()
|
||||||
|
|
||||||
superuser.attributes.add("_first_login", True)
|
superuser.attributes.add("_first_login", True)
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,8 @@ HTTP_LOG_FILE = os.path.join(LOG_DIR, "http_requests.log")
|
||||||
LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log")
|
LOCKWARNING_LOG_FILE = os.path.join(LOG_DIR, "lockwarnings.log")
|
||||||
# Number of lines to append to rotating channel logs when they rotate
|
# Number of lines to append to rotating channel logs when they rotate
|
||||||
CHANNEL_LOG_NUM_TAIL_LINES = 20
|
CHANNEL_LOG_NUM_TAIL_LINES = 20
|
||||||
# Max size (in bytes) of channel log files before they rotate
|
# Max size (in bytes) of channel log files before they rotate.
|
||||||
|
# Minimum is 1000 (1kB) but should usually be larger.
|
||||||
CHANNEL_LOG_ROTATE_SIZE = 1000000
|
CHANNEL_LOG_ROTATE_SIZE = 1000000
|
||||||
# Unused by default, but used by e.g. the MapSystem contrib. A place for storing
|
# Unused by default, but used by e.g. the MapSystem contrib. A place for storing
|
||||||
# semi-permanent data and avoid it being rebuilt over and over. It is created
|
# semi-permanent data and avoid it being rebuilt over and over. It is created
|
||||||
|
|
@ -584,9 +585,7 @@ OPTIONS_ACCOUNT_DEFAULT = {
|
||||||
"footer_text_color": ("Text inside Footer Lines.", "Color", "n"),
|
"footer_text_color": ("Text inside Footer Lines.", "Color", "n"),
|
||||||
"footer_fill": ("Fill for Footer Lines.", "Text", "="),
|
"footer_fill": ("Fill for Footer Lines.", "Text", "="),
|
||||||
"column_names_color": ("Table column header text.", "Color", "w"),
|
"column_names_color": ("Table column header text.", "Color", "w"),
|
||||||
"help_category_color": ("Help category names.", "Color", "n"),
|
"timezone": ("Timezone for dates.", "Timezone", "UTC"),
|
||||||
"help_entry_color": ("Help entry names.", "Color", "n"),
|
|
||||||
"timezone": ("Timezone for dates. @tz for a list.", "Timezone", "UTC"),
|
|
||||||
}
|
}
|
||||||
# Modules holding Option classes, responsible for serializing the option and
|
# Modules holding Option classes, responsible for serializing the option and
|
||||||
# calling validator functions on it. Same-named functions in modules added
|
# calling validator functions on it. Same-named functions in modules added
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,13 @@ all Attributes and TypedObjects).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import shlex
|
import shlex
|
||||||
from django.db.models import F, Q, Count, ExpressionWrapper, FloatField
|
|
||||||
|
from django.db.models import Count, ExpressionWrapper, F, FloatField, Q
|
||||||
from django.db.models.functions import Cast
|
from django.db.models.functions import Cast
|
||||||
from evennia.utils import idmapper
|
|
||||||
from evennia.utils.utils import make_iter, variable_from_module
|
|
||||||
from evennia.typeclasses.attributes import Attribute
|
from evennia.typeclasses.attributes import Attribute
|
||||||
from evennia.typeclasses.tags import Tag
|
from evennia.typeclasses.tags import Tag
|
||||||
|
from evennia.utils import idmapper
|
||||||
|
from evennia.utils.utils import class_from_module, make_iter, variable_from_module
|
||||||
|
|
||||||
__all__ = ("TypedObjectManager",)
|
__all__ = ("TypedObjectManager",)
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
|
|
@ -196,6 +197,8 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
||||||
query.append(("db_key", key))
|
query.append(("db_key", key))
|
||||||
if category:
|
if category:
|
||||||
query.append(("db_category", category))
|
query.append(("db_category", category))
|
||||||
|
else:
|
||||||
|
query.append(("db_category", None))
|
||||||
return _Tag.objects.filter(**dict(query))
|
return _Tag.objects.filter(**dict(query))
|
||||||
else:
|
else:
|
||||||
# search only among tags stored on on this model
|
# search only among tags stored on on this model
|
||||||
|
|
@ -537,15 +540,14 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
||||||
|
|
||||||
def typeclass_search(self, typeclass, include_children=False, include_parents=False):
|
def typeclass_search(self, typeclass, include_children=False, include_parents=False):
|
||||||
"""
|
"""
|
||||||
Searches through all objects returning those which has a
|
Searches through all objects returning those which are of the
|
||||||
certain typeclass. If location is set, limit search to objects
|
specified typeclass.
|
||||||
in that location.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
typeclass (str or class): A typeclass class or a python path to a typeclass.
|
typeclass (str or class): A typeclass class or a python path to a typeclass.
|
||||||
include_children (bool, optional): Return objects with
|
include_children (bool, optional): Return objects with
|
||||||
given typeclass *and* all children inheriting from this
|
given typeclass *and* all children inheriting from this
|
||||||
typeclass. Mutuall exclusive to `include_parents`.
|
typeclass. Mutually exclusive to `include_parents`.
|
||||||
include_parents (bool, optional): Return objects with
|
include_parents (bool, optional): Return objects with
|
||||||
given typeclass *and* all parents to this typeclass.
|
given typeclass *and* all parents to this typeclass.
|
||||||
Mutually exclusive to `include_children`.
|
Mutually exclusive to `include_children`.
|
||||||
|
|
@ -553,35 +555,28 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
||||||
Returns:
|
Returns:
|
||||||
objects (list): The objects found with the given typeclasses.
|
objects (list): The objects found with the given typeclasses.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ImportError: If the provided `typeclass` is not a valid typeclass or the
|
||||||
|
path to an existing typeclass.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if not callable(typeclass):
|
||||||
if callable(typeclass):
|
typeclass = class_from_module(typeclass)
|
||||||
cls = typeclass.__class__
|
|
||||||
typeclass = "%s.%s" % (cls.__module__, cls.__name__)
|
|
||||||
elif not isinstance(typeclass, str) and hasattr(typeclass, "path"):
|
|
||||||
typeclass = typeclass.path
|
|
||||||
|
|
||||||
# query objects of exact typeclass
|
|
||||||
query = Q(db_typeclass_path__exact=typeclass)
|
|
||||||
|
|
||||||
if include_children:
|
if include_children:
|
||||||
# build requests for child typeclass objects
|
query = typeclass.objects.all_family()
|
||||||
clsmodule, clsname = typeclass.rsplit(".", 1)
|
else:
|
||||||
cls = variable_from_module(clsmodule, clsname)
|
query = typeclass.objects.all()
|
||||||
subclasses = cls.__subclasses__()
|
|
||||||
if subclasses:
|
if include_parents:
|
||||||
for child in (child for child in subclasses if hasattr(child, "path")):
|
parents = typeclass.__mro__
|
||||||
query = query | Q(db_typeclass_path__exact=child.path)
|
|
||||||
elif include_parents:
|
|
||||||
# build requests for parent typeclass objects
|
|
||||||
clsmodule, clsname = typeclass.rsplit(".", 1)
|
|
||||||
cls = variable_from_module(clsmodule, clsname)
|
|
||||||
parents = cls.__mro__
|
|
||||||
if parents:
|
if parents:
|
||||||
|
parent_queries = []
|
||||||
for parent in (parent for parent in parents if hasattr(parent, "path")):
|
for parent in (parent for parent in parents if hasattr(parent, "path")):
|
||||||
query = query | Q(db_typeclass_path__exact=parent.path)
|
parent_queries.append(super().filter(db_typeclass_path__exact=parent.path))
|
||||||
# actually query the database
|
query = query.union(*parent_queries)
|
||||||
return super().filter(query)
|
|
||||||
|
return query
|
||||||
|
|
||||||
|
|
||||||
class TypeclassManager(TypedObjectManager):
|
class TypeclassManager(TypedObjectManager):
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Unit tests for typeclass base system
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from evennia.typeclasses import attributes
|
from evennia.objects.objects import DefaultObject
|
||||||
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTestCase
|
from evennia.utils.test_resources import BaseEvenniaTest, EvenniaTestCase
|
||||||
from mock import patch
|
from mock import patch
|
||||||
from parameterized import parameterized
|
from parameterized import parameterized
|
||||||
|
|
@ -213,6 +213,82 @@ class TestTypedObjectManager(BaseEvenniaTest):
|
||||||
self.assertEqual(tagobj.db_data, "data4")
|
self.assertEqual(tagobj.db_data, "data4")
|
||||||
|
|
||||||
|
|
||||||
|
# setting up testing typeclass with child- and parent class
|
||||||
|
class TestSearchManagerTypeclassParent(DefaultObject):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestSearchManagerTypeclass(TestSearchManagerTypeclassParent):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestSearchManagerTypeclassChild(TestSearchManagerTypeclass):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestSearchTypeclassFamily(EvenniaTestCase):
|
||||||
|
"""
|
||||||
|
Test the manager method for searching for inheriting typeclasses.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.obj_parent, _ = TestSearchManagerTypeclassParent.create(key="obj_parent")
|
||||||
|
self.obj1, _ = TestSearchManagerTypeclass.create(key="obj1")
|
||||||
|
self.obj2, _ = TestSearchManagerTypeclass.create(key="obj2")
|
||||||
|
self.obj_child, _ = TestSearchManagerTypeclassChild.create(key="obj_child")
|
||||||
|
|
||||||
|
def test_typeclass_search__inputs(self):
|
||||||
|
"""Test basic functionality"""
|
||||||
|
|
||||||
|
res1 = self.obj1.__class__.objects.typeclass_search(self.obj1.__class__)
|
||||||
|
res2 = self.obj1.__class__.objects.typeclass_search(
|
||||||
|
"evennia.typeclasses.tests.TestSearchManagerTypeclass"
|
||||||
|
)
|
||||||
|
self.assertEqual(set(res1), {self.obj1, self.obj2})
|
||||||
|
self.assertEqual(set(res2), {self.obj1, self.obj2})
|
||||||
|
|
||||||
|
def test_typeclass_search__children_and_parents(self):
|
||||||
|
"""Test getting parents/child classes"""
|
||||||
|
|
||||||
|
# just the objects of this typeclass
|
||||||
|
res1 = self.obj1.__class__.objects.typeclass_search(self.obj1.__class__)
|
||||||
|
res2 = self.obj2.__class__.objects.typeclass_search(self.obj2.__class__)
|
||||||
|
|
||||||
|
# these objects + children
|
||||||
|
res3 = self.obj1.__class__.objects.typeclass_search(
|
||||||
|
self.obj1.__class__, include_children=True
|
||||||
|
)
|
||||||
|
# these objects + parents
|
||||||
|
res4 = self.obj1.__class__.objects.typeclass_search(
|
||||||
|
self.obj1.__class__, include_parents=True
|
||||||
|
)
|
||||||
|
# these objects + parents + children
|
||||||
|
res5 = self.obj1.__class__.objects.typeclass_search(
|
||||||
|
self.obj1.__class__, include_children=True, include_parents=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(set(res1), {self.obj1, self.obj2})
|
||||||
|
self.assertEqual(set(res2), {self.obj1, self.obj2})
|
||||||
|
self.assertEqual(set(res3), {self.obj1, self.obj2, self.obj_child})
|
||||||
|
self.assertEqual(set(res4), {self.obj1, self.obj2, self.obj_parent})
|
||||||
|
self.assertEqual(set(res5), {self.obj1, self.obj2, self.obj_child, self.obj_parent})
|
||||||
|
|
||||||
|
def test_typeclass_search__nested(self):
|
||||||
|
"""Test several levels deep searches"""
|
||||||
|
# check all children of the parent
|
||||||
|
res1 = self.obj1.__class__.objects.typeclass_search(
|
||||||
|
self.obj_parent.__class__, include_children=True
|
||||||
|
)
|
||||||
|
# check all parents of the child
|
||||||
|
res2 = self.obj1.__class__.objects.typeclass_search(
|
||||||
|
self.obj_child.__class__, include_parents=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(set(res1), {self.obj_parent, self.obj1, self.obj2, self.obj_child})
|
||||||
|
self.assertEqual(set(res2), {self.obj_parent, self.obj1, self.obj2, self.obj_child})
|
||||||
|
|
||||||
|
|
||||||
class TestTags(BaseEvenniaTest):
|
class TestTags(BaseEvenniaTest):
|
||||||
def test_has_tag_key_only(self):
|
def test_has_tag_key_only(self):
|
||||||
self.obj1.tags.add("tagC")
|
self.obj1.tags.add("tagC")
|
||||||
|
|
@ -238,6 +314,23 @@ class TestTags(BaseEvenniaTest):
|
||||||
self.obj1.tags.add("tagC", "categoryC")
|
self.obj1.tags.add("tagC", "categoryC")
|
||||||
self.assertFalse(self.obj1.tags.has(category="categoryD"))
|
self.assertFalse(self.obj1.tags.has(category="categoryD"))
|
||||||
|
|
||||||
|
def test_tag_add_no_category__issue_2688(self):
|
||||||
|
"""
|
||||||
|
Adding a tag without a category should create a new tag:None tag
|
||||||
|
rather than trying to update an existing tag:category tag.
|
||||||
|
"""
|
||||||
|
# adding tag+category, creates tag+category entry
|
||||||
|
self.obj1.tags.add("testing", category="testing_category")
|
||||||
|
self.assertEqual(
|
||||||
|
self.obj1.tags.all(return_key_and_category=True), [("testing", "testing_category")]
|
||||||
|
)
|
||||||
|
# adding a new tag with no category should create a tag+None entry
|
||||||
|
self.obj1.tags.add("testing")
|
||||||
|
self.assertEqual(
|
||||||
|
self.obj1.tags.all(return_key_and_category=True),
|
||||||
|
[("testing", "testing_category"), ("testing", None)],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestNickHandler(BaseEvenniaTest):
|
class TestNickHandler(BaseEvenniaTest):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,9 @@ Use as follows:
|
||||||
# create a new form from the template
|
# create a new form from the template
|
||||||
form = EvForm("path/to/testform.py")
|
form = EvForm("path/to/testform.py")
|
||||||
|
|
||||||
(EvForm can also take a dictionary holding
|
# EvForm can also take a dictionary instead of a filepath, as long
|
||||||
the required keys FORMCHAR, TABLECHAR and FORM)
|
# as the dict contains the keys FORMCHAR, TABLECHAR and FORM
|
||||||
|
# form = EvForm(form=form_dict)
|
||||||
|
|
||||||
# add data to each tagged form cell
|
# add data to each tagged form cell
|
||||||
form.map(cells={1: "Tom the Bouncer",
|
form.map(cells={1: "Tom the Bouncer",
|
||||||
|
|
|
||||||
|
|
@ -354,7 +354,6 @@ class FuncParser:
|
||||||
|
|
||||||
if curr_func:
|
if curr_func:
|
||||||
# we are starting a nested funcdef
|
# we are starting a nested funcdef
|
||||||
return_str = True
|
|
||||||
if len(callstack) > _MAX_NESTING:
|
if len(callstack) > _MAX_NESTING:
|
||||||
# stack full - ignore this function
|
# stack full - ignore this function
|
||||||
if raise_errors:
|
if raise_errors:
|
||||||
|
|
@ -799,7 +798,7 @@ def funcparser_callable_round(*args, **kwargs):
|
||||||
num, *significant = args
|
num, *significant = args
|
||||||
significant = significant[0] if significant else 0
|
significant = significant[0] if significant else 0
|
||||||
try:
|
try:
|
||||||
round(num, significant)
|
return round(num, significant)
|
||||||
except Exception:
|
except Exception:
|
||||||
if kwargs.get("raise_errors"):
|
if kwargs.get("raise_errors"):
|
||||||
raise
|
raise
|
||||||
|
|
@ -867,22 +866,33 @@ def funcparser_callable_choice(*args, **kwargs):
|
||||||
Args:
|
Args:
|
||||||
listing (list): A list of items to randomly choose between.
|
listing (list): A list of items to randomly choose between.
|
||||||
This will be converted from a string to a real list.
|
This will be converted from a string to a real list.
|
||||||
|
*args: If multiple args are given, will pick one randomly from them.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
any: The randomly chosen element.
|
any: The randomly chosen element.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
- `$choice([key, flower, house])`
|
- `$choice(key, flower, house)`
|
||||||
- `$choice([1, 2, 3, 4])`
|
- `$choice([1, 2, 3, 4])`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not args:
|
if not args:
|
||||||
return ""
|
return ""
|
||||||
args, _ = safe_convert_to_types(("py", {}), *args, **kwargs)
|
|
||||||
if not args[0]:
|
nargs = len(args)
|
||||||
|
if nargs == 1:
|
||||||
|
# this needs to be a list/tuple for this to make sense
|
||||||
|
args, _ = safe_convert_to_types(("py", {}), args[0], **kwargs)
|
||||||
|
args = make_iter(args[0]) if args else None
|
||||||
|
else:
|
||||||
|
# separate arg per entry
|
||||||
|
converters = ["py" for _ in range(nargs)]
|
||||||
|
args, _ = safe_convert_to_types((converters, {}), *args, **kwargs)
|
||||||
|
|
||||||
|
if not args:
|
||||||
return ""
|
return ""
|
||||||
try:
|
try:
|
||||||
return random.choice(args[0])
|
return random.choice(args)
|
||||||
except Exception:
|
except Exception:
|
||||||
if kwargs.get("raise_errors"):
|
if kwargs.get("raise_errors"):
|
||||||
raise
|
raise
|
||||||
|
|
@ -1153,7 +1163,7 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs):
|
||||||
if not targets:
|
if not targets:
|
||||||
if return_list:
|
if return_list:
|
||||||
return []
|
return []
|
||||||
raise ParsingError(f"$search: Query '{query}' gave no matches.")
|
raise ParsingError(f"$search: Query '{args[0]}' gave no matches.")
|
||||||
|
|
||||||
if len(targets) > 1 and not return_list:
|
if len(targets) > 1 and not return_list:
|
||||||
raise ParsingError(
|
raise ParsingError(
|
||||||
|
|
@ -1162,7 +1172,7 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs):
|
||||||
)
|
)
|
||||||
|
|
||||||
for target in targets:
|
for target in targets:
|
||||||
if not target.access(caller, target, access):
|
if not target.access(caller, access):
|
||||||
raise ParsingError("$search Cannot add found entity - access failure.")
|
raise ParsingError("$search Cannot add found entity - access failure.")
|
||||||
|
|
||||||
return list(targets) if return_list else targets[0]
|
return list(targets) if return_list else targets[0]
|
||||||
|
|
|
||||||
|
|
@ -384,7 +384,7 @@ class EvenniaLogFile(logfile.LogFile):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
_CHANNEL_LOG_NUM_TAIL_LINES = settings.CHANNEL_LOG_NUM_TAIL_LINES
|
_CHANNEL_LOG_NUM_TAIL_LINES = settings.CHANNEL_LOG_NUM_TAIL_LINES
|
||||||
num_lines_to_append = _CHANNEL_LOG_NUM_TAIL_LINES
|
num_lines_to_append = max(1, _CHANNEL_LOG_NUM_TAIL_LINES)
|
||||||
|
|
||||||
def rotate(self, num_lines_to_append=None):
|
def rotate(self, num_lines_to_append=None):
|
||||||
"""
|
"""
|
||||||
|
|
@ -463,7 +463,7 @@ def _open_log_file(filename):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
_LOGDIR = settings.LOG_DIR
|
_LOGDIR = settings.LOG_DIR
|
||||||
_LOG_ROTATE_SIZE = settings.CHANNEL_LOG_ROTATE_SIZE
|
_LOG_ROTATE_SIZE = max(1000, settings.CHANNEL_LOG_ROTATE_SIZE)
|
||||||
|
|
||||||
filename = os.path.join(_LOGDIR, filename)
|
filename = os.path.join(_LOGDIR, filename)
|
||||||
if filename in _LOG_FILE_HANDLES:
|
if filename in _LOG_FILE_HANDLES:
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ __all__ = (
|
||||||
"search_script_tag",
|
"search_script_tag",
|
||||||
"search_account_tag",
|
"search_account_tag",
|
||||||
"search_channel_tag",
|
"search_channel_tag",
|
||||||
|
"search_typeclass",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -362,3 +363,35 @@ def search_channel_tag(key=None, category=None, tagtype=None, **kwargs):
|
||||||
|
|
||||||
# search for tag objects (not the objects they are attached to
|
# search for tag objects (not the objects they are attached to
|
||||||
search_tag_object = ObjectDB.objects.get_tag
|
search_tag_object = ObjectDB.objects.get_tag
|
||||||
|
|
||||||
|
|
||||||
|
# Locate Objects by Typeclass
|
||||||
|
|
||||||
|
# search_objects_by_typeclass(typeclass="", include_children=False, include_parents=False) (also search_typeclass works)
|
||||||
|
# This returns the objects of the given typeclass
|
||||||
|
|
||||||
|
|
||||||
|
def search_objects_by_typeclass(typeclass, include_children=False, include_parents=False):
|
||||||
|
"""
|
||||||
|
Searches through all objects returning those of a certain typeclass.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
typeclass (str or class): A typeclass class or a python path to a typeclass.
|
||||||
|
include_children (bool, optional): Return objects with
|
||||||
|
given typeclass *and* all children inheriting from this
|
||||||
|
typeclass. Mutuall exclusive to `include_parents`.
|
||||||
|
include_parents (bool, optional): Return objects with
|
||||||
|
given typeclass *and* all parents to this typeclass.
|
||||||
|
Mutually exclusive to `include_children`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
objects (list): The objects found with the given typeclasses.
|
||||||
|
"""
|
||||||
|
return ObjectDB.objects.typeclass_search(
|
||||||
|
typeclass=typeclass,
|
||||||
|
include_children=include_children,
|
||||||
|
include_parents=include_parents,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
search_typeclass = search_objects_by_typeclass
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ Test the funcparser module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import unittest
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
|
@ -77,6 +78,10 @@ def _lsum_callable(*args, **kwargs):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def _raises_callable(*args, **kwargs):
|
||||||
|
raise RuntimeError("Test exception raised by test callable")
|
||||||
|
|
||||||
|
|
||||||
_test_callables = {
|
_test_callables = {
|
||||||
"foo": _test_callable,
|
"foo": _test_callable,
|
||||||
"bar": _test_callable,
|
"bar": _test_callable,
|
||||||
|
|
@ -89,6 +94,7 @@ _test_callables = {
|
||||||
"add": _add_callable,
|
"add": _add_callable,
|
||||||
"lit": _lit_callable,
|
"lit": _lit_callable,
|
||||||
"sum": _lsum_callable,
|
"sum": _lsum_callable,
|
||||||
|
"raise": _raises_callable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -102,6 +108,22 @@ class TestFuncParser(TestCase):
|
||||||
|
|
||||||
self.parser = funcparser.FuncParser(_test_callables)
|
self.parser = funcparser.FuncParser(_test_callables)
|
||||||
|
|
||||||
|
def test_constructor_wrong_args(self):
|
||||||
|
# Given list argument doesn't contain modules or paths.
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
parser = funcparser.FuncParser(["foo", _test_callable])
|
||||||
|
|
||||||
|
def test_constructor_ignore_non_callables(self):
|
||||||
|
# Ignores callables that aren't actual functions.
|
||||||
|
parser = funcparser.FuncParser({"foo": 1, "bar": "baz"})
|
||||||
|
|
||||||
|
@patch("evennia.utils.funcparser.variable_from_module")
|
||||||
|
def test_constructor_raises(self, patched_variable_from_module):
|
||||||
|
# Patched variable from module returns FUNCPARSER_CALLABLES that isn't dict.
|
||||||
|
patched_variable_from_module.return_value = ["foo"]
|
||||||
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
parser = funcparser.FuncParser("foo.module")
|
||||||
|
|
||||||
@parameterized.expand(
|
@parameterized.expand(
|
||||||
[
|
[
|
||||||
("Test normal string", "Test normal string"),
|
("Test normal string", "Test normal string"),
|
||||||
|
|
@ -216,13 +238,49 @@ class TestFuncParser(TestCase):
|
||||||
# print(f"time: {(t1-t0)*1000} ms")
|
# print(f"time: {(t1-t0)*1000} ms")
|
||||||
self.assertEqual(expected, ret)
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
def test_parse_raise(self):
|
@parameterized.expand(
|
||||||
|
(
|
||||||
|
"Test malformed This is $dummy(a, b) and $bar(",
|
||||||
|
"Test $funcNotFound()",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
def test_parse_raise_unparseable(self, unparseable):
|
||||||
"""
|
"""
|
||||||
Make sure error is raised if told to do so.
|
Make sure error is raised if told to do so.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
string = "Test malformed This is $dummy(a, b) and $bar("
|
|
||||||
with self.assertRaises(funcparser.ParsingError):
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
self.parser.parse(unparseable, raise_errors=True)
|
||||||
|
|
||||||
|
@patch("evennia.utils.funcparser._MAX_NESTING", 2)
|
||||||
|
def test_parse_max_nesting(self):
|
||||||
|
"""
|
||||||
|
Make sure it is an error if the max nesting value is reached.
|
||||||
|
|
||||||
|
TODO: Does this make sense? When it sees the first function, len(callstack)
|
||||||
|
is 0. It doesn't raise until the stack length is greater than the
|
||||||
|
_MAX_NESTING value, which means you can nest 4 values with a value of
|
||||||
|
2, as demonstrated by this test.
|
||||||
|
"""
|
||||||
|
string = "$add(1, $add(1, $add(1, $toint(42))))"
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
|
||||||
|
# TODO: Does this return value actually make sense?
|
||||||
|
# It removed the spaces from the calls.
|
||||||
|
self.assertEqual("$add(1,$add(1,$add(1,$toint(42))))", ret)
|
||||||
|
|
||||||
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
self.parser.parse(string, raise_errors=True)
|
||||||
|
|
||||||
|
def test_parse_underlying_exception(self):
|
||||||
|
string = "test $add(1, 1) $raise()"
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
|
||||||
|
# TODO: Does this return value actually make sense?
|
||||||
|
# It completed the first function call.
|
||||||
|
self.assertEqual("test 2 $raise()", ret)
|
||||||
|
|
||||||
|
with self.assertRaises(RuntimeError):
|
||||||
self.parser.parse(string, raise_errors=True)
|
self.parser.parse(string, raise_errors=True)
|
||||||
|
|
||||||
def test_parse_strip(self):
|
def test_parse_strip(self):
|
||||||
|
|
@ -234,6 +292,12 @@ class TestFuncParser(TestCase):
|
||||||
ret = self.parser.parse(string, strip=True)
|
ret = self.parser.parse(string, strip=True)
|
||||||
self.assertEqual("Test and things", ret)
|
self.assertEqual("Test and things", ret)
|
||||||
|
|
||||||
|
@unittest.skip("broken due to https://github.com/evennia/evennia/issues/2927")
|
||||||
|
def test_parse_whitespace_preserved(self):
|
||||||
|
string = "The answer is $add(1, x)"
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
self.assertEqual("The answer is $add(1, x)", ret)
|
||||||
|
|
||||||
def test_parse_escape(self):
|
def test_parse_escape(self):
|
||||||
"""
|
"""
|
||||||
Test the parser's escape functionality.
|
Test the parser's escape functionality.
|
||||||
|
|
@ -368,8 +432,7 @@ class TestDefaultCallables(TestCase):
|
||||||
)
|
)
|
||||||
def test_conjugate(self, string, expected_you, expected_them):
|
def test_conjugate(self, string, expected_you, expected_them):
|
||||||
"""
|
"""
|
||||||
Test callables with various input strings
|
Test the $conj(), $you() and $pron callables with various input strings.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mapping = {"char1": self.obj1, "char2": self.obj2}
|
mapping = {"char1": self.obj1, "char2": self.obj2}
|
||||||
ret = self.parser.parse(
|
ret = self.parser.parse(
|
||||||
|
|
@ -381,6 +444,46 @@ class TestDefaultCallables(TestCase):
|
||||||
)
|
)
|
||||||
self.assertEqual(expected_them, ret)
|
self.assertEqual(expected_them, ret)
|
||||||
|
|
||||||
|
def test_conjugate_missing_args(self):
|
||||||
|
string = "You $conj(smile)"
|
||||||
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
self.parser.parse(string, raise_errors=True)
|
||||||
|
|
||||||
|
@parameterized.expand(
|
||||||
|
[
|
||||||
|
("male", "Char1 smiles at himself"),
|
||||||
|
("female", "Char1 smiles at herself"),
|
||||||
|
("neutral", "Char1 smiles at itself"),
|
||||||
|
("plural", "Char1 smiles at itself"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_pronoun_gender(self, gender, expected):
|
||||||
|
string = "Char1 smiles at $pron(yourself)"
|
||||||
|
|
||||||
|
self.obj1.gender = gender
|
||||||
|
ret = self.parser.parse(string, caller=self.obj1, raise_errors=True)
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
self.obj1.gender = lambda: gender
|
||||||
|
ret = self.parser.parse(string, caller=self.obj1, raise_errors=True)
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_pronoun_viewpoint(self):
|
||||||
|
string = "Char1 smiles at $pron(I)"
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.obj1, viewpoint="op", raise_errors=True)
|
||||||
|
self.assertEqual("Char1 smiles at it", ret)
|
||||||
|
|
||||||
|
def test_pronoun_capitalize(self):
|
||||||
|
string = "Char1 smiles at $pron(I)"
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.obj1, capitalize=True, raise_errors=True)
|
||||||
|
self.assertEqual("Char1 smiles at It", ret)
|
||||||
|
|
||||||
|
string = "Char1 smiles at $Pron(I)"
|
||||||
|
ret = self.parser.parse(string, caller=self.obj1, capitalize=True, raise_errors=True)
|
||||||
|
self.assertEqual("Char1 smiles at It", ret)
|
||||||
|
|
||||||
@parameterized.expand(
|
@parameterized.expand(
|
||||||
[
|
[
|
||||||
("Test $pad(Hello, 20, c, -) there", "Test -------Hello-------- there"),
|
("Test $pad(Hello, 20, c, -) there", "Test -------Hello-------- there"),
|
||||||
|
|
@ -396,6 +499,7 @@ class TestDefaultCallables(TestCase):
|
||||||
("Some $mult(3, 2) things", "Some 6 things"),
|
("Some $mult(3, 2) things", "Some 6 things"),
|
||||||
("Some $div(6, 2) things", "Some 3.0 things"),
|
("Some $div(6, 2) things", "Some 3.0 things"),
|
||||||
("Some $toint(6) things", "Some 6 things"),
|
("Some $toint(6) things", "Some 6 things"),
|
||||||
|
("Some $toint(3 + 3) things", "Some 6 things"),
|
||||||
("Some $ljust(Hello, 30)", "Some Hello "),
|
("Some $ljust(Hello, 30)", "Some Hello "),
|
||||||
("Some $rjust(Hello, 30)", "Some Hello"),
|
("Some $rjust(Hello, 30)", "Some Hello"),
|
||||||
("Some $rjust(Hello, width=30)", "Some Hello"),
|
("Some $rjust(Hello, width=30)", "Some Hello"),
|
||||||
|
|
@ -415,6 +519,33 @@ class TestDefaultCallables(TestCase):
|
||||||
("There is $an(thing) here", "There is a thing here"),
|
("There is $an(thing) here", "There is a thing here"),
|
||||||
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
|
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
|
||||||
('$crop("spider\'s silk", 5)', "spide"),
|
('$crop("spider\'s silk", 5)', "spide"),
|
||||||
|
("$an(apple)", "an apple"),
|
||||||
|
# These two are broken because of https://github.com/evennia/evennia/issues/2912
|
||||||
|
# ("$round(2.9) apples", "3.0 apples"),
|
||||||
|
# ("$round(2.967, 1) apples", "3.0 apples"),
|
||||||
|
# Degenerate cases
|
||||||
|
("$int2str() apples", " apples"),
|
||||||
|
("$int2str(x) apples", "x apples"),
|
||||||
|
("$int2str(1 + 1) apples", "1 + 1 apples"),
|
||||||
|
("$int2str(13) apples", "13 apples"),
|
||||||
|
("$toint([1, 2, 3]) apples", "[1, 2, 3] apples"),
|
||||||
|
("$an() foo bar", " foo bar"),
|
||||||
|
("$add(1) apple", " apple"),
|
||||||
|
("$add(1, [1, 2]) apples", " apples"),
|
||||||
|
("$round() apples", " apples"),
|
||||||
|
("$choice() apple", " apple"),
|
||||||
|
("A $pad() apple", "A apple"),
|
||||||
|
("A $pad(tasty, 13, x, -) apple", "A ----tasty---- apple"),
|
||||||
|
("A $crop() apple", "A apple"),
|
||||||
|
("A $space() apple", "A apple"),
|
||||||
|
("A $justify() apple", "A apple"),
|
||||||
|
("A $clr() apple", "A apple"),
|
||||||
|
("A $clr(red) apple", "A red apple"),
|
||||||
|
("10 $pluralize()", "10 "),
|
||||||
|
("10 $pluralize(apple, 10)", "10 apples"),
|
||||||
|
("1 $pluralize(apple)", "1 apple"),
|
||||||
|
("You $conj()", "You "),
|
||||||
|
("$pron() smiles", " smiles"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_other_callables(self, string, expected):
|
def test_other_callables(self, string, expected):
|
||||||
|
|
@ -426,6 +557,9 @@ class TestDefaultCallables(TestCase):
|
||||||
self.assertEqual(expected, ret)
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
def test_random(self):
|
def test_random(self):
|
||||||
|
"""
|
||||||
|
Test random callable, with ranges of expected values.
|
||||||
|
"""
|
||||||
string = "$random(1,10)"
|
string = "$random(1,10)"
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||||
|
|
@ -436,12 +570,52 @@ class TestDefaultCallables(TestCase):
|
||||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||||
self.assertTrue(0 <= ret <= 1)
|
self.assertTrue(0 <= ret <= 1)
|
||||||
|
|
||||||
|
string = "$random(2)"
|
||||||
|
for i in range(100):
|
||||||
|
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||||
|
self.assertTrue(0 <= ret <= 2)
|
||||||
|
|
||||||
string = "$random(1.0, 3.0)"
|
string = "$random(1.0, 3.0)"
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||||
self.assertTrue(isinstance(ret, float))
|
self.assertTrue(isinstance(ret, float))
|
||||||
self.assertTrue(1.0 <= ret <= 3.0)
|
self.assertTrue(1.0 <= ret <= 3.0)
|
||||||
|
|
||||||
|
string = "$random([1,2]) apples"
|
||||||
|
ret = self.parser.parse_to_any(string)
|
||||||
|
self.assertEqual(" apples", ret)
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||||
|
|
||||||
|
# @unittest.skip("underlying function seems broken")
|
||||||
|
def test_choice(self):
|
||||||
|
"""
|
||||||
|
Test choice callable, where output could be either choice.
|
||||||
|
"""
|
||||||
|
string = "$choice(red, green) apple"
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
self.assertIn(ret, ("red apple", "green apple"))
|
||||||
|
|
||||||
|
string = "$choice([red, green]) apple"
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
self.assertIn(ret, ("red apple", "green apple"))
|
||||||
|
|
||||||
|
string = "$choice(['red', 'green']) apple"
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
self.assertIn(ret, ("red apple", "green apple"))
|
||||||
|
|
||||||
|
string = "$choice([1, 2])"
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
self.assertIn(ret, ("1", "2"))
|
||||||
|
ret = self.parser.parse_to_any(string)
|
||||||
|
self.assertIn(ret, (1, 2))
|
||||||
|
|
||||||
|
string = "$choice(1, 2)"
|
||||||
|
ret = self.parser.parse(string)
|
||||||
|
self.assertIn(ret, ("1", "2"))
|
||||||
|
ret = self.parser.parse_to_any(string)
|
||||||
|
self.assertIn(ret, (1, 2))
|
||||||
|
|
||||||
def test_randint(self):
|
def test_randint(self):
|
||||||
string = "$randint(1.0, 3.0)"
|
string = "$randint(1.0, 3.0)"
|
||||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||||
|
|
@ -528,6 +702,7 @@ class TestCallableSearch(test_resources.BaseEvenniaTest):
|
||||||
"""
|
"""
|
||||||
string = "$search(TestAccount, type=account)"
|
string = "$search(TestAccount, type=account)"
|
||||||
expected = self.account
|
expected = self.account
|
||||||
|
self.account.locks.add("control:id(%s)" % self.char1.dbref)
|
||||||
|
|
||||||
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
self.assertEqual(expected, ret)
|
self.assertEqual(expected, ret)
|
||||||
|
|
@ -539,6 +714,7 @@ class TestCallableSearch(test_resources.BaseEvenniaTest):
|
||||||
"""
|
"""
|
||||||
string = "$search(Script, type=script)"
|
string = "$search(Script, type=script)"
|
||||||
expected = self.script
|
expected = self.script
|
||||||
|
self.script.locks.add("control:id(%s)" % self.char1.dbref)
|
||||||
|
|
||||||
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
self.assertEqual(expected, ret)
|
self.assertEqual(expected, ret)
|
||||||
|
|
@ -553,3 +729,86 @@ class TestCallableSearch(test_resources.BaseEvenniaTest):
|
||||||
|
|
||||||
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
self.assertEqual(expected, ret)
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_search_tag(self):
|
||||||
|
"""
|
||||||
|
Test searching for a tag
|
||||||
|
"""
|
||||||
|
self.char1.tags.add("foo")
|
||||||
|
|
||||||
|
string = "This is $search(foo, type=tag)"
|
||||||
|
expected = "This is %s" % str(self.char1)
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
|
self.assertEqual(expected, ret)
|
||||||
|
|
||||||
|
def test_search_not_found(self):
|
||||||
|
string = "$search(foo)"
|
||||||
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=False)
|
||||||
|
self.assertEqual("$search(foo)", ret)
|
||||||
|
|
||||||
|
ret = self.parser.parse_to_any(
|
||||||
|
string, caller=self.char1, return_list=True, raise_errors=False
|
||||||
|
)
|
||||||
|
self.assertEqual([], ret)
|
||||||
|
|
||||||
|
def test_search_multiple_results_no_list(self):
|
||||||
|
"""
|
||||||
|
Test exception when search returns multiple results but list is not requested
|
||||||
|
"""
|
||||||
|
string = "$search(BaseObject)"
|
||||||
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
self.parser.parse(string, caller=self.char1, return_str=False, raise_errors=True)
|
||||||
|
|
||||||
|
def test_search_no_access(self):
|
||||||
|
string = "Go to $search(Room)"
|
||||||
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
self.parser.parse(string, caller=self.char2, return_list=True, raise_errors=True)
|
||||||
|
|
||||||
|
def test_search_no_caller(self):
|
||||||
|
string = "$search(Char)"
|
||||||
|
with self.assertRaises(funcparser.ParsingError):
|
||||||
|
self.parser.parse(string, caller=None, raise_errors=True)
|
||||||
|
|
||||||
|
def test_search_no_args(self):
|
||||||
|
string = "$search()"
|
||||||
|
ret = self.parser.parse(string, caller=self.char1, return_list=False, raise_errors=True)
|
||||||
|
self.assertEqual("None", ret)
|
||||||
|
|
||||||
|
ret = self.parser.parse(string, caller=self.char1, return_list=True, raise_errors=True)
|
||||||
|
self.assertEqual("[]", ret)
|
||||||
|
|
||||||
|
def test_search_nested__issue2902(self):
|
||||||
|
"""
|
||||||
|
Search for objects by-tag, check that the result is a valid object
|
||||||
|
|
||||||
|
"""
|
||||||
|
# we
|
||||||
|
parser = funcparser.FuncParser(
|
||||||
|
{**funcparser.SEARCHING_CALLABLES, **funcparser.FUNCPARSER_CALLABLES}
|
||||||
|
)
|
||||||
|
|
||||||
|
# set up search targets
|
||||||
|
self.obj1.tags.add("beach", category="zone")
|
||||||
|
self.obj2.tags.add("beach", category="zone")
|
||||||
|
|
||||||
|
# first a plain search
|
||||||
|
string = "$objlist(beach,category=zone,type=tag)"
|
||||||
|
ret = parser.parse_to_any(string, caller=self.char1, raise_errors=True)
|
||||||
|
|
||||||
|
self.assertEqual(ret, [self.obj1, self.obj2])
|
||||||
|
|
||||||
|
# get random result from the possible matches
|
||||||
|
string = "$choice($objlist(beach,category=zone,type=tag))"
|
||||||
|
ret = parser.parse_to_any(string, caller=self.char1, raise_errors=True)
|
||||||
|
|
||||||
|
self.assertIn(ret, [self.obj1, self.obj2])
|
||||||
|
|
||||||
|
# test wrapping in $obj(), should just pass object through
|
||||||
|
string = "$obj($choice($objlist(beach,category=zone,type=tag)))"
|
||||||
|
ret = parser.parse_to_any(string, caller=self.char1, raise_errors=True)
|
||||||
|
|
||||||
|
self.assertIn(ret, [self.obj1, self.obj2])
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
|
from evennia import DefaultObject, DefaultRoom
|
||||||
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.scripts.scripts import DefaultScript
|
from evennia.scripts.scripts import DefaultScript
|
||||||
|
from evennia.utils.search import (
|
||||||
|
search_script,
|
||||||
|
search_script_attribute,
|
||||||
|
search_script_tag,
|
||||||
|
search_typeclass,
|
||||||
|
)
|
||||||
from evennia.utils.test_resources import EvenniaTest
|
from evennia.utils.test_resources import EvenniaTest
|
||||||
from evennia.utils.search import search_script_attribute, search_script_tag, search_script
|
|
||||||
|
|
||||||
|
|
||||||
class TestSearch(EvenniaTest):
|
class TestSearch(EvenniaTest):
|
||||||
|
|
@ -61,3 +68,15 @@ class TestSearch(EvenniaTest):
|
||||||
script, errors = DefaultScript.create("a-script")
|
script, errors = DefaultScript.create("a-script")
|
||||||
found = search_script("wrong_key")
|
found = search_script("wrong_key")
|
||||||
self.assertEqual(len(found), 0, errors)
|
self.assertEqual(len(found), 0, errors)
|
||||||
|
|
||||||
|
def test_search_typeclass(self):
|
||||||
|
"""Check that an object can be found by typeclass"""
|
||||||
|
DefaultObject.create("test_obj")
|
||||||
|
found = search_typeclass("evennia.objects.objects.DefaultObject")
|
||||||
|
self.assertEqual(len(found), 1)
|
||||||
|
|
||||||
|
def test_search_wrong_typeclass(self):
|
||||||
|
"""Check that an object cannot be found by wrong typeclass"""
|
||||||
|
DefaultObject.create("test_obj_2")
|
||||||
|
with self.assertRaises(ImportError):
|
||||||
|
search_typeclass("not.a.typeclass")
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,12 @@ class TestListToString(TestCase):
|
||||||
[1,2,3] -> '1, 2, 3'
|
[1,2,3] -> '1, 2, 3'
|
||||||
with sep==';' and endsep==';':
|
with sep==';' and endsep==';':
|
||||||
[1,2,3] -> '1; 2; 3'
|
[1,2,3] -> '1; 2; 3'
|
||||||
|
with sep=='or':
|
||||||
|
[1,2,3] -> '1 or 2, and 3'
|
||||||
with endsep=='and':
|
with endsep=='and':
|
||||||
[1,2,3] -> '1, 2 and 3'
|
[1,2,3] -> '1, 2 and 3'
|
||||||
|
with endsep=='; and':
|
||||||
|
[1,2,3] -> '1, 2; and 3'
|
||||||
with endsep=='':
|
with endsep=='':
|
||||||
[1,2,3] -> '1, 2 3'
|
[1,2,3] -> '1, 2 3'
|
||||||
with addquote and endsep="and"
|
with addquote and endsep="and"
|
||||||
|
|
@ -80,6 +84,8 @@ class TestListToString(TestCase):
|
||||||
self.assertEqual("1, 2 and 3", utils.list_to_string([1, 2, 3], endsep="and"))
|
self.assertEqual("1, 2 and 3", utils.list_to_string([1, 2, 3], endsep="and"))
|
||||||
self.assertEqual("1, 2 3", utils.list_to_string([1, 2, 3], endsep=""))
|
self.assertEqual("1, 2 3", utils.list_to_string([1, 2, 3], endsep=""))
|
||||||
self.assertEqual("1; 2; 3", utils.list_to_string([1, 2, 3], sep=";", endsep=";"))
|
self.assertEqual("1; 2; 3", utils.list_to_string([1, 2, 3], sep=";", endsep=";"))
|
||||||
|
self.assertEqual("1 or 2, and 3", utils.list_to_string([1, 2, 3], sep="or"))
|
||||||
|
self.assertEqual("1, 2; and 3", utils.list_to_string([1, 2, 3], endsep="; and"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep=",", addquote=True)
|
'"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep=",", addquote=True)
|
||||||
)
|
)
|
||||||
|
|
@ -696,3 +702,36 @@ class TestDelay(BaseEvenniaTest):
|
||||||
timedelay
|
timedelay
|
||||||
) # Clock must advance to trigger, even if past timedelay
|
) # Clock must advance to trigger, even if past timedelay
|
||||||
self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran")
|
self.assertEqual(self.char1.ndb.dummy_var, "dummy_func ran")
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntConversions(TestCase):
|
||||||
|
def test_int2str(self):
|
||||||
|
self.assertEqual("three", utils.int2str(3))
|
||||||
|
# special adjective conversion
|
||||||
|
self.assertEqual("3rd", utils.int2str(3, adjective=True))
|
||||||
|
# generic adjective conversion
|
||||||
|
self.assertEqual("5th", utils.int2str(5, adjective=True))
|
||||||
|
# No mapping return int as str
|
||||||
|
self.assertEqual("15", utils.int2str(15))
|
||||||
|
|
||||||
|
def test_str2int(self):
|
||||||
|
# simple conversions
|
||||||
|
self.assertEqual(5, utils.str2int("5"))
|
||||||
|
|
||||||
|
# basic mapped numbers
|
||||||
|
self.assertEqual(3, utils.str2int("three"))
|
||||||
|
self.assertEqual(20, utils.str2int("twenty"))
|
||||||
|
|
||||||
|
# multi-place numbers
|
||||||
|
self.assertEqual(2345, utils.str2int("two thousand, three hundred and forty-five"))
|
||||||
|
|
||||||
|
# ordinal numbers
|
||||||
|
self.assertEqual(1, utils.str2int("1st"))
|
||||||
|
self.assertEqual(1, utils.str2int("first"))
|
||||||
|
self.assertEqual(4, utils.str2int("fourth"))
|
||||||
|
# ordinal sound-change conversions
|
||||||
|
self.assertEqual(5, utils.str2int("fifth"))
|
||||||
|
self.assertEqual(20, utils.str2int("twentieth"))
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
utils.str2int("not a number")
|
||||||
|
|
@ -24,6 +24,7 @@ from ast import literal_eval
|
||||||
from collections import OrderedDict, defaultdict
|
from collections import OrderedDict, defaultdict
|
||||||
from inspect import getmembers, getmodule, getmro, ismodule, trace
|
from inspect import getmembers, getmodule, getmro, ismodule, trace
|
||||||
from os.path import join as osjoin
|
from os.path import join as osjoin
|
||||||
|
from string import punctuation
|
||||||
from unicodedata import east_asian_width
|
from unicodedata import east_asian_width
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
|
|
@ -409,12 +410,17 @@ def iter_to_str(iterable, sep=",", endsep=", and", addquote=False):
|
||||||
else:
|
else:
|
||||||
iterable = tuple(str(val) for val in iterable)
|
iterable = tuple(str(val) for val in iterable)
|
||||||
|
|
||||||
if endsep.startswith(sep):
|
if endsep:
|
||||||
# oxford comma alternative
|
if endsep.startswith(sep):
|
||||||
endsep = endsep[1:] if len_iter < 3 else endsep
|
# oxford comma alternative
|
||||||
elif endsep:
|
endsep = endsep[1:] if len_iter < 3 else endsep
|
||||||
# normal space-separated end separator
|
elif endsep[0] not in punctuation:
|
||||||
endsep = " " + str(endsep).strip()
|
# add a leading space if endsep is a word
|
||||||
|
endsep = " " + str(endsep).strip()
|
||||||
|
|
||||||
|
# also add a leading space if separator is a word
|
||||||
|
if sep not in punctuation:
|
||||||
|
sep = " " + sep
|
||||||
|
|
||||||
if len_iter == 1:
|
if len_iter == 1:
|
||||||
return str(iterable[0])
|
return str(iterable[0])
|
||||||
|
|
@ -2281,14 +2287,17 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
||||||
)
|
)
|
||||||
|
|
||||||
for num, result in enumerate(matches):
|
for num, result in enumerate(matches):
|
||||||
# we need to consider Commands, where .aliases is a list
|
# we need to consider that result could be a Command, where .aliases
|
||||||
aliases = result.aliases.all() if hasattr(result.aliases, "all") else result.aliases
|
# is a list of strings
|
||||||
# remove any pluralization aliases
|
if hasattr(result.aliases, "all"):
|
||||||
aliases = [
|
# result is a typeclassed entity where `.aliases` is an AliasHandler.
|
||||||
alias
|
aliases = result.aliases.all(return_objs=True)
|
||||||
for alias in aliases
|
# remove pluralization aliases
|
||||||
if hasattr(alias, "category") and alias.category not in ("plural_key",)
|
aliases = [alias for alias in aliases if alias.category not in ("plural_key",)]
|
||||||
]
|
else:
|
||||||
|
# result is likely a Command, where `.aliases` is a list of strings.
|
||||||
|
aliases = result.aliases
|
||||||
|
|
||||||
error += _MULTIMATCH_TEMPLATE.format(
|
error += _MULTIMATCH_TEMPLATE.format(
|
||||||
number=num + 1,
|
number=num + 1,
|
||||||
name=result.get_display_name(caller)
|
name=result.get_display_name(caller)
|
||||||
|
|
@ -2563,6 +2572,14 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs):
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
container_end_char = {"(": ")", "[": "]", "{": "}"} # tuples, lists, sets
|
||||||
|
|
||||||
|
def _manual_parse_containers(inp):
|
||||||
|
startchar = inp[0]
|
||||||
|
endchar = inp[-1]
|
||||||
|
if endchar != container_end_char.get(startchar):
|
||||||
|
return
|
||||||
|
return [str(part).strip() for part in inp[1:-1].split(",")]
|
||||||
|
|
||||||
def _safe_eval(inp):
|
def _safe_eval(inp):
|
||||||
if not inp:
|
if not inp:
|
||||||
|
|
@ -2570,16 +2587,21 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs):
|
||||||
if not isinstance(inp, str):
|
if not isinstance(inp, str):
|
||||||
# already converted
|
# already converted
|
||||||
return inp
|
return inp
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return literal_eval(inp)
|
try:
|
||||||
|
return literal_eval(inp)
|
||||||
|
except ValueError:
|
||||||
|
parts = _manual_parse_containers(inp)
|
||||||
|
if not parts:
|
||||||
|
raise
|
||||||
|
return parts
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
literal_err = f"{err.__class__.__name__}: {err}"
|
literal_err = f"{err.__class__.__name__}: {err}"
|
||||||
try:
|
try:
|
||||||
return simple_eval(inp)
|
return simple_eval(inp)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
simple_err = f"{str(err.__class__.__name__)}: {err}"
|
simple_err = f"{str(err.__class__.__name__)}: {err}"
|
||||||
pass
|
|
||||||
|
|
||||||
if raise_errors:
|
if raise_errors:
|
||||||
from evennia.utils.funcparser import ParsingError
|
from evennia.utils.funcparser import ParsingError
|
||||||
|
|
@ -2590,6 +2612,9 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs):
|
||||||
f"simple_eval raised {simple_err}"
|
f"simple_eval raised {simple_err}"
|
||||||
)
|
)
|
||||||
raise ParsingError(err)
|
raise ParsingError(err)
|
||||||
|
else:
|
||||||
|
# fallback - convert to str
|
||||||
|
return str(inp)
|
||||||
|
|
||||||
# handle an incomplete/mixed set of input converters
|
# handle an incomplete/mixed set of input converters
|
||||||
if not converters:
|
if not converters:
|
||||||
|
|
@ -2755,3 +2780,110 @@ def int2str(number, adjective=False):
|
||||||
if adjective:
|
if adjective:
|
||||||
return _INT2STR_MAP_ADJ.get(number, f"{number}th")
|
return _INT2STR_MAP_ADJ.get(number, f"{number}th")
|
||||||
return _INT2STR_MAP_NOUN.get(number, str(number))
|
return _INT2STR_MAP_NOUN.get(number, str(number))
|
||||||
|
|
||||||
|
|
||||||
|
_STR2INT_MAP = {
|
||||||
|
"one": 1,
|
||||||
|
"two": 2,
|
||||||
|
"three": 3,
|
||||||
|
"four": 4,
|
||||||
|
"five": 5,
|
||||||
|
"six": 6,
|
||||||
|
"seven": 7,
|
||||||
|
"eight": 8,
|
||||||
|
"nine": 9,
|
||||||
|
"ten": 10,
|
||||||
|
"eleven": 11,
|
||||||
|
"twelve": 12,
|
||||||
|
"thirteen": 13,
|
||||||
|
"fourteen": 14,
|
||||||
|
"fifteen": 15,
|
||||||
|
"sixteen": 16,
|
||||||
|
"seventeen": 17,
|
||||||
|
"eighteen": 18,
|
||||||
|
"nineteen": 19,
|
||||||
|
"twenty": 20,
|
||||||
|
"thirty": 30,
|
||||||
|
"forty": 40,
|
||||||
|
"fifty": 50,
|
||||||
|
"sixty": 60,
|
||||||
|
"seventy": 70,
|
||||||
|
"eighty": 80,
|
||||||
|
"ninety": 90,
|
||||||
|
"hundred": 100,
|
||||||
|
"thousand": 1000,
|
||||||
|
}
|
||||||
|
_STR2INT_ADJS = {
|
||||||
|
"first": 1,
|
||||||
|
"second": 2,
|
||||||
|
"third": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def str2int(number):
|
||||||
|
"""
|
||||||
|
Converts a string to an integer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
number (str): The string to convert. It can be a digit such as "1", or a number word such as "one".
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: The string represented as an integer.
|
||||||
|
"""
|
||||||
|
number = str(number)
|
||||||
|
original_input = number
|
||||||
|
try:
|
||||||
|
# it's a digit already
|
||||||
|
return int(number)
|
||||||
|
except:
|
||||||
|
# if it's an ordinal number such as "1st", it'll convert to int with the last two characters chopped off
|
||||||
|
try:
|
||||||
|
return int(number[:-2])
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# convert sound changes for generic ordinal numbers
|
||||||
|
if number[-2:] == "th":
|
||||||
|
# remove "th"
|
||||||
|
number = number[:-2]
|
||||||
|
if number[-1] == "f":
|
||||||
|
# e.g. twelfth, fifth
|
||||||
|
number = number[:-1] + "ve"
|
||||||
|
elif number[-2:] == "ie":
|
||||||
|
# e.g. twentieth, fortieth
|
||||||
|
number = number[:-2] + "y"
|
||||||
|
# custom case for ninth
|
||||||
|
elif number[-3:] == "nin":
|
||||||
|
number += "e"
|
||||||
|
|
||||||
|
if i := _STR2INT_MAP.get(number):
|
||||||
|
# it's a single number, return it
|
||||||
|
return i
|
||||||
|
|
||||||
|
# remove optional "and"s
|
||||||
|
number = number.replace(" and ", " ")
|
||||||
|
|
||||||
|
# split number words by spaces, hyphens and commas, to accommodate multiple styles
|
||||||
|
numbers = [word.lower() for word in re.split(r"[-\s\,]", number) if word]
|
||||||
|
sums = []
|
||||||
|
for word in numbers:
|
||||||
|
# check if it's a known number-word
|
||||||
|
if i := _STR2INT_MAP.get(word):
|
||||||
|
if not len(sums):
|
||||||
|
# initialize the list with the current value
|
||||||
|
sums = [i]
|
||||||
|
else:
|
||||||
|
# if the previous number was smaller, it's a multiplier
|
||||||
|
# e.g. the "two" in "two hundred"
|
||||||
|
if sums[-1] < i:
|
||||||
|
sums[-1] = sums[-1] * i
|
||||||
|
# otherwise, it's added on, like the "five" in "twenty five"
|
||||||
|
else:
|
||||||
|
sums.append(i)
|
||||||
|
elif i := _STR2INT_ADJS.get(word):
|
||||||
|
# it's a special adj word; ordinal case will never be a multiplier
|
||||||
|
sums.append(i)
|
||||||
|
else:
|
||||||
|
# invalid number-word, raise ValueError
|
||||||
|
raise ValueError(f"String {original_input} cannot be converted to int.")
|
||||||
|
return sum(sums)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
English pronoun mapping between 1st/2nd person and 3rd person perspective (and vice-versa).
|
English pronoun mapping between 1st/2nd person and 3rd person perspective (and vice-versa).
|
||||||
|
|
||||||
This file is released under the Evennia regular BSD License.
|
This file is released under the Evennia regular BSD License.
|
||||||
(Griatch 2021)
|
(Griatch 2021) - revised by InspectorCaracal 2022
|
||||||
|
|
||||||
Pronouns are words you use instead of a proper name, such as 'him', 'herself', 'theirs' etc. These
|
Pronouns are words you use instead of a proper name, such as 'him', 'herself', 'theirs' etc. These
|
||||||
look different depending on who sees the outgoing string. This mapping maps between 1st/2nd case and
|
look different depending on who sees the outgoing string. This mapping maps between 1st/2nd case and
|
||||||
|
|
@ -21,227 +21,266 @@ viewpoint/pronouns Subject Object Possessive Possessive Reflexive
|
||||||
|
|
||||||
3rd person male he him his his himself
|
3rd person male he him his his himself
|
||||||
3rd person female she her her hers herself
|
3rd person female she her her hers herself
|
||||||
3rd person neutral it it its theirs* itself
|
3rd person neutral it it its its itself
|
||||||
3rd person plural they them their theirs themselves
|
3rd person plural they them their theirs themselves
|
||||||
==================== ======= ======== ========== ========== ===========
|
==================== ======= ======== ========== ========== ===========
|
||||||
|
|
||||||
> `*`) Not formally used, we use `theirs` here as a filler.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from evennia.utils.utils import copy_word_case
|
from evennia.utils.utils import copy_word_case, is_iter
|
||||||
|
|
||||||
DEFAULT_PRONOUN_TYPE = "object_pronoun"
|
DEFAULT_PRONOUN_TYPE = "subject pronoun"
|
||||||
DEFAULT_VIEWPOINT = "2nd person"
|
DEFAULT_VIEWPOINT = "2nd person"
|
||||||
DEFAULT_GENDER = "neutral"
|
DEFAULT_GENDER = "neutral"
|
||||||
|
|
||||||
|
PRONOUN_TYPES = [
|
||||||
|
"subject pronoun",
|
||||||
|
"object pronoun",
|
||||||
|
"possessive adjective",
|
||||||
|
"possessive pronoun",
|
||||||
|
"reflexive pronoun",
|
||||||
|
]
|
||||||
|
VIEWPOINTS = ["1st person", "2nd person", "3rd person"]
|
||||||
|
GENDERS = ["male", "female", "neutral", "plural"]
|
||||||
|
|
||||||
PRONOUN_MAPPING = {
|
PRONOUN_MAPPING = {
|
||||||
# 1st/2nd person -> 3rd person mappings
|
"1st person": {
|
||||||
"I": {"subject pronoun": {"3rd person": {"male": "he", "female": "she", "neutral": "it"}}},
|
|
||||||
"me": {"object pronoun": {"3rd person": {"male": "him", "female": "her", "neutral": "it"}}},
|
|
||||||
"my": {
|
|
||||||
"possessive adjective": {"3rd person": {"male": "his", "female": "her", "neutral": "its"}}
|
|
||||||
},
|
|
||||||
"mine": {
|
|
||||||
"possessive pronoun": {
|
|
||||||
"3rd person": {
|
|
||||||
"male": "his",
|
|
||||||
"female": "hers",
|
|
||||||
"neutral": "theirs", # colloqial,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"myself": {
|
|
||||||
"reflexive_pronoun": {
|
|
||||||
"3rd person": {
|
|
||||||
"male": "himself",
|
|
||||||
"female": "herself",
|
|
||||||
"neutral": "itself",
|
|
||||||
"plural": "themselves",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"you": {
|
|
||||||
"subject pronoun": {
|
"subject pronoun": {
|
||||||
"3rd person": {
|
"neutral": "I",
|
||||||
"male": "he",
|
"plural": "we",
|
||||||
"female": "she",
|
|
||||||
"neutral": "it",
|
|
||||||
"plural": "they",
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"object pronoun": {
|
"object pronoun": {
|
||||||
"3rd person": {
|
"neutral": "me",
|
||||||
"male": "him",
|
"plural": "us",
|
||||||
"female": "her",
|
|
||||||
"neutral": "it",
|
|
||||||
"plural": "them",
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
"your": {
|
|
||||||
"possessive adjective": {
|
"possessive adjective": {
|
||||||
"3rd person": {
|
"neutral": "my",
|
||||||
"male": "his",
|
"plural": "our",
|
||||||
"female": "her",
|
|
||||||
"neutral": "its",
|
|
||||||
"plural": "their",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"yours": {
|
|
||||||
"possessive pronoun": {
|
|
||||||
"3rd person": {
|
|
||||||
"male": "his",
|
|
||||||
"female": "hers",
|
|
||||||
"neutral": "theirs", # colloqial
|
|
||||||
"plural": "theirs",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"yourself": {
|
|
||||||
"reflexive_pronoun": {
|
|
||||||
"3rd person": {
|
|
||||||
"male": "himself",
|
|
||||||
"female": "herself",
|
|
||||||
"neutral": "itself",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"we": {"subject pronoun": {"3rd person": {"plural": "they"}}},
|
|
||||||
"us": {"object pronoun": {"3rd person": {"plural": "them"}}},
|
|
||||||
"our": {"possessive adjective": {"3rd person": {"plural": "their"}}},
|
|
||||||
"ours": {"possessive pronoun": {"3rd person": {"plural": "theirs"}}},
|
|
||||||
"ourselves": {"reflexive pronoun": {"3rd person": {"plural": "themselves"}}},
|
|
||||||
"ours": {"possessive pronoun": {"3rd person": {"plural": "theirs"}}},
|
|
||||||
"ourselves": {"reflexive pronoun": {"3rd person": {"plural": "themselves"}}},
|
|
||||||
"yourselves": {"reflexive_pronoun": {"3rd person": {"plural": "themselves"}}},
|
|
||||||
# 3rd person to 1st/second person mappings
|
|
||||||
"he": {
|
|
||||||
"subject pronoun": {
|
|
||||||
"1st person": {"neutral": "I", "plural": "we"}, # pluralis majestatis
|
|
||||||
"2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"him": {
|
|
||||||
"object pronoun": {
|
|
||||||
"1st person": {"neutral": "me", "plural": "us"}, # pluralis majestatis
|
|
||||||
"2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"his": {
|
|
||||||
"possessive adjective": {
|
|
||||||
"1st person": {"neutral": "my", "plural": "our"}, # pluralis majestatis
|
|
||||||
"2nd person": {"neutral": "your", "plural": "your"}, # pluralis majestatis
|
|
||||||
},
|
},
|
||||||
"possessive pronoun": {
|
"possessive pronoun": {
|
||||||
"1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis
|
"neutral": "mine",
|
||||||
"2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis
|
"plural": "ours",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
"himself": {
|
|
||||||
"reflexive pronoun": {
|
"reflexive pronoun": {
|
||||||
"1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis
|
"neutral": "myself",
|
||||||
"2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis
|
"plural": "ourselves"
|
||||||
},
|
|
||||||
},
|
|
||||||
"she": {
|
|
||||||
"subject pronoun": {
|
|
||||||
"1st person": {"neutral": "I", "plural": "you"}, # pluralis majestatis
|
|
||||||
"2nd person": {"neutral": "you", "plural": "we"}, # pluralis majestatis
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"her": {
|
"2nd person": {
|
||||||
|
"subject pronoun": {
|
||||||
|
"neutral": "you",
|
||||||
|
},
|
||||||
"object pronoun": {
|
"object pronoun": {
|
||||||
"1st person": {"neutral": "me", "plural": "us"}, # pluralis majestatis
|
"neutral": "you",
|
||||||
"2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis
|
|
||||||
},
|
},
|
||||||
"possessive adjective": {
|
"possessive adjective": {
|
||||||
"1st person": {"neutral": "my", "plural": "our"}, # pluralis majestatis
|
"neutral": "your",
|
||||||
"2nd person": {"neutral": "your", "plural": "your"}, # pluralis majestatis
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
"hers": {
|
|
||||||
"possessive pronoun": {
|
"possessive pronoun": {
|
||||||
"1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis
|
"neutral": "yours",
|
||||||
"2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis
|
},
|
||||||
|
"reflexive pronoun": {
|
||||||
|
"neutral": "yourself",
|
||||||
|
"plural": "yourselves",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"herself": {
|
"3rd person": {
|
||||||
"reflexive pronoun": {
|
|
||||||
"1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis
|
|
||||||
"2nd person": {"neutral": "yourself", "plural": "yourselves"}, # pluralis majestatis
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"it": {
|
|
||||||
"subject pronoun": {
|
"subject pronoun": {
|
||||||
"1st person": {"neutral": "I", "plural": "we"}, # pluralis majestatis
|
"male": "he",
|
||||||
"2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis
|
"female": "she",
|
||||||
|
"neutral": "it",
|
||||||
|
"plural": "they"
|
||||||
},
|
},
|
||||||
"object pronoun": {
|
"object pronoun": {
|
||||||
"1st person": {"neutral": "me", "plural": "us"}, # pluralis majestatis
|
"male": "him",
|
||||||
"2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis
|
"female": "her",
|
||||||
|
"neutral": "it",
|
||||||
|
"plural": "them"
|
||||||
},
|
},
|
||||||
},
|
|
||||||
"its": {
|
|
||||||
"possessive adjective": {
|
"possessive adjective": {
|
||||||
"1st person": {"neutral": "my", "plural": "our"}, # pluralis majestatis
|
"male": "his",
|
||||||
"2nd person": {"neutral": "your", "plural": "your"}, # pluralis majestatis
|
"female": "her",
|
||||||
}
|
"neutral": "its",
|
||||||
},
|
"plural": "their"
|
||||||
"theirs": {
|
},
|
||||||
"possessive pronoun": {
|
"possessive pronoun": {
|
||||||
"1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis
|
"male": "his",
|
||||||
"2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis
|
"female": "hers",
|
||||||
}
|
"neutral": "its",
|
||||||
},
|
"plural": "theirs",
|
||||||
"itself": {
|
|
||||||
"reflexive pronoun": {
|
|
||||||
"1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis
|
|
||||||
"2nd person": {"neutral": "yourself", "plural": "yourselves"}, # pluralis majestatis
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
"they": {
|
|
||||||
"subject pronoun": {
|
|
||||||
"1st person": {
|
|
||||||
"plural": "we",
|
|
||||||
},
|
|
||||||
"2nd person": {
|
|
||||||
"plural": "you",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"them": {
|
|
||||||
"object pronoun": {
|
|
||||||
"1st person": {
|
|
||||||
"plural": "us",
|
|
||||||
},
|
|
||||||
"2nd person": {
|
|
||||||
"plural": "you",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"their": {
|
|
||||||
"possessive adjective": {
|
|
||||||
"1st person": {
|
|
||||||
"plural": "our",
|
|
||||||
},
|
|
||||||
"2nd person": {
|
|
||||||
"plural": "your",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"themselves": {
|
|
||||||
"reflexive pronoun": {
|
"reflexive pronoun": {
|
||||||
"1st person": {
|
"male": "himself",
|
||||||
"plural": "ourselves",
|
"female": "herself",
|
||||||
},
|
"neutral": "itself",
|
||||||
"2nd person": {
|
"plural": "themselves",
|
||||||
"plural": "yourselves",
|
},
|
||||||
},
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PRONOUN_TABLE = {
|
||||||
|
"I": (
|
||||||
|
"1st person",
|
||||||
|
("neutral", "male", "female"),
|
||||||
|
"subject pronoun"
|
||||||
|
),
|
||||||
|
"me": (
|
||||||
|
"1st person",
|
||||||
|
("neutral", "male", "female"),
|
||||||
|
"object pronoun"
|
||||||
|
),
|
||||||
|
"my": (
|
||||||
|
"1st person",
|
||||||
|
("neutral", "male", "female"),
|
||||||
|
"possessive adjective"
|
||||||
|
),
|
||||||
|
"mine": (
|
||||||
|
"1st person",
|
||||||
|
("neutral", "male", "female"),
|
||||||
|
"possessive pronoun"
|
||||||
|
),
|
||||||
|
"myself": (
|
||||||
|
"1st person",
|
||||||
|
("neutral", "male", "female"),
|
||||||
|
"reflexive pronoun"
|
||||||
|
),
|
||||||
|
|
||||||
|
"we": (
|
||||||
|
"1st person",
|
||||||
|
"plural",
|
||||||
|
"subject pronoun"
|
||||||
|
),
|
||||||
|
"us": (
|
||||||
|
"1st person",
|
||||||
|
"plural",
|
||||||
|
"object pronoun"
|
||||||
|
),
|
||||||
|
"our": (
|
||||||
|
"1st person",
|
||||||
|
"plural",
|
||||||
|
"possessive adjective"
|
||||||
|
),
|
||||||
|
"ours": (
|
||||||
|
"1st person",
|
||||||
|
"plural",
|
||||||
|
"possessive pronoun"
|
||||||
|
),
|
||||||
|
"ourselves": (
|
||||||
|
"1st person",
|
||||||
|
"plural",
|
||||||
|
"reflexive pronoun"
|
||||||
|
),
|
||||||
|
"you": (
|
||||||
|
"2nd person",
|
||||||
|
("neutral", "male", "female", "plural"),
|
||||||
|
("subject pronoun", "object pronoun")
|
||||||
|
),
|
||||||
|
"your": (
|
||||||
|
"2nd person",
|
||||||
|
("neutral", "male", "female", "plural"),
|
||||||
|
"possessive adjective"
|
||||||
|
),
|
||||||
|
"yours": (
|
||||||
|
"2nd person",
|
||||||
|
("neutral", "male", "female", "plural"),
|
||||||
|
"possessive pronoun"
|
||||||
|
),
|
||||||
|
"yourself": (
|
||||||
|
"2nd person",
|
||||||
|
("neutral", "male", "female"),
|
||||||
|
"reflexive pronoun"
|
||||||
|
),
|
||||||
|
"yourselves": (
|
||||||
|
"2nd person",
|
||||||
|
"plural",
|
||||||
|
"reflexive pronoun"
|
||||||
|
),
|
||||||
|
"he": (
|
||||||
|
"3rd person",
|
||||||
|
"male",
|
||||||
|
"subject pronoun"
|
||||||
|
),
|
||||||
|
"him": (
|
||||||
|
"3rd person",
|
||||||
|
"male",
|
||||||
|
"object pronoun"
|
||||||
|
),
|
||||||
|
"his":(
|
||||||
|
"3rd person",
|
||||||
|
"male",
|
||||||
|
("possessive pronoun","possessive adjective"),
|
||||||
|
),
|
||||||
|
"himself": (
|
||||||
|
"3rd person",
|
||||||
|
"male",
|
||||||
|
"reflexive pronoun"
|
||||||
|
),
|
||||||
|
"she": (
|
||||||
|
"3rd person",
|
||||||
|
"female",
|
||||||
|
"subject pronoun"
|
||||||
|
),
|
||||||
|
"her": (
|
||||||
|
"3rd person",
|
||||||
|
"female",
|
||||||
|
("object pronoun", "possessive adjective"),
|
||||||
|
),
|
||||||
|
"hers": (
|
||||||
|
"3rd person",
|
||||||
|
"female",
|
||||||
|
"possessive pronoun"
|
||||||
|
),
|
||||||
|
"herself": (
|
||||||
|
"3rd person",
|
||||||
|
"female",
|
||||||
|
"reflexive pronoun"
|
||||||
|
),
|
||||||
|
"it": (
|
||||||
|
"3rd person",
|
||||||
|
"neutral",
|
||||||
|
("subject pronoun", "object pronoun"),
|
||||||
|
),
|
||||||
|
"its": (
|
||||||
|
"3rd person",
|
||||||
|
"neutral",
|
||||||
|
("possessive pronoun", "possessive adjective"),
|
||||||
|
),
|
||||||
|
"itself": (
|
||||||
|
"3rd person",
|
||||||
|
"neutral",
|
||||||
|
"reflexive pronoun"
|
||||||
|
),
|
||||||
|
"they": (
|
||||||
|
"3rd person",
|
||||||
|
"plural",
|
||||||
|
"subject pronoun"
|
||||||
|
),
|
||||||
|
"them": (
|
||||||
|
"3rd person",
|
||||||
|
"plural",
|
||||||
|
"object pronoun"
|
||||||
|
),
|
||||||
|
"their": (
|
||||||
|
"3rd person",
|
||||||
|
"plural",
|
||||||
|
"possessive adjective"
|
||||||
|
),
|
||||||
|
"theirs": (
|
||||||
|
"3rd person",
|
||||||
|
"plural",
|
||||||
|
"possessive pronoun"
|
||||||
|
),
|
||||||
|
"themselves": (
|
||||||
|
"3rd person",
|
||||||
|
"plural",
|
||||||
|
"reflexive pronoun"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# define the default viewpoint conversions
|
||||||
|
VIEWPOINT_CONVERSION = {
|
||||||
|
"1st person": "3rd person",
|
||||||
|
"2nd person": "3rd person",
|
||||||
|
"3rd person": ("1st person", "2nd person"),
|
||||||
|
}
|
||||||
|
|
||||||
ALIASES = {
|
ALIASES = {
|
||||||
"m": "male",
|
"m": "male",
|
||||||
|
|
@ -263,19 +302,9 @@ ALIASES = {
|
||||||
"pp": "possessive pronoun",
|
"pp": "possessive pronoun",
|
||||||
}
|
}
|
||||||
|
|
||||||
PRONOUN_TYPES = [
|
|
||||||
"subject pronoun",
|
|
||||||
"object pronoun",
|
|
||||||
"possessive adjective",
|
|
||||||
"possessive pronoun",
|
|
||||||
"reflexive pronoun",
|
|
||||||
]
|
|
||||||
VIEWPOINTS = ["1st person", "2nd person", "3rd person"]
|
|
||||||
GENDERS = ["male", "female", "neutral", "plural"] # including plural as a gender for simplicity
|
|
||||||
|
|
||||||
|
|
||||||
def pronoun_to_viewpoints(
|
def pronoun_to_viewpoints(
|
||||||
pronoun, options=None, pronoun_type="object_pronoun", gender="neutral", viewpoint="2nd person"
|
pronoun, options=None, pronoun_type=DEFAULT_PRONOUN_TYPE, gender=DEFAULT_GENDER, viewpoint=DEFAULT_VIEWPOINT
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Access function for determining the forms of a pronount from different viewpoints.
|
Access function for determining the forms of a pronount from different viewpoints.
|
||||||
|
|
@ -292,7 +321,7 @@ def pronoun_to_viewpoints(
|
||||||
- `subject pronoun`/`subject`/`sp` (I, you, he, they)
|
- `subject pronoun`/`subject`/`sp` (I, you, he, they)
|
||||||
- `object pronoun`/`object/`/`op` (me, you, him, them)
|
- `object pronoun`/`object/`/`op` (me, you, him, them)
|
||||||
- `possessive adjective`/`adjective`/`pa` (my, your, his, their)
|
- `possessive adjective`/`adjective`/`pa` (my, your, his, their)
|
||||||
- `possessive pronoun`/`pronoun`/`pp` (mine, yours, his, theirs)
|
- `possessive pronoun`/`pronoun`/`pp` (mine, yours, his, theirs)
|
||||||
|
|
||||||
gender (str, optional): Specific gender to use (plural counts a gender for this purpose).
|
gender (str, optional): Specific gender to use (plural counts a gender for this purpose).
|
||||||
A gender specified in `options` takes precedence. Values and aliases are:
|
A gender specified in `options` takes precedence. Values and aliases are:
|
||||||
|
|
@ -323,11 +352,13 @@ def pronoun_to_viewpoints(
|
||||||
|
|
||||||
pronoun_lower = "I" if pronoun == "I" else pronoun.lower()
|
pronoun_lower = "I" if pronoun == "I" else pronoun.lower()
|
||||||
|
|
||||||
if pronoun_lower not in PRONOUN_MAPPING:
|
if pronoun_lower not in PRONOUN_TABLE:
|
||||||
return pronoun
|
return pronoun
|
||||||
|
|
||||||
# differentiators
|
# get the default data for the input pronoun
|
||||||
|
source_viewpoint, source_gender, source_type = PRONOUN_TABLE[pronoun_lower]
|
||||||
|
|
||||||
|
# differentiators
|
||||||
if pronoun_type not in PRONOUN_TYPES:
|
if pronoun_type not in PRONOUN_TYPES:
|
||||||
pronoun_type = DEFAULT_PRONOUN_TYPE
|
pronoun_type = DEFAULT_PRONOUN_TYPE
|
||||||
if viewpoint not in VIEWPOINTS:
|
if viewpoint not in VIEWPOINTS:
|
||||||
|
|
@ -350,43 +381,34 @@ def pronoun_to_viewpoints(
|
||||||
elif opt in GENDERS:
|
elif opt in GENDERS:
|
||||||
gender = opt
|
gender = opt
|
||||||
|
|
||||||
# step down into the mapping, using differentiators as needed
|
# check if pronoun maps to multiple options and differentiate
|
||||||
pronoun_types = PRONOUN_MAPPING[pronoun_lower]
|
# but don't allow invalid differentiators
|
||||||
# this has one or more pronoun-types
|
if is_iter(source_type):
|
||||||
if len(pronoun_types) == 1:
|
pronoun_type = pronoun_type if pronoun_type in source_type else source_type[0]
|
||||||
pronoun_type, viewpoints = next(iter(pronoun_types.items()))
|
|
||||||
elif pronoun_type in pronoun_types:
|
|
||||||
viewpoints = pronoun_types[pronoun_type]
|
|
||||||
elif DEFAULT_PRONOUN_TYPE in pronoun_types:
|
|
||||||
pronoun_type = DEFAULT_PRONOUN_TYPE
|
|
||||||
viewpoints = pronoun_types[pronoun_type]
|
|
||||||
else:
|
else:
|
||||||
# not enough info - grab the first of the mappings
|
pronoun_type = source_type
|
||||||
pronoun_type, viewpoints = next(iter(pronoun_types.items()))
|
target_viewpoint = VIEWPOINT_CONVERSION[source_viewpoint]
|
||||||
|
if is_iter(target_viewpoint):
|
||||||
|
viewpoint = viewpoint if viewpoint in target_viewpoint else target_viewpoint[0]
|
||||||
|
else:
|
||||||
|
viewpoint = target_viewpoint
|
||||||
|
|
||||||
# we have one or more viewpoints at this point
|
# special handling for the royal "we"
|
||||||
if len(viewpoints) == 1:
|
if is_iter(source_gender):
|
||||||
viewpoint, genders = next(iter(viewpoints.items()))
|
gender_opts = list(source_gender)
|
||||||
elif viewpoint in viewpoints:
|
|
||||||
genders = viewpoints[viewpoint]
|
|
||||||
elif DEFAULT_VIEWPOINT in viewpoints:
|
|
||||||
viewpoint = DEFAULT_VIEWPOINT
|
|
||||||
genders = viewpoints[viewpoint]
|
|
||||||
else:
|
else:
|
||||||
# not enough info - grab first of mappings
|
gender_opts = [source_gender]
|
||||||
viewpoint, genders = next(iter(viewpoints.items()))
|
if viewpoint == "1st person":
|
||||||
|
# make sure plural is always an option when converting to 1st person
|
||||||
|
# it doesn't matter if it's in the list twice, so don't bother checking
|
||||||
|
gender_opts.append("plural")
|
||||||
|
# if the gender is still not in the extended options, fall back to source pronoun's default
|
||||||
|
gender = gender if gender in gender_opts else gender_opts[0]
|
||||||
|
|
||||||
# we have one or more possible genders (including plural forms)
|
# step down into the mapping
|
||||||
if len(genders) == 1:
|
viewpoint_map = PRONOUN_MAPPING[viewpoint]
|
||||||
gender, mapped_pronoun = next(iter(genders.items()))
|
pronouns = viewpoint_map.get(pronoun_type, viewpoint_map[DEFAULT_PRONOUN_TYPE])
|
||||||
elif gender in genders:
|
mapped_pronoun = pronouns.get(gender, pronouns[DEFAULT_GENDER])
|
||||||
mapped_pronoun = genders[gender]
|
|
||||||
elif DEFAULT_GENDER in genders:
|
|
||||||
gender = DEFAULT_GENDER
|
|
||||||
mapped_pronoun = genders[gender]
|
|
||||||
else:
|
|
||||||
# not enough info - grab first mapping
|
|
||||||
gender, mapped_pronoun = next(iter(genders.items()))
|
|
||||||
|
|
||||||
# keep the same capitalization as the original
|
# keep the same capitalization as the original
|
||||||
if pronoun != "I":
|
if pronoun != "I":
|
||||||
|
|
@ -396,10 +418,10 @@ def pronoun_to_viewpoints(
|
||||||
mapped_pronoun = mapped_pronoun.upper()
|
mapped_pronoun = mapped_pronoun.upper()
|
||||||
|
|
||||||
if viewpoint == "3rd person":
|
if viewpoint == "3rd person":
|
||||||
# the remapped viewpoing is in 3rd person, meaning the ingoing viewpoing
|
# the desired viewpoint is 3rd person, meaning the incoming viewpoint
|
||||||
# must have been 1st or 2nd person.
|
# must have been 1st or 2nd person.
|
||||||
return pronoun, mapped_pronoun
|
return pronoun, mapped_pronoun
|
||||||
else:
|
else:
|
||||||
# the remapped viewpoint is 1st or 2nd person, so ingoing must have been
|
# the desired viewpoint is 1st or 2nd person, so incoming must have been
|
||||||
# in 3rd person form.
|
# in 3rd person form.
|
||||||
return mapped_pronoun, pronoun
|
return mapped_pronoun, pronoun
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ class TestPronounMapping(TestCase):
|
||||||
("you", "m", "you", "he"),
|
("you", "m", "you", "he"),
|
||||||
("you", "f op", "you", "her"),
|
("you", "f op", "you", "her"),
|
||||||
("I", "", "I", "it"),
|
("I", "", "I", "it"),
|
||||||
("I", "p", "I", "it"), # plural is invalid
|
("I", "p", "I", "it"), # plural is invalid
|
||||||
("I", "m", "I", "he"),
|
("I", "m", "I", "he"),
|
||||||
("Me", "n", "Me", "It"),
|
("Me", "n", "Me", "It"),
|
||||||
("your", "p", "your", "their"),
|
("your", "p", "your", "their"),
|
||||||
|
|
@ -295,7 +295,6 @@ class TestPronounMapping(TestCase):
|
||||||
("her", "p", "you", "her"),
|
("her", "p", "you", "her"),
|
||||||
("her", "pa", "your", "her"),
|
("her", "pa", "your", "her"),
|
||||||
("their", "pa", "your", "their"),
|
("their", "pa", "your", "their"),
|
||||||
("their", "pa", "your", "their"),
|
|
||||||
("itself", "", "yourself", "itself"),
|
("itself", "", "yourself", "itself"),
|
||||||
("themselves", "", "yourselves", "themselves"),
|
("themselves", "", "yourselves", "themselves"),
|
||||||
("herself", "", "yourself", "herself"),
|
("herself", "", "yourself", "herself"),
|
||||||
|
|
@ -311,6 +310,5 @@ class TestPronounMapping(TestCase):
|
||||||
received_1st_or_2nd_person, received_3rd_person = pronouns.pronoun_to_viewpoints(
|
received_1st_or_2nd_person, received_3rd_person = pronouns.pronoun_to_viewpoints(
|
||||||
pronoun, options
|
pronoun, options
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(expected_1st_or_2nd_person, received_1st_or_2nd_person)
|
self.assertEqual(expected_1st_or_2nd_person, received_1st_or_2nd_person)
|
||||||
self.assertEqual(expected_3rd_person, received_3rd_person)
|
self.assertEqual(expected_3rd_person, received_3rd_person)
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,6 @@ django-extensions >= 3.1.0
|
||||||
|
|
||||||
# xyzroom contrib
|
# xyzroom contrib
|
||||||
scipy<1.9
|
scipy<1.9
|
||||||
|
|
||||||
|
# Git contrib
|
||||||
|
gitpython >= 3.1.27
|
||||||
Loading…
Add table
Add a link
Reference in a new issue