Merge branch 'develop' into global-scripts-raise
This commit is contained in:
commit
d1981bd15b
62 changed files with 1995 additions and 818 deletions
|
|
@ -61,8 +61,9 @@ Use as follows:
|
|||
# create a new form from the template
|
||||
form = EvForm("path/to/testform.py")
|
||||
|
||||
(EvForm can also take a dictionary holding
|
||||
the required keys FORMCHAR, TABLECHAR and FORM)
|
||||
# EvForm can also take a dictionary instead of a filepath, as long
|
||||
# as the dict contains the keys FORMCHAR, TABLECHAR and FORM
|
||||
# form = EvForm(form=form_dict)
|
||||
|
||||
# add data to each tagged form cell
|
||||
form.map(cells={1: "Tom the Bouncer",
|
||||
|
|
|
|||
|
|
@ -354,7 +354,6 @@ class FuncParser:
|
|||
|
||||
if curr_func:
|
||||
# we are starting a nested funcdef
|
||||
return_str = True
|
||||
if len(callstack) > _MAX_NESTING:
|
||||
# stack full - ignore this function
|
||||
if raise_errors:
|
||||
|
|
@ -799,7 +798,7 @@ def funcparser_callable_round(*args, **kwargs):
|
|||
num, *significant = args
|
||||
significant = significant[0] if significant else 0
|
||||
try:
|
||||
round(num, significant)
|
||||
return round(num, significant)
|
||||
except Exception:
|
||||
if kwargs.get("raise_errors"):
|
||||
raise
|
||||
|
|
@ -867,22 +866,33 @@ def funcparser_callable_choice(*args, **kwargs):
|
|||
Args:
|
||||
listing (list): A list of items to randomly choose between.
|
||||
This will be converted from a string to a real list.
|
||||
*args: If multiple args are given, will pick one randomly from them.
|
||||
|
||||
Returns:
|
||||
any: The randomly chosen element.
|
||||
|
||||
Example:
|
||||
- `$choice([key, flower, house])`
|
||||
- `$choice(key, flower, house)`
|
||||
- `$choice([1, 2, 3, 4])`
|
||||
|
||||
"""
|
||||
if not args:
|
||||
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 ""
|
||||
try:
|
||||
return random.choice(args[0])
|
||||
return random.choice(args)
|
||||
except Exception:
|
||||
if kwargs.get("raise_errors"):
|
||||
raise
|
||||
|
|
@ -1153,7 +1163,7 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs):
|
|||
if not targets:
|
||||
if return_list:
|
||||
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:
|
||||
raise ParsingError(
|
||||
|
|
@ -1162,7 +1172,7 @@ def funcparser_callable_search(*args, caller=None, access="control", **kwargs):
|
|||
)
|
||||
|
||||
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.")
|
||||
|
||||
return list(targets) if return_list else targets[0]
|
||||
|
|
|
|||
|
|
@ -384,7 +384,7 @@ class EvenniaLogFile(logfile.LogFile):
|
|||
from django.conf import settings
|
||||
|
||||
_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):
|
||||
"""
|
||||
|
|
@ -463,7 +463,7 @@ def _open_log_file(filename):
|
|||
from django.conf import settings
|
||||
|
||||
_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)
|
||||
if filename in _LOG_FILE_HANDLES:
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ __all__ = (
|
|||
"search_script_tag",
|
||||
"search_account_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_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 unittest
|
||||
from ast import literal_eval
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
|
@ -77,6 +78,10 @@ def _lsum_callable(*args, **kwargs):
|
|||
return ""
|
||||
|
||||
|
||||
def _raises_callable(*args, **kwargs):
|
||||
raise RuntimeError("Test exception raised by test callable")
|
||||
|
||||
|
||||
_test_callables = {
|
||||
"foo": _test_callable,
|
||||
"bar": _test_callable,
|
||||
|
|
@ -89,6 +94,7 @@ _test_callables = {
|
|||
"add": _add_callable,
|
||||
"lit": _lit_callable,
|
||||
"sum": _lsum_callable,
|
||||
"raise": _raises_callable,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -102,6 +108,22 @@ class TestFuncParser(TestCase):
|
|||
|
||||
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(
|
||||
[
|
||||
("Test normal string", "Test normal string"),
|
||||
|
|
@ -216,13 +238,49 @@ class TestFuncParser(TestCase):
|
|||
# print(f"time: {(t1-t0)*1000} ms")
|
||||
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.
|
||||
|
||||
"""
|
||||
string = "Test malformed This is $dummy(a, b) and $bar("
|
||||
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)
|
||||
|
||||
def test_parse_strip(self):
|
||||
|
|
@ -234,6 +292,12 @@ class TestFuncParser(TestCase):
|
|||
ret = self.parser.parse(string, strip=True)
|
||||
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):
|
||||
"""
|
||||
Test the parser's escape functionality.
|
||||
|
|
@ -368,8 +432,7 @@ class TestDefaultCallables(TestCase):
|
|||
)
|
||||
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}
|
||||
ret = self.parser.parse(
|
||||
|
|
@ -381,6 +444,46 @@ class TestDefaultCallables(TestCase):
|
|||
)
|
||||
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(
|
||||
[
|
||||
("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 $div(6, 2) things", "Some 3.0 things"),
|
||||
("Some $toint(6) things", "Some 6 things"),
|
||||
("Some $toint(3 + 3) things", "Some 6 things"),
|
||||
("Some $ljust(Hello, 30)", "Some Hello "),
|
||||
("Some $rjust(Hello, 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"),
|
||||
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
|
||||
('$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):
|
||||
|
|
@ -426,6 +557,9 @@ class TestDefaultCallables(TestCase):
|
|||
self.assertEqual(expected, ret)
|
||||
|
||||
def test_random(self):
|
||||
"""
|
||||
Test random callable, with ranges of expected values.
|
||||
"""
|
||||
string = "$random(1,10)"
|
||||
for i in range(100):
|
||||
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)
|
||||
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)"
|
||||
for i in range(100):
|
||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||
self.assertTrue(isinstance(ret, float))
|
||||
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):
|
||||
string = "$randint(1.0, 3.0)"
|
||||
ret = self.parser.parse_to_any(string, raise_errors=True)
|
||||
|
|
@ -528,6 +702,7 @@ class TestCallableSearch(test_resources.BaseEvenniaTest):
|
|||
"""
|
||||
string = "$search(TestAccount, type=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)
|
||||
self.assertEqual(expected, ret)
|
||||
|
|
@ -539,6 +714,7 @@ class TestCallableSearch(test_resources.BaseEvenniaTest):
|
|||
"""
|
||||
string = "$search(Script, type=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)
|
||||
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)
|
||||
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.utils.search import (
|
||||
search_script,
|
||||
search_script_attribute,
|
||||
search_script_tag,
|
||||
search_typeclass,
|
||||
)
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
from evennia.utils.search import search_script_attribute, search_script_tag, search_script
|
||||
|
||||
|
||||
class TestSearch(EvenniaTest):
|
||||
|
|
@ -61,3 +68,15 @@ class TestSearch(EvenniaTest):
|
|||
script, errors = DefaultScript.create("a-script")
|
||||
found = search_script("wrong_key")
|
||||
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'
|
||||
with sep==';' and endsep==';':
|
||||
[1,2,3] -> '1; 2; 3'
|
||||
with sep=='or':
|
||||
[1,2,3] -> '1 or 2, and 3'
|
||||
with endsep=='and':
|
||||
[1,2,3] -> '1, 2 and 3'
|
||||
with endsep=='; and':
|
||||
[1,2,3] -> '1, 2; and 3'
|
||||
with endsep=='':
|
||||
[1,2,3] -> '1, 2 3'
|
||||
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 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 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(
|
||||
'"1", "2", "3"', utils.list_to_string([1, 2, 3], endsep=",", addquote=True)
|
||||
)
|
||||
|
|
@ -696,3 +702,36 @@ class TestDelay(BaseEvenniaTest):
|
|||
timedelay
|
||||
) # Clock must advance to trigger, even if past timedelay
|
||||
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 inspect import getmembers, getmodule, getmro, ismodule, trace
|
||||
from os.path import join as osjoin
|
||||
from string import punctuation
|
||||
from unicodedata import east_asian_width
|
||||
|
||||
from django.apps import apps
|
||||
|
|
@ -409,12 +410,17 @@ def iter_to_str(iterable, sep=",", endsep=", and", addquote=False):
|
|||
else:
|
||||
iterable = tuple(str(val) for val in iterable)
|
||||
|
||||
if endsep.startswith(sep):
|
||||
# oxford comma alternative
|
||||
endsep = endsep[1:] if len_iter < 3 else endsep
|
||||
elif endsep:
|
||||
# normal space-separated end separator
|
||||
endsep = " " + str(endsep).strip()
|
||||
if endsep:
|
||||
if endsep.startswith(sep):
|
||||
# oxford comma alternative
|
||||
endsep = endsep[1:] if len_iter < 3 else endsep
|
||||
elif endsep[0] not in punctuation:
|
||||
# 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:
|
||||
return str(iterable[0])
|
||||
|
|
@ -2281,14 +2287,17 @@ def at_search_result(matches, caller, query="", quiet=False, **kwargs):
|
|||
)
|
||||
|
||||
for num, result in enumerate(matches):
|
||||
# we need to consider Commands, where .aliases is a list
|
||||
aliases = result.aliases.all() if hasattr(result.aliases, "all") else result.aliases
|
||||
# remove any pluralization aliases
|
||||
aliases = [
|
||||
alias
|
||||
for alias in aliases
|
||||
if hasattr(alias, "category") and alias.category not in ("plural_key",)
|
||||
]
|
||||
# we need to consider that result could be a Command, where .aliases
|
||||
# is a list of strings
|
||||
if hasattr(result.aliases, "all"):
|
||||
# result is a typeclassed entity where `.aliases` is an AliasHandler.
|
||||
aliases = result.aliases.all(return_objs=True)
|
||||
# remove pluralization aliases
|
||||
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(
|
||||
number=num + 1,
|
||||
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):
|
||||
if not inp:
|
||||
|
|
@ -2570,16 +2587,21 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs):
|
|||
if not isinstance(inp, str):
|
||||
# already converted
|
||||
return inp
|
||||
|
||||
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:
|
||||
literal_err = f"{err.__class__.__name__}: {err}"
|
||||
try:
|
||||
return simple_eval(inp)
|
||||
except Exception as err:
|
||||
simple_err = f"{str(err.__class__.__name__)}: {err}"
|
||||
pass
|
||||
|
||||
if raise_errors:
|
||||
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}"
|
||||
)
|
||||
raise ParsingError(err)
|
||||
else:
|
||||
# fallback - convert to str
|
||||
return str(inp)
|
||||
|
||||
# handle an incomplete/mixed set of input converters
|
||||
if not converters:
|
||||
|
|
@ -2755,3 +2780,110 @@ def int2str(number, adjective=False):
|
|||
if adjective:
|
||||
return _INT2STR_MAP_ADJ.get(number, f"{number}th")
|
||||
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).
|
||||
|
||||
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
|
||||
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 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
|
||||
==================== ======= ======== ========== ========== ===========
|
||||
|
||||
> `*`) 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_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 = {
|
||||
# 1st/2nd person -> 3rd person mappings
|
||||
"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": {
|
||||
"1st person": {
|
||||
"subject pronoun": {
|
||||
"3rd person": {
|
||||
"male": "he",
|
||||
"female": "she",
|
||||
"neutral": "it",
|
||||
"plural": "they",
|
||||
}
|
||||
"neutral": "I",
|
||||
"plural": "we",
|
||||
},
|
||||
"object pronoun": {
|
||||
"3rd person": {
|
||||
"male": "him",
|
||||
"female": "her",
|
||||
"neutral": "it",
|
||||
"plural": "them",
|
||||
}
|
||||
"neutral": "me",
|
||||
"plural": "us",
|
||||
},
|
||||
},
|
||||
"your": {
|
||||
"possessive adjective": {
|
||||
"3rd person": {
|
||||
"male": "his",
|
||||
"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
|
||||
"neutral": "my",
|
||||
"plural": "our",
|
||||
},
|
||||
"possessive pronoun": {
|
||||
"1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis
|
||||
"neutral": "mine",
|
||||
"plural": "ours",
|
||||
},
|
||||
},
|
||||
"himself": {
|
||||
"reflexive pronoun": {
|
||||
"1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis
|
||||
},
|
||||
},
|
||||
"she": {
|
||||
"subject pronoun": {
|
||||
"1st person": {"neutral": "I", "plural": "you"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "you", "plural": "we"}, # pluralis majestatis
|
||||
"neutral": "myself",
|
||||
"plural": "ourselves"
|
||||
}
|
||||
},
|
||||
"her": {
|
||||
"2nd person": {
|
||||
"subject pronoun": {
|
||||
"neutral": "you",
|
||||
},
|
||||
"object pronoun": {
|
||||
"1st person": {"neutral": "me", "plural": "us"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis
|
||||
"neutral": "you",
|
||||
},
|
||||
"possessive adjective": {
|
||||
"1st person": {"neutral": "my", "plural": "our"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "your", "plural": "your"}, # pluralis majestatis
|
||||
"neutral": "your",
|
||||
},
|
||||
},
|
||||
"hers": {
|
||||
"possessive pronoun": {
|
||||
"1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis
|
||||
"neutral": "yours",
|
||||
},
|
||||
"reflexive pronoun": {
|
||||
"neutral": "yourself",
|
||||
"plural": "yourselves",
|
||||
}
|
||||
},
|
||||
"herself": {
|
||||
"reflexive pronoun": {
|
||||
"1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "yourself", "plural": "yourselves"}, # pluralis majestatis
|
||||
},
|
||||
},
|
||||
"it": {
|
||||
"3rd person": {
|
||||
"subject pronoun": {
|
||||
"1st person": {"neutral": "I", "plural": "we"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis
|
||||
"male": "he",
|
||||
"female": "she",
|
||||
"neutral": "it",
|
||||
"plural": "they"
|
||||
},
|
||||
"object pronoun": {
|
||||
"1st person": {"neutral": "me", "plural": "us"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "you", "plural": "you"}, # pluralis majestatis
|
||||
"male": "him",
|
||||
"female": "her",
|
||||
"neutral": "it",
|
||||
"plural": "them"
|
||||
},
|
||||
},
|
||||
"its": {
|
||||
"possessive adjective": {
|
||||
"1st person": {"neutral": "my", "plural": "our"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "your", "plural": "your"}, # pluralis majestatis
|
||||
}
|
||||
},
|
||||
"theirs": {
|
||||
"male": "his",
|
||||
"female": "her",
|
||||
"neutral": "its",
|
||||
"plural": "their"
|
||||
},
|
||||
"possessive pronoun": {
|
||||
"1st person": {"neutral": "mine", "plural": "ours"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "yours", "plural": "yours"}, # pluralis majestatis
|
||||
}
|
||||
},
|
||||
"itself": {
|
||||
"reflexive pronoun": {
|
||||
"1st person": {"neutral": "myself", "plural": "ourselves"}, # pluralis majestatis
|
||||
"2nd person": {"neutral": "yourself", "plural": "yourselves"}, # pluralis majestatis
|
||||
"male": "his",
|
||||
"female": "hers",
|
||||
"neutral": "its",
|
||||
"plural": "theirs",
|
||||
},
|
||||
},
|
||||
"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": {
|
||||
"1st person": {
|
||||
"plural": "ourselves",
|
||||
},
|
||||
"2nd person": {
|
||||
"plural": "yourselves",
|
||||
},
|
||||
}
|
||||
},
|
||||
"male": "himself",
|
||||
"female": "herself",
|
||||
"neutral": "itself",
|
||||
"plural": "themselves",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 = {
|
||||
"m": "male",
|
||||
|
|
@ -263,19 +302,9 @@ ALIASES = {
|
|||
"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(
|
||||
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.
|
||||
|
|
@ -292,7 +321,7 @@ def pronoun_to_viewpoints(
|
|||
- `subject pronoun`/`subject`/`sp` (I, you, he, they)
|
||||
- `object pronoun`/`object/`/`op` (me, you, him, them)
|
||||
- `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).
|
||||
A gender specified in `options` takes precedence. Values and aliases are:
|
||||
|
|
@ -323,18 +352,20 @@ def pronoun_to_viewpoints(
|
|||
|
||||
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
|
||||
|
||||
# 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:
|
||||
pronoun_type = DEFAULT_PRONOUN_TYPE
|
||||
if viewpoint not in VIEWPOINTS:
|
||||
viewpoint = DEFAULT_VIEWPOINT
|
||||
if gender not in GENDERS:
|
||||
gender = DEFAULT_GENDER
|
||||
|
||||
|
||||
if options:
|
||||
# option string/list will override the kwargs differentiators given
|
||||
if isinstance(options, str):
|
||||
|
|
@ -350,44 +381,35 @@ def pronoun_to_viewpoints(
|
|||
elif opt in GENDERS:
|
||||
gender = opt
|
||||
|
||||
# step down into the mapping, using differentiators as needed
|
||||
pronoun_types = PRONOUN_MAPPING[pronoun_lower]
|
||||
# this has one or more pronoun-types
|
||||
if len(pronoun_types) == 1:
|
||||
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]
|
||||
# check if pronoun maps to multiple options and differentiate
|
||||
# but don't allow invalid differentiators
|
||||
if is_iter(source_type):
|
||||
pronoun_type = pronoun_type if pronoun_type in source_type else source_type[0]
|
||||
else:
|
||||
# not enough info - grab the first of the mappings
|
||||
pronoun_type, viewpoints = next(iter(pronoun_types.items()))
|
||||
|
||||
# we have one or more viewpoints at this point
|
||||
if len(viewpoints) == 1:
|
||||
viewpoint, genders = next(iter(viewpoints.items()))
|
||||
elif viewpoint in viewpoints:
|
||||
genders = viewpoints[viewpoint]
|
||||
elif DEFAULT_VIEWPOINT in viewpoints:
|
||||
viewpoint = DEFAULT_VIEWPOINT
|
||||
genders = viewpoints[viewpoint]
|
||||
pronoun_type = source_type
|
||||
target_viewpoint = VIEWPOINT_CONVERSION[source_viewpoint]
|
||||
if is_iter(target_viewpoint):
|
||||
viewpoint = viewpoint if viewpoint in target_viewpoint else target_viewpoint[0]
|
||||
else:
|
||||
# not enough info - grab first of mappings
|
||||
viewpoint, genders = next(iter(viewpoints.items()))
|
||||
viewpoint = target_viewpoint
|
||||
|
||||
# we have one or more possible genders (including plural forms)
|
||||
if len(genders) == 1:
|
||||
gender, mapped_pronoun = next(iter(genders.items()))
|
||||
elif gender in genders:
|
||||
mapped_pronoun = genders[gender]
|
||||
elif DEFAULT_GENDER in genders:
|
||||
gender = DEFAULT_GENDER
|
||||
mapped_pronoun = genders[gender]
|
||||
# special handling for the royal "we"
|
||||
if is_iter(source_gender):
|
||||
gender_opts = list(source_gender)
|
||||
else:
|
||||
# not enough info - grab first mapping
|
||||
gender, mapped_pronoun = next(iter(genders.items()))
|
||||
gender_opts = [source_gender]
|
||||
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]
|
||||
|
||||
# step down into the mapping
|
||||
viewpoint_map = PRONOUN_MAPPING[viewpoint]
|
||||
pronouns = viewpoint_map.get(pronoun_type, viewpoint_map[DEFAULT_PRONOUN_TYPE])
|
||||
mapped_pronoun = pronouns.get(gender, pronouns[DEFAULT_GENDER])
|
||||
|
||||
# keep the same capitalization as the original
|
||||
if pronoun != "I":
|
||||
# don't remap I, since this is always capitalized.
|
||||
|
|
@ -396,10 +418,10 @@ def pronoun_to_viewpoints(
|
|||
mapped_pronoun = mapped_pronoun.upper()
|
||||
|
||||
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.
|
||||
return pronoun, mapped_pronoun
|
||||
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.
|
||||
return mapped_pronoun, pronoun
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ class TestPronounMapping(TestCase):
|
|||
("you", "m", "you", "he"),
|
||||
("you", "f op", "you", "her"),
|
||||
("I", "", "I", "it"),
|
||||
("I", "p", "I", "it"), # plural is invalid
|
||||
("I", "p", "I", "it"), # plural is invalid
|
||||
("I", "m", "I", "he"),
|
||||
("Me", "n", "Me", "It"),
|
||||
("your", "p", "your", "their"),
|
||||
|
|
@ -295,7 +295,6 @@ class TestPronounMapping(TestCase):
|
|||
("her", "p", "you", "her"),
|
||||
("her", "pa", "your", "her"),
|
||||
("their", "pa", "your", "their"),
|
||||
("their", "pa", "your", "their"),
|
||||
("itself", "", "yourself", "itself"),
|
||||
("themselves", "", "yourselves", "themselves"),
|
||||
("herself", "", "yourself", "herself"),
|
||||
|
|
@ -311,6 +310,5 @@ class TestPronounMapping(TestCase):
|
|||
received_1st_or_2nd_person, received_3rd_person = pronouns.pronoun_to_viewpoints(
|
||||
pronoun, options
|
||||
)
|
||||
|
||||
self.assertEqual(expected_1st_or_2nd_person, received_1st_or_2nd_person)
|
||||
self.assertEqual(expected_3rd_person, received_3rd_person)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue