Resolve merge conflict
This commit is contained in:
commit
1153433a2c
55 changed files with 590 additions and 348 deletions
|
|
@ -674,6 +674,13 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
|
||||
"""
|
||||
|
||||
# A compiled Regex for the format mini-language: https://docs.python.org/3/library/string.html#formatspec
|
||||
re_format = re.compile(
|
||||
r"(?i)(?P<just>(?P<fill>.)?(?P<align>\<|\>|\=|\^))?(?P<sign>\+|\-| )?(?P<alt>\#)?"
|
||||
r"(?P<zero>0)?(?P<width>\d+)?(?P<grouping>\_|\,)?(?:\.(?P<precision>\d+))?"
|
||||
r"(?P<type>b|c|d|e|E|f|F|g|G|n|o|s|x|X|%)?"
|
||||
)
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
"""
|
||||
When creating a new ANSIString, you may use a custom parser that has
|
||||
|
|
@ -733,6 +740,47 @@ class ANSIString(str, metaclass=ANSIMeta):
|
|||
def __str__(self):
|
||||
return self._raw_string
|
||||
|
||||
def __format__(self, format_spec):
|
||||
"""
|
||||
This magic method covers ANSIString's behavior within a str.format() or f-string.
|
||||
|
||||
Current features supported: fill, align, width.
|
||||
|
||||
Args:
|
||||
format_spec (str): The format specification passed by f-string or str.format(). This is a string such as
|
||||
"0<30" which would mean "left justify to 30, filling with zeros". The full specification can be found
|
||||
at https://docs.python.org/3/library/string.html#formatspec
|
||||
|
||||
Returns:
|
||||
ansi_str (str): The formatted ANSIString's .raw() form, for display.
|
||||
"""
|
||||
# This calls the compiled regex stored on ANSIString's class to analyze the format spec.
|
||||
# It returns a dictionary.
|
||||
format_data = self.re_format.match(format_spec).groupdict()
|
||||
clean = self.clean()
|
||||
base_output = ANSIString(self.raw())
|
||||
align = format_data.get("align", "<")
|
||||
fill = format_data.get("fill", " ")
|
||||
|
||||
# Need to coerce width into an integer. We can be certain that it's numeric thanks to regex.
|
||||
width = format_data.get("width", None)
|
||||
if width is None:
|
||||
width = len(clean)
|
||||
else:
|
||||
width = int(width)
|
||||
|
||||
if align == "<":
|
||||
base_output = self.ljust(width, fill)
|
||||
elif align == ">":
|
||||
base_output = self.rjust(width, fill)
|
||||
elif align == "^":
|
||||
base_output = self.center(width, fill)
|
||||
elif align == "=":
|
||||
pass
|
||||
|
||||
# Return the raw string with ANSI markup, ready to be displayed.
|
||||
return base_output.raw()
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
Let's make the repr the command that would actually be used to
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ except ImportError:
|
|||
from pickle import dumps, loads
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.safestring import SafeString, SafeBytes
|
||||
from django.utils.safestring import SafeString
|
||||
from evennia.utils.utils import uses_database, is_iter, to_str, to_bytes
|
||||
from evennia.utils import logger
|
||||
|
||||
|
|
@ -549,7 +549,7 @@ def to_pickle(data):
|
|||
def process_item(item):
|
||||
"""Recursive processor and identification of data"""
|
||||
dtype = type(item)
|
||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||
return item
|
||||
elif dtype == tuple:
|
||||
return tuple(process_item(val) for val in item)
|
||||
|
|
@ -577,7 +577,7 @@ def to_pickle(data):
|
|||
except TypeError:
|
||||
return item
|
||||
except Exception:
|
||||
logger.log_error(f"The object {item} of type {type(item)} could not be stored.")
|
||||
logger.log_err(f"The object {item} of type {type(item)} could not be stored.")
|
||||
raise
|
||||
|
||||
return process_item(data)
|
||||
|
|
@ -609,7 +609,7 @@ def from_pickle(data, db_obj=None):
|
|||
def process_item(item):
|
||||
"""Recursive processor and identification of data"""
|
||||
dtype = type(item)
|
||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||
return item
|
||||
elif _IS_PACKED_DBOBJ(item):
|
||||
# this must be checked before tuple
|
||||
|
|
@ -638,7 +638,7 @@ def from_pickle(data, db_obj=None):
|
|||
def process_tree(item, parent):
|
||||
"""Recursive processor, building a parent-tree from iterable data"""
|
||||
dtype = type(item)
|
||||
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||
return item
|
||||
elif _IS_PACKED_DBOBJ(item):
|
||||
# this must be checked before tuple
|
||||
|
|
@ -716,7 +716,7 @@ def do_pickle(data):
|
|||
try:
|
||||
return dumps(data, protocol=PICKLE_PROTOCOL)
|
||||
except Exception:
|
||||
logger.log_error(f"Could not pickle data for storage: {data}")
|
||||
logger.log_err(f"Could not pickle data for storage: {data}")
|
||||
raise
|
||||
|
||||
|
||||
|
|
@ -725,7 +725,7 @@ def do_unpickle(data):
|
|||
try:
|
||||
return loads(to_bytes(data))
|
||||
except Exception:
|
||||
logger.log_error(f"Could not unpickle data from storage: {data}")
|
||||
logger.log_err(f"Could not unpickle data from storage: {data}")
|
||||
raise
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ _CMD_NOINPUT = cmdhandler.CMD_NOINPUT
|
|||
# Return messages
|
||||
|
||||
# i18n
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
_ERR_NOT_IMPLEMENTED = _(
|
||||
"Menu node '{nodename}' is either not implemented or " "caused an error. Make another choice."
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ from django.forms.fields import CharField
|
|||
from django.forms.widgets import Textarea
|
||||
|
||||
from pickle import loads, dumps
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
|
||||
DEFAULT_PROTOCOL = 4
|
||||
|
|
@ -210,10 +210,10 @@ class PickledObjectField(models.Field):
|
|||
"""
|
||||
Returns the default value for this field.
|
||||
|
||||
The default implementation on models.Field calls force_text
|
||||
The default implementation on models.Field calls force_str
|
||||
on the default, which means you can't set arbitrary Python
|
||||
objects as the default. To fix this, we just return the value
|
||||
without calling force_text on it. Note that if you set a
|
||||
without calling force_str on it. Note that if you set a
|
||||
callable as a default, the field will still call it. It will
|
||||
*not* try to pickle and encode it.
|
||||
|
||||
|
|
@ -267,13 +267,13 @@ class PickledObjectField(models.Field):
|
|||
|
||||
"""
|
||||
if value is not None and not isinstance(value, PickledObject):
|
||||
# We call force_text here explicitly, so that the encoded string
|
||||
# isn't rejected by the postgresql_psycopg2 backend. Alternatively,
|
||||
# We call force_str here explicitly, so that the encoded string
|
||||
# isn't rejected by the postgresql backend. Alternatively,
|
||||
# we could have just registered PickledObject with the psycopg
|
||||
# marshaller (telling it to store it like it would a string), but
|
||||
# since both of these methods result in the same value being stored,
|
||||
# doing things this way is much easier.
|
||||
value = force_text(dbsafe_encode(value, self.compress, self.protocol))
|
||||
value = force_str(dbsafe_encode(value, self.compress, self.protocol))
|
||||
return value
|
||||
|
||||
def value_to_string(self, obj):
|
||||
|
|
|
|||
|
|
@ -98,6 +98,30 @@ class TestMLen(TestCase):
|
|||
self.assertEqual(utils.m_len({"hello": True, "Goodbye": False}), 2)
|
||||
|
||||
|
||||
class TestANSIString(TestCase):
|
||||
"""
|
||||
Verifies that ANSIString's string-API works as intended.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.example_raw = "|relectric |cboogaloo|n"
|
||||
self.example_ansi = ANSIString(self.example_raw)
|
||||
self.example_str = "electric boogaloo"
|
||||
self.example_output = "\x1b[1m\x1b[31melectric \x1b[1m\x1b[36mboogaloo\x1b[0m"
|
||||
|
||||
def test_length(self):
|
||||
self.assertEqual(len(self.example_ansi), 17)
|
||||
|
||||
def test_clean(self):
|
||||
self.assertEqual(self.example_ansi.clean(), self.example_str)
|
||||
|
||||
def test_raw(self):
|
||||
self.assertEqual(self.example_ansi.raw(), self.example_output)
|
||||
|
||||
def test_format(self):
|
||||
self.assertEqual(f"{self.example_ansi:0<20}", self.example_output + "000")
|
||||
|
||||
|
||||
class TestTimeformat(TestCase):
|
||||
"""
|
||||
Default function header from utils.py:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from collections import defaultdict, OrderedDict
|
|||
from twisted.internet import threads, reactor
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import gettext as _
|
||||
from django.apps import apps
|
||||
from evennia.utils import logger
|
||||
|
||||
|
|
@ -1036,7 +1036,7 @@ def uses_database(name="sqlite3"):
|
|||
shortcut to having to use the full backend name.
|
||||
|
||||
Args:
|
||||
name (str): One of 'sqlite3', 'mysql', 'postgresql_psycopg2'
|
||||
name (str): One of 'sqlite3', 'mysql', 'postgresql'
|
||||
or 'oracle'.
|
||||
|
||||
Returns:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue