mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Cleanups. Containers created and BaseOption done better.
This commit is contained in:
parent
5bc9a42bb5
commit
d96cf3b809
9 changed files with 159 additions and 234 deletions
|
|
@ -198,6 +198,10 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
|||
def sessions(self):
|
||||
return AccountSessionHandler(self)
|
||||
|
||||
@lazy_property
|
||||
def options(self):
|
||||
return OptionHandler(self, options_dict=settings.OPTIONS_ACCOUNT_DEFAULT, save_category='option')
|
||||
|
||||
# Do not make this a lazy property; the web UI will not refresh it!
|
||||
@property
|
||||
def characters(self):
|
||||
|
|
@ -1384,10 +1388,6 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
|||
look_string = ("-" * 68) + "\n" + "".join(result) + "\n" + ("-" * 68)
|
||||
return look_string
|
||||
|
||||
@lazy_property
|
||||
def options(self):
|
||||
return OptionHandler(self, options_dict=settings.ACCOUNT_OPTIONS, save_category='option')
|
||||
|
||||
|
||||
class DefaultGuest(DefaultAccount):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
"""
|
||||
Styles (playing off CSS) are a way to change the colors and symbols used for standardized
|
||||
displays used in Evennia. Accounts all have a StyleHandler accessible via .style which
|
||||
retrieves per-Account settings, falling back to the global settings contained in settings.py.
|
||||
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class StyleHandler(object):
|
||||
category = 'style'
|
||||
|
||||
def __init__(self, acc):
|
||||
self.acc = acc
|
||||
|
||||
def set(self, option, value):
|
||||
pass
|
||||
|
||||
def get(self, option):
|
||||
"""
|
||||
Get the stored Style information from this Account's Attributes if possible.
|
||||
If not, fallback to the Global.
|
||||
|
||||
Args:
|
||||
option (str): The key of the Style to retrieve.
|
||||
|
||||
Returns:
|
||||
String or None
|
||||
"""
|
||||
stored = self.acc.attributes.get(option, category=self.category)
|
||||
if stored:
|
||||
return stored
|
||||
default = settings.DEFAULT_STYLES.get(option, None)
|
||||
if default:
|
||||
return default[2]
|
||||
return None
|
||||
|
|
@ -886,12 +886,12 @@ class CmdStyle(COMMAND_DEFAULT_CLASS):
|
|||
styles_table = self.style_table('Option', 'Description', 'Type', 'Value', width=78)
|
||||
for op_key in self.account.options.options_dict.keys():
|
||||
op_found = self.account.options.get(op_key, return_obj=True)
|
||||
styles_table.add_row(op_key, op_found.description, op_found.expect_type, op_found.display())
|
||||
styles_table.add_row(op_key, op_found.description, op_found.__class__.__name__, op_found.display())
|
||||
self.msg(str(styles_table))
|
||||
|
||||
def set(self):
|
||||
try:
|
||||
result = self.account.options.set(self.lhs, self.rhs, account=self.account)
|
||||
result = self.account.options.set(self.lhs, self.rhs)
|
||||
except ValueError as e:
|
||||
self.msg(str(e))
|
||||
return
|
||||
|
|
|
|||
|
|
@ -500,7 +500,7 @@ TYPECLASS_AGGRESSIVE_CACHE = True
|
|||
# Option tuples are in this format:
|
||||
# ("Description", 'Option Class', 'Default Value')
|
||||
|
||||
ACCOUNT_OPTIONS = {
|
||||
OPTIONS_ACCOUNT_DEFAULT = {
|
||||
'border_color': ('Headers, footers, table borders, etc.', 'Color', 'M'),
|
||||
'header_star_color': ('* inside Header lines.', 'Color', 'm'),
|
||||
'header_text_color': ('Text inside Header lines.', 'Color', 'w'),
|
||||
|
|
@ -565,11 +565,11 @@ PROTOTYPEFUNC_MODULES = ["evennia.utils.prototypefuncs",
|
|||
|
||||
# Module holding validator functions. functions in later modules will
|
||||
# override those in earlier ones.
|
||||
VALIDFUNC_MODULES = ['evennia.utils.validfuncs', ]
|
||||
VALIDATOR_MODULES = ['evennia.utils.validfuncs', ]
|
||||
|
||||
# Modules holding Option classes. Those in later modules will
|
||||
# override ones in earlier modules.
|
||||
OPTIONCLASS_MODULES = ['evennia.utils.opclasses', ]
|
||||
OPTION_MODULES = ['evennia.utils.opclasses', ]
|
||||
|
||||
######################################################################
|
||||
# Default Account setup and access
|
||||
|
|
|
|||
47
evennia/utils/containers.py
Normal file
47
evennia/utils/containers.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
from django.conf import settings
|
||||
from evennia.utils.utils import callables_from_module
|
||||
|
||||
|
||||
class ValidContainer(object):
|
||||
"""
|
||||
Loads and stores the final list of VALIDATOR FUNCTIONS.
|
||||
|
||||
Can access these as properties or dictionary-contents.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.valid_storage = {}
|
||||
for module in settings.VALIDATOR_MODULES:
|
||||
self.valid_storage.update(callables_from_module(module))
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.valid_storage.get(item, None)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self[item]
|
||||
|
||||
|
||||
# Ensure that we have a Singleton of ValidHandler that is always loaded... and only needs to be loaded once.
|
||||
VALID_CONTAINER = ValidContainer()
|
||||
|
||||
|
||||
class OptionContainer(object):
|
||||
"""
|
||||
Loads and stores the final list of OPTION CLASSES.
|
||||
|
||||
Can access these as properties or dictionary-contents.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.option_storage = {}
|
||||
for module in settings.OPTION_MODULES:
|
||||
self.option_storage.update(callables_from_module(module))
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.option_storage.get(item, None)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self[item]
|
||||
|
||||
|
||||
# Ensure that we have a Singleton that keeps all loaded Options.
|
||||
OPTION_CONTAINER = OptionContainer()
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import datetime as _dt
|
||||
from evennia import logger as _log
|
||||
from evennia.utils.ansi import ANSIString as _ANSI
|
||||
from evennia.utils.validfuncs import _TZ_DICT
|
||||
from evennia.utils.valid import VALID_HANDLER as _VAL
|
||||
from evennia.utils.containers import VALID_CONTAINER as _VAL
|
||||
|
||||
|
||||
class _BaseOption(object):
|
||||
class BaseOption(object):
|
||||
"""
|
||||
Abstract Class to deal with encapsulating individual Options. An Option has a name/key, a description
|
||||
to display in relevant commands and menus, and a default value. It saves to the owner's Attributes using
|
||||
|
|
@ -17,9 +18,7 @@ class _BaseOption(object):
|
|||
valid: Shortcut to the loaded VALID_HANDLER.
|
||||
valid_type (str): The key of the Validator this uses.
|
||||
"""
|
||||
expect_type = ''
|
||||
valid = _VAL
|
||||
valid_type = ''
|
||||
validator_key = ''
|
||||
|
||||
def __str__(self):
|
||||
return self.key
|
||||
|
|
@ -47,7 +46,16 @@ class _BaseOption(object):
|
|||
# And it's not loaded until it's called upon to spit out its contents.
|
||||
self.loaded = False
|
||||
|
||||
def load(self):
|
||||
def display(self, **kwargs):
|
||||
"""
|
||||
Renders the Option's value as something pretty to look at.
|
||||
|
||||
Returns:
|
||||
How the stored value should be projected to users. a raw timedelta is pretty ugly, y'know?
|
||||
"""
|
||||
return self.value
|
||||
|
||||
def _load(self):
|
||||
"""
|
||||
Takes the provided save data, validates it, and gets this Option ready to use.
|
||||
|
||||
|
|
@ -56,17 +64,23 @@ class _BaseOption(object):
|
|||
"""
|
||||
if self.save_data is not None:
|
||||
try:
|
||||
self.value_storage = self.valid_save(self.save_data)
|
||||
self.value_storage = self.deserialize(self.save_data)
|
||||
self.loaded = True
|
||||
return True
|
||||
except Exception as e:
|
||||
print(e) # need some kind of error message here!
|
||||
_log.log_trace(e)
|
||||
return False
|
||||
|
||||
def customized(self):
|
||||
return self.value_storage != self.default_value
|
||||
def _save(self):
|
||||
"""
|
||||
Exports the current value to an Attribute.
|
||||
|
||||
def valid_save(self, save_data):
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.handler.obj.attributes.add(self.key, category=self.handler.save_category, value=self.serialize())
|
||||
|
||||
def deserialize(self, save_data):
|
||||
"""
|
||||
Perform sanity-checking on the save data. This isn't the same as Validators, as Validators deal with
|
||||
user input. save data might be a timedelta or a list or some other object. isinstance() is probably
|
||||
|
|
@ -81,16 +95,18 @@ class _BaseOption(object):
|
|||
"""
|
||||
return save_data
|
||||
|
||||
def clear(self):
|
||||
def serialize(self):
|
||||
"""
|
||||
Resets this Option to default settings.
|
||||
Serializes the save data for Attribute storage if it's something complicated.
|
||||
|
||||
Returns:
|
||||
self. Why?
|
||||
Whatever best handles the Attribute.
|
||||
"""
|
||||
self.value_storage = None
|
||||
self.loaded = False
|
||||
return self
|
||||
return self.value_storage
|
||||
|
||||
@property
|
||||
def changed(self):
|
||||
return self.value_storage != self.default_value
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
|
|
@ -99,13 +115,30 @@ class _BaseOption(object):
|
|||
@property
|
||||
def value(self):
|
||||
if not self.loaded and self.save_data is not None:
|
||||
self.load()
|
||||
self._load()
|
||||
if self.loaded:
|
||||
return self.value_storage
|
||||
else:
|
||||
return self.default
|
||||
|
||||
def validate(self, value, account):
|
||||
@value.setter
|
||||
def value(self, value):
|
||||
"""
|
||||
Takes user input, presumed to be a string, and changes the value if it is a valid input.
|
||||
|
||||
Args:
|
||||
value:
|
||||
account:
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
final_value = self.validate(value)
|
||||
self.value_storage = final_value
|
||||
self.loaded = True
|
||||
self._save()
|
||||
|
||||
def validate(self, value):
|
||||
"""
|
||||
Validate user input, which is presumed to be a string.
|
||||
|
||||
|
|
@ -118,202 +151,128 @@ class _BaseOption(object):
|
|||
Returns:
|
||||
The results of a Validator call. Might be any kind of python object.
|
||||
"""
|
||||
return self.do_validate(value, account)
|
||||
|
||||
def do_validate(self, value, account):
|
||||
"""
|
||||
Second layer of abstraction on validation due to design choices.
|
||||
|
||||
Args:
|
||||
value:
|
||||
account:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
return self.valid[self.valid_type](value, thing_name=self.key, account=account)
|
||||
|
||||
def set(self, value, account):
|
||||
"""
|
||||
Takes user input, presumed to be a string, and changes the value if it is a valid input.
|
||||
|
||||
Args:
|
||||
value:
|
||||
account:
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
final_value = self.validate(value, account)
|
||||
self.value_storage = final_value
|
||||
self.loaded = True
|
||||
self.save()
|
||||
return self.display()
|
||||
|
||||
def display(self):
|
||||
"""
|
||||
Renders the Option's value as something pretty to look at.
|
||||
|
||||
Returns:
|
||||
How the stored value should be projected to users. a raw timedelta is pretty ugly, y'know?
|
||||
"""
|
||||
return self.value
|
||||
|
||||
def export(self):
|
||||
"""
|
||||
Serializes the save data for Attribute storage if it's something complicated.
|
||||
|
||||
Returns:
|
||||
Whatever best handles the Attribute.
|
||||
"""
|
||||
return self.value_storage
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Exports the current value to an Attribute.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.handler.obj.attributes.add(self.key, category=self.handler.save_category, value=self.export())
|
||||
return _VAL[self.validator_key](value, thing_name=self.key)
|
||||
|
||||
|
||||
class Text(_BaseOption):
|
||||
expect_type = 'Text'
|
||||
valid_type = 'text'
|
||||
class Text(BaseOption):
|
||||
validator_key = 'text'
|
||||
|
||||
def do_validate(self, value, account):
|
||||
if not str(value):
|
||||
raise ValueError("Must enter some text!")
|
||||
return str(value)
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
got_data = str(save_data)
|
||||
if not got_data:
|
||||
raise ValueError(f"{self.key} expected Text data, got '{save_data}'")
|
||||
return got_data
|
||||
|
||||
|
||||
class Email(_BaseOption):
|
||||
expect_type = 'Email'
|
||||
valid_type = 'email'
|
||||
class Email(BaseOption):
|
||||
validator_key = 'email'
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
got_data = str(save_data)
|
||||
if not got_data:
|
||||
raise ValueError(f"{self.key} expected String data, got '{save_data}'")
|
||||
return got_data
|
||||
|
||||
|
||||
class Boolean(_BaseOption):
|
||||
expect_type = 'Boolean'
|
||||
valid_type = 'boolean'
|
||||
class Boolean(BaseOption):
|
||||
validator_key = 'boolean'
|
||||
|
||||
def display(self):
|
||||
def display(self, **kwargs):
|
||||
if self.value:
|
||||
return '1 - On/True'
|
||||
return '0 - Off/False'
|
||||
|
||||
def export(self):
|
||||
def serialize(self):
|
||||
return self.value
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
if not isinstance(save_data, bool):
|
||||
raise ValueError(f"{self.key} expected Boolean, got '{save_data}'")
|
||||
return save_data
|
||||
|
||||
|
||||
class Color(_BaseOption):
|
||||
expect_type = 'Color'
|
||||
valid_type = 'color'
|
||||
class Color(BaseOption):
|
||||
validator_key = 'color'
|
||||
|
||||
def display(self):
|
||||
def display(self, **kwargs):
|
||||
return f'{self.value} - |{self.value}this|n'
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
if not save_data or len(_ANSI(f'|{save_data}|n')) > 0:
|
||||
raise ValueError(f"{self.key} expected Color Code, got '{save_data}'")
|
||||
return save_data
|
||||
|
||||
|
||||
class Timezone(_BaseOption):
|
||||
expect_type = 'Timezone'
|
||||
valid_type = 'timezone'
|
||||
class Timezone(BaseOption):
|
||||
validator_key = 'timezone'
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
return _TZ_DICT[self.default_value]
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
if save_data not in _TZ_DICT:
|
||||
raise ValueError(f"{self.key} expected Timezone Data, got '{save_data}'")
|
||||
return _TZ_DICT[save_data]
|
||||
|
||||
def export(self):
|
||||
def serialize(self):
|
||||
return str(self.value_storage)
|
||||
|
||||
|
||||
class UnsignedInteger(_BaseOption):
|
||||
expect_type = 'Whole Number 0+'
|
||||
valid_type = 'unsigned_integer'
|
||||
class UnsignedInteger(BaseOption):
|
||||
validator_key = 'unsigned_integer'
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
if isinstance(save_data, int) and save_data >= 0:
|
||||
return save_data
|
||||
raise ValueError(f"{self.key} expected Whole Number 0+, got '{save_data}'")
|
||||
|
||||
|
||||
class SignedInteger(_BaseOption):
|
||||
expect_type = 'Whole Number'
|
||||
valid_type = 'signed_integer'
|
||||
class SignedInteger(BaseOption):
|
||||
validator_key = 'signed_integer'
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
if isinstance(save_data, int):
|
||||
return save_data
|
||||
raise ValueError(f"{self.key} expected Whole Number, got '{save_data}'")
|
||||
|
||||
|
||||
class PositiveInteger(_BaseOption):
|
||||
expect_type = 'Whole Number 1+'
|
||||
valid_type = 'positive_integer'
|
||||
class PositiveInteger(BaseOption):
|
||||
validator_key = 'positive_integer'
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
if isinstance(save_data, int) and save_data > 0:
|
||||
return save_data
|
||||
raise ValueError(f"{self.key} expected Whole Number 1+, got '{save_data}'")
|
||||
|
||||
|
||||
class Duration(_BaseOption):
|
||||
expect_type = 'Duration'
|
||||
valid_type = 'duration'
|
||||
class Duration(BaseOption):
|
||||
validator_key = 'duration'
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
if isinstance(save_data, int):
|
||||
return _dt.timedelta(0, save_data, 0, 0, 0, 0, 0)
|
||||
raise ValueError(f"{self.key} expected Timedelta in seconds, got '{save_data}'")
|
||||
|
||||
def export(self):
|
||||
def serialize(self):
|
||||
return self.value_storage.seconds
|
||||
|
||||
|
||||
class Datetime(_BaseOption):
|
||||
expect_type = 'Datetime'
|
||||
valid_type = 'datetime'
|
||||
class Datetime(BaseOption):
|
||||
validator_key = 'datetime'
|
||||
|
||||
def valid_save(self, save_data):
|
||||
def deserialize(self, save_data):
|
||||
if isinstance(save_data, int):
|
||||
return _dt.datetime.utcfromtimestamp(save_data)
|
||||
raise ValueError(f"{self.key} expected UTC Datetime in EPOCH format, got '{save_data}'")
|
||||
|
||||
def export(self):
|
||||
def serialize(self):
|
||||
return int(self.value_storage.strftime('%s'))
|
||||
|
||||
|
||||
class Future(Datetime):
|
||||
expect_type = 'Future Datetime'
|
||||
valid_type = 'future'
|
||||
validator_key = 'future'
|
||||
|
||||
|
||||
class Lock(Text):
|
||||
expect_type = 'Lock String'
|
||||
valid_type = 'lock'
|
||||
validator_key = 'lock'
|
||||
|
|
|
|||
|
|
@ -1,27 +1,5 @@
|
|||
from django.conf import settings
|
||||
from evennia.utils.utils import string_partial_matching, callables_from_module
|
||||
|
||||
|
||||
class OptionManager(object):
|
||||
"""
|
||||
Loads and stores the final list of OPTION CLASSES.
|
||||
|
||||
Can access these as properties or dictionary-contents.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.option_storage = {}
|
||||
for module in settings.OPTIONCLASS_MODULES:
|
||||
self.option_storage.update(callables_from_module(module))
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.option_storage.get(item, None)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self[item]
|
||||
|
||||
|
||||
# Ensure that we have a Singleton that keeps all loaded Options.
|
||||
OPTION_MANAGER = OptionManager()
|
||||
from evennia.utils.utils import string_partial_matching
|
||||
from evennia.utils.containers import OPTION_CONTAINER
|
||||
|
||||
|
||||
class OptionHandler(object):
|
||||
|
|
@ -58,7 +36,7 @@ class OptionHandler(object):
|
|||
# We use lazy-loading of each Option when it's called for, but it's good to have the save data
|
||||
# on hand.
|
||||
self.save_data = {s.key: s.value for s in obj.attributes.get(category=save_category,
|
||||
return_list=True, return_obj=True) if s}
|
||||
return_list=True, return_obj=True) if s}
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.get(item).value
|
||||
|
|
@ -78,19 +56,17 @@ class OptionHandler(object):
|
|||
option_def = self.options_dict[key]
|
||||
save_data = self.save_data.get(key, None)
|
||||
self.obj.msg(save_data)
|
||||
loaded_option = OPTION_MANAGER[option_def[1]](self, key, option_def[0], option_def[2], save_data)
|
||||
loaded_option = OPTION_CONTAINER[option_def[1]](self, key, option_def[0], option_def[2], save_data)
|
||||
self.options[key] = loaded_option
|
||||
return loaded_option
|
||||
|
||||
def set(self, option, value, account):
|
||||
def set(self, option, value):
|
||||
"""
|
||||
Change an individual option.
|
||||
|
||||
Args:
|
||||
option (str): The key of an option that can be changed. Allows partial matching.
|
||||
value (str): The value that should be checked, coerced, and stored.
|
||||
account (AccountDB): The Account performing the setting. Necessary due to other
|
||||
option lookups like timezone.
|
||||
|
||||
Returns:
|
||||
New value
|
||||
|
|
@ -104,4 +80,8 @@ class OptionHandler(object):
|
|||
raise ValueError(f"That matched: {', '.join(found)}. Please be more specific.")
|
||||
found = found[0]
|
||||
op = self.get(found, return_obj=True)
|
||||
return op.set(value, account)
|
||||
op.value = value
|
||||
return op.display()
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
from django.conf import settings
|
||||
from evennia.utils.utils import callables_from_module
|
||||
|
||||
|
||||
class ValidHandler(object):
|
||||
"""
|
||||
Loads and stores the final list of VALIDATOR FUNCTIONS.
|
||||
|
||||
Can access these as properties or dictionary-contents.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.valid_storage = {}
|
||||
for module in settings.VALIDFUNC_MODULES:
|
||||
self.valid_storage.update(callables_from_module(module))
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.valid_storage.get(item, None)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return self[item]
|
||||
|
||||
|
||||
# Ensure that we have a Singleton of ValidHandler that is always loaded... and only needs to be loaded once.
|
||||
VALID_HANDLER = ValidHandler()
|
||||
|
|
@ -47,8 +47,8 @@ def datetime(entry, thing_name='Datetime', account=None, from_tz=None, **kwargs)
|
|||
if not entry:
|
||||
raise ValueError(f"No {thing_name} entered!")
|
||||
if not from_tz:
|
||||
from_tz = _pytz['UTC']
|
||||
utc = _pytz['UTC']
|
||||
from_tz = _pytz.UTC
|
||||
utc = _pytz.UTC
|
||||
now = _dt.datetime.utcnow().replace(tzinfo=utc)
|
||||
cur_year = now.strftime('%Y')
|
||||
split_time = entry.split(' ')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue