Added Option Classes.
This commit is contained in:
parent
1678db2435
commit
5bc9a42bb5
9 changed files with 472 additions and 66 deletions
|
|
@ -109,7 +109,6 @@ TASK_HANDLER = None
|
||||||
TICKER_HANDLER = None
|
TICKER_HANDLER = None
|
||||||
MONITOR_HANDLER = None
|
MONITOR_HANDLER = None
|
||||||
CHANNEL_HANDLER = None
|
CHANNEL_HANDLER = None
|
||||||
VALID_HANDLER = None
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -154,7 +153,7 @@ def _init():
|
||||||
global search_object, search_script, search_account, search_channel, search_help, search_tag, search_message
|
global search_object, search_script, search_account, search_channel, search_help, search_tag, search_message
|
||||||
global create_object, create_script, create_account, create_channel, create_message, create_help_entry
|
global create_object, create_script, create_account, create_channel, create_message, create_help_entry
|
||||||
global settings, lockfuncs, logger, utils, gametime, ansi, spawn, managers
|
global settings, lockfuncs, logger, utils, gametime, ansi, spawn, managers
|
||||||
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER, TASK_HANDLER, VALID_HANDLER
|
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER, TASK_HANDLER
|
||||||
global EvMenu, EvTable, EvForm, EvMore, EvEditor
|
global EvMenu, EvTable, EvForm, EvMore, EvEditor
|
||||||
global ANSIString
|
global ANSIString
|
||||||
|
|
||||||
|
|
@ -215,7 +214,6 @@ def _init():
|
||||||
from .server.sessionhandler import SESSION_HANDLER
|
from .server.sessionhandler import SESSION_HANDLER
|
||||||
from .comms.channelhandler import CHANNEL_HANDLER
|
from .comms.channelhandler import CHANNEL_HANDLER
|
||||||
from .scripts.monitorhandler import MONITOR_HANDLER
|
from .scripts.monitorhandler import MONITOR_HANDLER
|
||||||
from .utils.valid import VALID_HANDLER
|
|
||||||
|
|
||||||
# initialize the doc string
|
# initialize the doc string
|
||||||
global __doc__
|
global __doc__
|
||||||
|
|
|
||||||
|
|
@ -1385,7 +1385,7 @@ class DefaultAccount(with_metaclass(TypeclassBase, AccountDB)):
|
||||||
return look_string
|
return look_string
|
||||||
|
|
||||||
@lazy_property
|
@lazy_property
|
||||||
def option(self):
|
def options(self):
|
||||||
return OptionHandler(self, options_dict=settings.ACCOUNT_OPTIONS, save_category='option')
|
return OptionHandler(self, options_dict=settings.ACCOUNT_OPTIONS, save_category='option')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -883,14 +883,15 @@ class CmdStyle(COMMAND_DEFAULT_CLASS):
|
||||||
self.set()
|
self.set()
|
||||||
|
|
||||||
def list_styles(self):
|
def list_styles(self):
|
||||||
styles_table = self.style_table('Option', 'Description', 'Type', 'Value')
|
styles_table = self.style_table('Option', 'Description', 'Type', 'Value', width=78)
|
||||||
for k, v in self.account.option.options_dict.items():
|
for op_key in self.account.options.options_dict.keys():
|
||||||
styles_table.add_row(k, v[0], v[1], v[2])
|
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())
|
||||||
self.msg(str(styles_table))
|
self.msg(str(styles_table))
|
||||||
|
|
||||||
def set(self):
|
def set(self):
|
||||||
try:
|
try:
|
||||||
result = self.account.option.set(self.lhs, self.rhs)
|
result = self.account.options.set(self.lhs, self.rhs, account=self.account)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.msg(str(e))
|
self.msg(str(e))
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -236,8 +236,8 @@ class MuxCommand(Command):
|
||||||
return self.session.protocol_flags['SCREENWIDTH'][0]
|
return self.session.protocol_flags['SCREENWIDTH'][0]
|
||||||
|
|
||||||
def style_table(self, *args, **kwargs):
|
def style_table(self, *args, **kwargs):
|
||||||
border_color = self.account.option.get('border_color')
|
border_color = self.account.options.get('border_color')
|
||||||
column_color = self.account.option.get('column_names_color')
|
column_color = self.account.options.get('column_names_color')
|
||||||
|
|
||||||
colornames = ['|%s%s|n' % (column_color, col) for col in args]
|
colornames = ['|%s%s|n' % (column_color, col) for col in args]
|
||||||
|
|
||||||
|
|
@ -267,9 +267,9 @@ class MuxCommand(Command):
|
||||||
def render_header(self, header_text=None, fill_character=None, edge_character=None,
|
def render_header(self, header_text=None, fill_character=None, edge_character=None,
|
||||||
mode='header', color_header=True):
|
mode='header', color_header=True):
|
||||||
colors = dict()
|
colors = dict()
|
||||||
colors['border'] = self.account.option.get('border_color')
|
colors['border'] = self.account.options.get('border_color')
|
||||||
colors['headertext'] = self.account.option.get('%s_text_color' % mode)
|
colors['headertext'] = self.account.options.get('%s_text_color' % mode)
|
||||||
colors['headerstar'] = self.account.option.get('%s_star_color' % mode)
|
colors['headerstar'] = self.account.options.get('%s_star_color' % mode)
|
||||||
|
|
||||||
width = self.width()
|
width = self.width()
|
||||||
if edge_character:
|
if edge_character:
|
||||||
|
|
@ -288,7 +288,7 @@ class MuxCommand(Command):
|
||||||
else:
|
else:
|
||||||
center_string = ''
|
center_string = ''
|
||||||
|
|
||||||
fill_character = self.account.option.get('%s_fill' % mode)
|
fill_character = self.account.options.get('%s_fill' % mode)
|
||||||
|
|
||||||
remain_fill = width - len(center_string)
|
remain_fill = width - len(center_string)
|
||||||
if remain_fill % 2 == 0:
|
if remain_fill % 2 == 0:
|
||||||
|
|
|
||||||
|
|
@ -493,21 +493,24 @@ TYPECLASS_AGGRESSIVE_CACHE = True
|
||||||
# Options
|
# Options
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
# Replace entries in this dictionary to change the default stylings
|
# Replace entries in this dictionary to change the default options
|
||||||
# Evennia uses for commands. Or add more entries! Accounts can have
|
# Evennia uses for commands. Or add more entries! Accounts can change
|
||||||
# per-user settings that override these.
|
# their own settings with a command, but this sets down defaults.
|
||||||
|
|
||||||
|
# Option tuples are in this format:
|
||||||
|
# ("Description", 'Option Class', 'Default Value')
|
||||||
|
|
||||||
ACCOUNT_OPTIONS = {
|
ACCOUNT_OPTIONS = {
|
||||||
'border_color': ('Headers, footers, table borders, etc.', 'color', 'M'),
|
'border_color': ('Headers, footers, table borders, etc.', 'Color', 'M'),
|
||||||
'header_star_color': ('* inside Header lines.', 'color', 'm'),
|
'header_star_color': ('* inside Header lines.', 'Color', 'm'),
|
||||||
'header_text_color': ('Text inside Header lines.', 'color', 'w'),
|
'header_text_color': ('Text inside Header lines.', 'Color', 'w'),
|
||||||
'footer_text_color': ('Text inside Footer Lines.', 'color', 'w'),
|
'footer_text_color': ('Text inside Footer Lines.', 'Color', 'w'),
|
||||||
'column_names_color': ('Table column header text.', 'color', 'G'),
|
'column_names_color': ('Table column header text.', 'Color', 'G'),
|
||||||
'header_fill': ('Fill for Header lines.', 'word', '='),
|
'header_fill': ('Fill for Header lines.', 'Text', '='),
|
||||||
'separator_fill': ('Fill for Separator Lines.', 'word', '-'),
|
'separator_fill': ('Fill for Separator Lines.', 'Text', '-'),
|
||||||
'footer_fill': ('Fill for Footer Lines.', 'word', '='),
|
'footer_fill': ('Fill for Footer Lines.', 'Text', '='),
|
||||||
'help_category_color': ('Help category names.', 'color', 'g'),
|
'help_category_color': ('Help category names.', 'Color', 'g'),
|
||||||
'help_entry_color': ('Help entry names.', 'color', 'c'),
|
'help_entry_color': ('Help entry names.', 'Color', 'c'),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -564,6 +567,10 @@ PROTOTYPEFUNC_MODULES = ["evennia.utils.prototypefuncs",
|
||||||
# override those in earlier ones.
|
# override those in earlier ones.
|
||||||
VALIDFUNC_MODULES = ['evennia.utils.validfuncs', ]
|
VALIDFUNC_MODULES = ['evennia.utils.validfuncs', ]
|
||||||
|
|
||||||
|
# Modules holding Option classes. Those in later modules will
|
||||||
|
# override ones in earlier modules.
|
||||||
|
OPTIONCLASS_MODULES = ['evennia.utils.opclasses', ]
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
# Default Account setup and access
|
# Default Account setup and access
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
||||||
319
evennia/utils/opclasses.py
Normal file
319
evennia/utils/opclasses.py
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
import datetime as _dt
|
||||||
|
from evennia.utils.ansi import ANSIString as _ANSI
|
||||||
|
from evennia.utils.validfuncs import _TZ_DICT
|
||||||
|
from evennia.utils.valid import VALID_HANDLER as _VAL
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
its Handler's save category.
|
||||||
|
|
||||||
|
Designed to be extremely overloadable as some options can be cantankerous.
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
expect_type (str): What users will see this as asking for. Example: Color or email.
|
||||||
|
valid: Shortcut to the loaded VALID_HANDLER.
|
||||||
|
valid_type (str): The key of the Validator this uses.
|
||||||
|
"""
|
||||||
|
expect_type = ''
|
||||||
|
valid = _VAL
|
||||||
|
valid_type = ''
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.key
|
||||||
|
|
||||||
|
def __init__(self, handler, key, description, default, save_data=None):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (OptionHandler): The OptionHandler that 'owns' this Option.
|
||||||
|
key (str): The name this will be used for storage in a dictionary. Must be unique per
|
||||||
|
OptionHandler.
|
||||||
|
description (str): What this Option's text will show in commands and menus.
|
||||||
|
default: A default value for this Option.
|
||||||
|
save_data: Whatever was saved to Attributes. This differs by Option.
|
||||||
|
"""
|
||||||
|
self.handler = handler
|
||||||
|
self.key = key
|
||||||
|
self.default_value = default
|
||||||
|
self.description = description
|
||||||
|
self.save_data = save_data
|
||||||
|
|
||||||
|
# Value Storage contains None until the Option is loaded.
|
||||||
|
self.value_storage = None
|
||||||
|
|
||||||
|
# And it's not loaded until it's called upon to spit out its contents.
|
||||||
|
self.loaded = False
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
"""
|
||||||
|
Takes the provided save data, validates it, and gets this Option ready to use.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean: Whether loading was successful.
|
||||||
|
"""
|
||||||
|
if self.save_data is not None:
|
||||||
|
try:
|
||||||
|
self.value_storage = self.valid_save(self.save_data)
|
||||||
|
self.loaded = True
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(e) # need some kind of error message here!
|
||||||
|
return False
|
||||||
|
|
||||||
|
def customized(self):
|
||||||
|
return self.value_storage != self.default_value
|
||||||
|
|
||||||
|
def valid_save(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
|
||||||
|
very useful here.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
save_data: The data to check.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Arbitrary: Whatever the Option needs to track, like a string or a datetime. Not the same as what
|
||||||
|
users are SHOWN.
|
||||||
|
"""
|
||||||
|
return save_data
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Resets this Option to default settings.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
self. Why?
|
||||||
|
"""
|
||||||
|
self.value_storage = None
|
||||||
|
self.loaded = False
|
||||||
|
return self
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default(self):
|
||||||
|
return self.default_value
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value(self):
|
||||||
|
if not self.loaded and self.save_data is not None:
|
||||||
|
self.load()
|
||||||
|
if self.loaded:
|
||||||
|
return self.value_storage
|
||||||
|
else:
|
||||||
|
return self.default
|
||||||
|
|
||||||
|
def validate(self, value, account):
|
||||||
|
"""
|
||||||
|
Validate user input, which is presumed to be a string.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value (str): User input.
|
||||||
|
account (AccountDB): The Account that is performing the validation. This is necessary because of
|
||||||
|
other settings which may affect the check, such as an Account's timezone affecting how their
|
||||||
|
datetime entries are processed.
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
|
||||||
|
class Text(_BaseOption):
|
||||||
|
expect_type = 'Text'
|
||||||
|
valid_type = '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):
|
||||||
|
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'
|
||||||
|
|
||||||
|
def valid_save(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'
|
||||||
|
|
||||||
|
def display(self):
|
||||||
|
if self.value:
|
||||||
|
return '1 - On/True'
|
||||||
|
return '0 - Off/False'
|
||||||
|
|
||||||
|
def export(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def valid_save(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'
|
||||||
|
|
||||||
|
def display(self):
|
||||||
|
return f'{self.value} - |{self.value}this|n'
|
||||||
|
|
||||||
|
def valid_save(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'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default(self):
|
||||||
|
return _TZ_DICT[self.default_value]
|
||||||
|
|
||||||
|
def valid_save(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):
|
||||||
|
return str(self.value_storage)
|
||||||
|
|
||||||
|
|
||||||
|
class UnsignedInteger(_BaseOption):
|
||||||
|
expect_type = 'Whole Number 0+'
|
||||||
|
valid_type = 'unsigned_integer'
|
||||||
|
|
||||||
|
def valid_save(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'
|
||||||
|
|
||||||
|
def valid_save(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'
|
||||||
|
|
||||||
|
def valid_save(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'
|
||||||
|
|
||||||
|
def valid_save(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):
|
||||||
|
return self.value_storage.seconds
|
||||||
|
|
||||||
|
|
||||||
|
class Datetime(_BaseOption):
|
||||||
|
expect_type = 'Datetime'
|
||||||
|
valid_type = 'datetime'
|
||||||
|
|
||||||
|
def valid_save(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):
|
||||||
|
return int(self.value_storage.strftime('%s'))
|
||||||
|
|
||||||
|
|
||||||
|
class Future(Datetime):
|
||||||
|
expect_type = 'Future Datetime'
|
||||||
|
valid_type = 'future'
|
||||||
|
|
||||||
|
|
||||||
|
class Lock(Text):
|
||||||
|
expect_type = 'Lock String'
|
||||||
|
valid_type = 'lock'
|
||||||
|
|
@ -1,4 +1,27 @@
|
||||||
from evennia.utils.utils import string_partial_matching
|
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()
|
||||||
|
|
||||||
|
|
||||||
class OptionHandler(object):
|
class OptionHandler(object):
|
||||||
|
|
@ -7,44 +30,71 @@ class OptionHandler(object):
|
||||||
|
|
||||||
It uses a dictionary to store-and-cache frequently used settings such as colors for borders or an
|
It uses a dictionary to store-and-cache frequently used settings such as colors for borders or an
|
||||||
account's timezone.
|
account's timezone.
|
||||||
|
|
||||||
|
This is used for Account.options but it could be used by Scripts or Objects just as easily. All
|
||||||
|
it needs to be provided is an options_dict.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, obj, options_dict=None, save_category=None):
|
def __init__(self, obj, options_dict=None, save_category=None):
|
||||||
|
"""
|
||||||
|
Initialize an OptionHandler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (TypedObject): The Typed Object this sits on. Obj MUST implement the Evennia AttributeHandler
|
||||||
|
or this will barf.
|
||||||
|
options_dict (dict): A dictionary of option keys, where the values are options. The format of those
|
||||||
|
tuples is: ('key', "Description to show", 'option_type', <default value>)
|
||||||
|
save_category (str): The Options data will be stored to this Attribute category on obj.
|
||||||
|
"""
|
||||||
if not options_dict:
|
if not options_dict:
|
||||||
options_dict = dict()
|
options_dict = dict()
|
||||||
self.options_dict = options_dict
|
self.options_dict = options_dict
|
||||||
self.save_category = save_category
|
self.save_category = save_category
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
|
||||||
|
# This dictionary stores the in-memory Options by their key. Values are the Option objects.
|
||||||
self.options = dict()
|
self.options = dict()
|
||||||
|
|
||||||
|
# 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}
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
|
return self.get(item).value
|
||||||
|
|
||||||
|
def get(self, item, return_obj=False):
|
||||||
if item not in self.options_dict:
|
if item not in self.options_dict:
|
||||||
raise KeyError("Option not found!")
|
raise KeyError("Option not found!")
|
||||||
if item in self.options:
|
if item in self.options:
|
||||||
return self.options[item]
|
op_found = self.options[item]
|
||||||
import evennia
|
else:
|
||||||
option_def = self.options_dict[item]
|
op_found = self.load_option(item)
|
||||||
save_data = self.obj.attributes.get(item, category=self.save_category)
|
if return_obj:
|
||||||
if not save_data:
|
return op_found
|
||||||
return evennia.VALID_HANDLER[option_def[1]](option_def[2])
|
return op_found.value
|
||||||
self.options[item] = save_data
|
|
||||||
return save_data
|
|
||||||
|
|
||||||
def get(self, item):
|
def load_option(self, key):
|
||||||
return self[item]
|
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)
|
||||||
|
self.options[key] = loaded_option
|
||||||
|
return loaded_option
|
||||||
|
|
||||||
def set(self, option, value):
|
def set(self, option, value, account):
|
||||||
"""
|
"""
|
||||||
Change an individual option.
|
Change an individual option.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
option (str): The key of an option that can be changed. Allows partial matching.
|
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.
|
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:
|
Returns:
|
||||||
New value
|
New value
|
||||||
"""
|
"""
|
||||||
import evennia
|
|
||||||
if not option:
|
if not option:
|
||||||
raise ValueError("Option field blank!")
|
raise ValueError("Option field blank!")
|
||||||
found = string_partial_matching(list(self.options_dict.keys()), option, ret_index=False)
|
found = string_partial_matching(list(self.options_dict.keys()), option, ret_index=False)
|
||||||
|
|
@ -53,10 +103,5 @@ class OptionHandler(object):
|
||||||
if len(found) > 1:
|
if len(found) > 1:
|
||||||
raise ValueError(f"That matched: {', '.join(found)}. Please be more specific.")
|
raise ValueError(f"That matched: {', '.join(found)}. Please be more specific.")
|
||||||
found = found[0]
|
found = found[0]
|
||||||
option_def = self.options_dict[found]
|
op = self.get(found, return_obj=True)
|
||||||
if not value:
|
return op.set(value, account)
|
||||||
raise ValueError("Value field blank!")
|
|
||||||
new_value = evennia.VALID_HANDLER[option_def[1]](value, thing_name=found)
|
|
||||||
self.obj.attributes.add(found, category=self.save_category, value=new_value)
|
|
||||||
self.options[found] = new_value
|
|
||||||
return new_value
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,11 @@ from evennia.utils.utils import callables_from_module
|
||||||
|
|
||||||
|
|
||||||
class ValidHandler(object):
|
class ValidHandler(object):
|
||||||
|
"""
|
||||||
|
Loads and stores the final list of VALIDATOR FUNCTIONS.
|
||||||
|
|
||||||
|
Can access these as properties or dictionary-contents.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.valid_storage = {}
|
self.valid_storage = {}
|
||||||
|
|
@ -12,5 +17,9 @@ class ValidHandler(object):
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
return self.valid_storage.get(item, None)
|
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()
|
VALID_HANDLER = ValidHandler()
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ Contains all the validation functions.
|
||||||
All validation functions must have a checker (probably a session) and entry arg.
|
All validation functions must have a checker (probably a session) and entry arg.
|
||||||
|
|
||||||
They can employ more paramters at your leisure.
|
They can employ more paramters at your leisure.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re as _re
|
import re as _re
|
||||||
|
|
@ -17,7 +19,7 @@ from evennia.utils.utils import string_partial_matching as _partial
|
||||||
_TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}
|
_TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}
|
||||||
|
|
||||||
|
|
||||||
def color(entry, thing_name='Color'):
|
def color(entry, thing_name='Color', **kwargs):
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError(f"Nothing entered for a {thing_name}!")
|
raise ValueError(f"Nothing entered for a {thing_name}!")
|
||||||
test_str = _ansi('|%s|n' % entry)
|
test_str = _ansi('|%s|n' % entry)
|
||||||
|
|
@ -26,13 +28,18 @@ def color(entry, thing_name='Color'):
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def datetime(entry, thing_name='Datetime', from_tz=None):
|
def datetime(entry, thing_name='Datetime', account=None, from_tz=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
Process a datetime string in standard forms while accounting for the inputter's timezone.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
entry (str): A date string from a user.
|
entry (str): A date string from a user.
|
||||||
thing_name (str): Name to display this datetime as.
|
thing_name (str): Name to display this datetime as.
|
||||||
from_tz (pytz): An instance of pytz from the user.
|
account (AccountDB): The Account performing this lookup. Unless from_tz is provided,
|
||||||
|
account's timezone will be used (if found) for local time and convert the results
|
||||||
|
to UTC.
|
||||||
|
from_tz (pytz): An instance of pytz from the user. If not provided, defaults to whatever
|
||||||
|
the Account uses. If neither one is provided, defaults to UTC.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
datetime in utc.
|
datetime in utc.
|
||||||
|
|
@ -59,7 +66,7 @@ def datetime(entry, thing_name='Datetime', from_tz=None):
|
||||||
return local_tz.astimezone(utc)
|
return local_tz.astimezone(utc)
|
||||||
|
|
||||||
|
|
||||||
def duration(entry, thing_name='Duration'):
|
def duration(entry, thing_name='Duration', **kwargs):
|
||||||
"""
|
"""
|
||||||
Take a string and derive a datetime timedelta from it.
|
Take a string and derive a datetime timedelta from it.
|
||||||
|
|
||||||
|
|
@ -98,14 +105,14 @@ def duration(entry, thing_name='Duration'):
|
||||||
return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)
|
return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)
|
||||||
|
|
||||||
|
|
||||||
def future(entry, thing_name="Future Datetime", from_tz=None):
|
def future(entry, thing_name="Future Datetime", from_tz=None, **kwargs):
|
||||||
time = datetime(entry, thing_name)
|
time = datetime(entry, thing_name)
|
||||||
if time < _dt.datetime.utcnow():
|
if time < _dt.datetime.utcnow():
|
||||||
raise ValueError(f"That {thing_name} is in the past! Must give a Future datetime!")
|
raise ValueError(f"That {thing_name} is in the past! Must give a Future datetime!")
|
||||||
return time
|
return time
|
||||||
|
|
||||||
|
|
||||||
def signed_integer(entry, thing_name="Signed Integer"):
|
def signed_integer(entry, thing_name="Signed Integer", **kwargs):
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError(f"Must enter a whole number for {thing_name}!")
|
raise ValueError(f"Must enter a whole number for {thing_name}!")
|
||||||
try:
|
try:
|
||||||
|
|
@ -115,21 +122,30 @@ def signed_integer(entry, thing_name="Signed Integer"):
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
def positive_integer(entry, thing_name="Positive Integer"):
|
def positive_integer(entry, thing_name="Positive Integer", **kwargs):
|
||||||
num = signed_integer(entry, thing_name)
|
num = signed_integer(entry, thing_name)
|
||||||
if not num >= 1:
|
if not num >= 1:
|
||||||
raise ValueError(f"Must enter a whole number greater than 0 for {thing_name}!")
|
raise ValueError(f"Must enter a whole number greater than 0 for {thing_name}!")
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
def unsigned_integer(entry, thing_name="Unsigned Integer"):
|
def unsigned_integer(entry, thing_name="Unsigned Integer", **kwargs):
|
||||||
num = signed_integer(entry, thing_name)
|
num = signed_integer(entry, thing_name)
|
||||||
if not num >= 0:
|
if not num >= 0:
|
||||||
raise ValueError(f"{thing_name} must be a whole number greater than or equal to 0!")
|
raise ValueError(f"{thing_name} must be a whole number greater than or equal to 0!")
|
||||||
return num
|
return num
|
||||||
|
|
||||||
|
|
||||||
def boolean(entry, thing_name="True/False"):
|
def boolean(entry, thing_name="True/False", **kwargs):
|
||||||
|
"""
|
||||||
|
Simplest check in computer logic, right? This will take user input to flick the switch on or off
|
||||||
|
Args:
|
||||||
|
entry (str): A value such as True, On, Enabled, Disabled, False, 0, or 1.
|
||||||
|
thing_name (str): What kind of Boolean we are setting. What Option is this for?
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean
|
||||||
|
"""
|
||||||
entry = entry.upper()
|
entry = entry.upper()
|
||||||
error = f"Must enter 0 (false) or 1 (true) for {thing_name}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled"
|
error = f"Must enter 0 (false) or 1 (true) for {thing_name}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled"
|
||||||
if not entry:
|
if not entry:
|
||||||
|
|
@ -141,24 +157,36 @@ def boolean(entry, thing_name="True/False"):
|
||||||
raise ValueError(error)
|
raise ValueError(error)
|
||||||
|
|
||||||
|
|
||||||
def timezone(entry, thing_name="Timezone"):
|
def timezone(entry, thing_name="Timezone", **kwargs):
|
||||||
|
"""
|
||||||
|
Takes user input as string, and partial matches a Timezone.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entry (str): The name of the Timezone.
|
||||||
|
thing_name (str): What this Timezone is used for.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A PYTZ timezone.
|
||||||
|
"""
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError(f"No {thing_name} entered!")
|
raise ValueError(f"No {thing_name} entered!")
|
||||||
found = _partial(_TZ_DICT.keys(), entry)
|
found = _partial(list(_TZ_DICT.keys()), entry)
|
||||||
if found:
|
if found:
|
||||||
return _TZ_DICT[found]
|
return _TZ_DICT[found]
|
||||||
raise ValueError(f"Could not find timezone '{entry}' for {thing_name}!")
|
raise ValueError(f"Could not find timezone '{entry}' for {thing_name}!")
|
||||||
|
|
||||||
|
|
||||||
def email(entry, thing_name="Email Address"):
|
def email(entry, thing_name="Email Address", **kwargs):
|
||||||
|
if not entry:
|
||||||
|
raise ValueError("Email address field empty!")
|
||||||
try:
|
try:
|
||||||
_val_email(entry) #offloading the hard work to Django!
|
_val_email(entry) # offloading the hard work to Django!
|
||||||
except _error:
|
except _error:
|
||||||
raise ValueError(f"That isn't a valid {thing_name}!")
|
raise ValueError(f"That isn't a valid {thing_name}!")
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
||||||
def lock(entry, thing_name='lockstring', options=None):
|
def lock(entry, thing_name='locks', access_options=None, **kwargs):
|
||||||
entry = entry.strip()
|
entry = entry.strip()
|
||||||
if not entry:
|
if not entry:
|
||||||
raise ValueError(f"No {thing_name} entered to set!")
|
raise ValueError(f"No {thing_name} entered to set!")
|
||||||
|
|
@ -166,10 +194,9 @@ def lock(entry, thing_name='lockstring', options=None):
|
||||||
access_type, lockfunc = locksetting.split(':', 1)
|
access_type, lockfunc = locksetting.split(':', 1)
|
||||||
if not access_type:
|
if not access_type:
|
||||||
raise ValueError("Must enter an access type!")
|
raise ValueError("Must enter an access type!")
|
||||||
if options:
|
if access_options:
|
||||||
accmatch = _partial(options, access_type)
|
if access_type not in access_options:
|
||||||
if not accmatch:
|
raise ValueError(f"Access type must be one of: {', '.join(access_options)}")
|
||||||
raise ValueError(f"Access type must be one of: {', '.join(options)}")
|
|
||||||
if not lockfunc:
|
if not lockfunc:
|
||||||
raise ValueError("Lock func not entered.")
|
raise ValueError("Lock func not entered.")
|
||||||
return entry
|
return entry
|
||||||
Loading…
Add table
Add a link
Reference in a new issue