Refactored with more consistent naming and placement. Fixed the table style wrapper.

This commit is contained in:
Andrew Bastien 2019-04-13 21:47:09 -04:00
parent d96cf3b809
commit 652186d829
9 changed files with 164 additions and 118 deletions

View file

@ -118,6 +118,9 @@ Web/Django standard initiative (@strikaco)
- `evennia.MONITOR_HANDLER.all` now takes keyword argument `obj` to only retrieve monitors from that specific - `evennia.MONITOR_HANDLER.all` now takes keyword argument `obj` to only retrieve monitors from that specific
Object (rather than all monitors in the entire handler). Object (rather than all monitors in the entire handler).
- Support adding `\f` in command doc strings to force where EvMore puts page breaks. - Support adding `\f` in command doc strings to force where EvMore puts page breaks.
- Validation Functions now added with standard API to homogenize user input validation.
- Option Classes added to make storing user-options easier and smoother.
- `evennia.VALIDATOR_CONTAINER` and `evennia.OPTION_CONTAINER` added to load these.
### Contribs ### Contribs

View file

@ -110,6 +110,10 @@ TICKER_HANDLER = None
MONITOR_HANDLER = None MONITOR_HANDLER = None
CHANNEL_HANDLER = None CHANNEL_HANDLER = None
# Containers
VALIDATOR_CONTAINER = None
OPTION_CONTAINER = None
def _create_version(): def _create_version():
@ -154,6 +158,7 @@ def _init():
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 global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER, TASK_HANDLER
global VALIDATOR_CONTAINER, OPTION_CONTAINER
global EvMenu, EvTable, EvForm, EvMore, EvEditor global EvMenu, EvTable, EvForm, EvMore, EvEditor
global ANSIString global ANSIString
@ -215,6 +220,10 @@ def _init():
from .comms.channelhandler import CHANNEL_HANDLER from .comms.channelhandler import CHANNEL_HANDLER
from .scripts.monitorhandler import MONITOR_HANDLER from .scripts.monitorhandler import MONITOR_HANDLER
# containers
from .utils.containers import VALIDATOR_CONTAINER
from .utils.containers import OPTION_CONTAINER
# initialize the doc string # initialize the doc string
global __doc__ global __doc__
__doc__ = ansi.parse_ansi(DOCSTRING) __doc__ = ansi.parse_ansi(DOCSTRING)

View file

@ -7,11 +7,15 @@ All commands in Evennia inherit from the 'Command' class in this module.
from builtins import range from builtins import range
import re import re
import math
from django.conf import settings from django.conf import settings
from evennia.locks.lockhandler import LockHandler from evennia.locks.lockhandler import LockHandler
from evennia.utils.utils import is_iter, fill, lazy_property, make_iter from evennia.utils.utils import is_iter, fill, lazy_property, make_iter
from evennia.utils.evtable import EvTable
from evennia.utils.ansi import ANSIString
from future.utils import with_metaclass from future.utils import with_metaclass
@ -468,6 +472,98 @@ class Command(with_metaclass(CommandMeta, object)):
""" """
return self.__doc__ return self.__doc__
def width(self):
return self.session.protocol_flags['SCREENWIDTH'][0]
def style_table(self, *args, **kwargs):
border_color = self.account.options.get('border_color')
column_color = self.account.options.get('column_names_color')
colornames = ['|%s%s|n' % (column_color, col) for col in args]
h_line_char = kwargs.pop('header_line_char', '~')
header_line_char = ANSIString(f'|{border_color}{h_line_char}|n')
c_char = kwargs.pop('corner_char', '+')
corner_char = ANSIString(f'|{border_color}{c_char}|n')
b_left_char = kwargs.pop('border_left_char', '||')
border_left_char = ANSIString(f'|{border_color}{b_left_char}|n')
b_right_char = kwargs.pop('border_right_char', '||')
border_right_char = ANSIString(f'|{border_color}{b_right_char}|n')
b_bottom_char = kwargs.pop('border_bottom_char', '-')
border_bottom_char = ANSIString(f'|{border_color}{b_bottom_char}|n')
b_top_char = kwargs.pop('border_top_char', '-')
border_top_char = ANSIString(f'|{border_color}{b_top_char}|n')
table = EvTable(*colornames, header_line_char=header_line_char, corner_char=corner_char,
border_left_char=border_left_char, border_right_char=border_right_char,
border_top_char=border_top_char, **kwargs)
return table
def render_header(self, header_text=None, fill_character=None, edge_character=None,
mode='header', color_header=True):
colors = dict()
colors['border'] = self.account.options.get('border_color')
colors['headertext'] = self.account.options.get('%s_text_color' % mode)
colors['headerstar'] = self.account.options.get('%s_star_color' % mode)
width = self.width()
if edge_character:
width -= 2
if header_text:
if color_header:
header_text = ANSIString(header_text).clean()
header_text = ANSIString('|n|%s%s|n' % (colors['headertext'], header_text))
if mode == 'header':
begin_center = ANSIString("|n|%s<|%s* |n" % (colors['border'], colors['headerstar']))
end_center = ANSIString("|n |%s*|%s>|n" % (colors['headerstar'], colors['border']))
center_string = ANSIString(begin_center + header_text + end_center)
else:
center_string = ANSIString('|n |%s%s |n' % (colors['headertext'], header_text))
else:
center_string = ''
fill_character = self.account.options.get('%s_fill' % mode)
remain_fill = width - len(center_string)
if remain_fill % 2 == 0:
right_width = remain_fill / 2
left_width = remain_fill / 2
else:
right_width = math.floor(remain_fill / 2)
left_width = math.ceil(remain_fill / 2)
right_fill = ANSIString('|n|%s%s|n' % (colors['border'], fill_character * int(right_width)))
left_fill = ANSIString('|n|%s%s|n' % (colors['border'], fill_character * int(left_width)))
if edge_character:
edge_fill = ANSIString('|n|%s%s|n' % (colors['border'], edge_character))
main_string = ANSIString(center_string)
final_send = ANSIString(edge_fill) + left_fill + main_string + right_fill + ANSIString(edge_fill)
else:
final_send = left_fill + ANSIString(center_string) + right_fill
return final_send
def style_header(self, *args, **kwargs):
if 'mode' not in kwargs:
kwargs['mode'] = 'header'
return self.render_header(*args, **kwargs)
def style_separator(self, *args, **kwargs):
if 'mode' not in kwargs:
kwargs['mode'] = 'separator'
return self.render_header(*args, **kwargs)
def style_footer(self, *args, **kwargs):
if 'mode' not in kwargs:
kwargs['mode'] = 'footer'
return self.render_header(*args, **kwargs)
class InterruptCommand(Exception): class InterruptCommand(Exception):

View file

@ -3,11 +3,8 @@ The command template for the default MUX-style command set. There
is also an Account/OOC version that makes sure caller is an Account object. is also an Account/OOC version that makes sure caller is an Account object.
""" """
import math
from evennia.utils import utils from evennia.utils import utils
from evennia.commands.command import Command from evennia.commands.command import Command
from evennia.utils.evtable import EvTable
from evennia.utils.ansi import ANSIString
# limit symbol import for API # limit symbol import for API
__all__ = ("MuxCommand", "MuxAccountCommand") __all__ = ("MuxCommand", "MuxAccountCommand")
@ -232,98 +229,6 @@ class MuxCommand(Command):
string += "-" * 50 string += "-" * 50
self.caller.msg(string) self.caller.msg(string)
def width(self):
return self.session.protocol_flags['SCREENWIDTH'][0]
def style_table(self, *args, **kwargs):
border_color = self.account.options.get('border_color')
column_color = self.account.options.get('column_names_color')
colornames = ['|%s%s|n' % (column_color, col) for col in args]
h_line_char = kwargs.pop('header_line_char', '-')
header_line_char = ANSIString(f'|{border_color}{h_line_char}|n')
c_char = kwargs.pop('corner_char', '+')
corner_char = ANSIString(f'|{border_color}{c_char}|n')
b_left_char = kwargs.pop('border_left_char', '||')
border_left_char = ANSIString(f'|{border_color}{b_left_char}|n')
b_right_char = kwargs.pop('border_right_char', '||')
border_right_char = ANSIString(f'|{border_color}{b_right_char}|n')
b_bottom_char = kwargs.pop('border_bottom_char', '-')
border_bottom_char = ANSIString(f'|{border_color}{b_bottom_char}|n')
b_top_char = kwargs.pop('border_top_char', '-')
border_top_char = ANSIString(f'|{border_color}{b_top_char}|n')
table = EvTable(*colornames, header_line_char=header_line_char, corner_char=corner_char,
border_left_char=border_left_char, border_right_char=border_right_char,
border_bottom_char=border_bottom_char, border_top_char=border_top_char, **kwargs)
return table
def render_header(self, header_text=None, fill_character=None, edge_character=None,
mode='header', color_header=True):
colors = dict()
colors['border'] = self.account.options.get('border_color')
colors['headertext'] = self.account.options.get('%s_text_color' % mode)
colors['headerstar'] = self.account.options.get('%s_star_color' % mode)
width = self.width()
if edge_character:
width -= 2
if header_text:
if color_header:
header_text = ANSIString(header_text).clean()
header_text = ANSIString('|n|%s%s|n' % (colors['headertext'], header_text))
if mode == 'header':
begin_center = ANSIString("|n|%s<|%s* |n" % (colors['border'], colors['headerstar']))
end_center = ANSIString("|n |%s*|%s>|n" % (colors['headerstar'], colors['border']))
center_string = ANSIString(begin_center + header_text + end_center)
else:
center_string = ANSIString('|n |%s%s |n' % (colors['headertext'], header_text))
else:
center_string = ''
fill_character = self.account.options.get('%s_fill' % mode)
remain_fill = width - len(center_string)
if remain_fill % 2 == 0:
right_width = remain_fill / 2
left_width = remain_fill / 2
else:
right_width = math.floor(remain_fill / 2)
left_width = math.ceil(remain_fill / 2)
right_fill = ANSIString('|n|%s%s|n' % (colors['border'], fill_character * int(right_width)))
left_fill = ANSIString('|n|%s%s|n' % (colors['border'], fill_character * int(left_width)))
if edge_character:
edge_fill = ANSIString('|n|%s%s|n' % (colors['border'], edge_character))
main_string = ANSIString(center_string)
final_send = ANSIString(edge_fill) + left_fill + main_string + right_fill + ANSIString(edge_fill)
else:
final_send = left_fill + ANSIString(center_string) + right_fill
return final_send
def style_header(self, *args, **kwargs):
if 'mode' not in kwargs:
kwargs['mode'] = 'header'
return self.render_header(*args, **kwargs)
def style_separator(self, *args, **kwargs):
if 'mode' not in kwargs:
kwargs['mode'] = 'separator'
return self.render_header(*args, **kwargs)
def style_footer(self, *args, **kwargs):
if 'mode' not in kwargs:
kwargs['mode'] = 'footer'
return self.render_header(*args, **kwargs)
class MuxAccountCommand(MuxCommand): class MuxAccountCommand(MuxCommand):
""" """

View file

@ -511,6 +511,7 @@ OPTIONS_ACCOUNT_DEFAULT = {
'footer_fill': ('Fill for Footer Lines.', 'Text', '='), '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'),
'timezone': ('Timezone for dates. @tz for a list.', 'Timezone', 'UTC')
} }
@ -565,11 +566,11 @@ PROTOTYPEFUNC_MODULES = ["evennia.utils.prototypefuncs",
# Module holding validator functions. functions in later modules will # Module holding validator functions. functions in later modules will
# override those in earlier ones. # override those in earlier ones.
VALIDATOR_MODULES = ['evennia.utils.validfuncs', ] VALIDATOR_MODULES = ['evennia.utils.validatorfunctions', ]
# Modules holding Option classes. Those in later modules will # Modules holding Option classes. Those in later modules will
# override ones in earlier modules. # override ones in earlier modules.
OPTION_MODULES = ['evennia.utils.opclasses', ] OPTION_MODULES = ['evennia.utils.optionclasses', ]
###################################################################### ######################################################################
# Default Account setup and access # Default Account setup and access

View file

@ -2,7 +2,7 @@ from django.conf import settings
from evennia.utils.utils import callables_from_module from evennia.utils.utils import callables_from_module
class ValidContainer(object): class ValidatorContainer(object):
""" """
Loads and stores the final list of VALIDATOR FUNCTIONS. Loads and stores the final list of VALIDATOR FUNCTIONS.
@ -22,7 +22,7 @@ class ValidContainer(object):
# Ensure that we have a Singleton of ValidHandler that is always loaded... and only needs to be loaded once. # Ensure that we have a Singleton of ValidHandler that is always loaded... and only needs to be loaded once.
VALID_CONTAINER = ValidContainer() VALIDATOR_CONTAINER = ValidatorContainer()
class OptionContainer(object): class OptionContainer(object):

View file

@ -39,20 +39,49 @@ class OptionHandler(object):
return_list=True, return_obj=True) if s} return_list=True, return_obj=True) if s}
def __getitem__(self, item): def __getitem__(self, item):
"""
Shortcut to self.get(item) used as a different syntax. This entire object is
essentially a dictionary of option_key -> value.
Args:
item (str): The Key of the item to get.
Returns:
The Option's value.
"""
return self.get(item).value return self.get(item).value
def get(self, item, return_obj=False): def get(self, item, return_obj=False):
"""
Retrieves an Option stored in the handler. Will load it if it doesn't exist.
Args:
item (str): The key to retrieve.
return_obj (bool): If True, returns the actual option object instead of its value.
Returns:
An option value (varies) or the Option itself.
"""
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:
op_found = self.options[item] op_found = self.options[item]
else: else:
op_found = self.load_option(item) op_found = self._load_option(item)
if return_obj: if return_obj:
return op_found return op_found
return op_found.value return op_found.value
def load_option(self, key): def _load_option(self, key):
"""
Loads option on-demand if it has not been loaded yet.
Args:
key (str): The option being loaded.
Returns:
"""
option_def = self.options_dict[key] option_def = self.options_dict[key]
save_data = self.save_data.get(key, None) save_data = self.save_data.get(key, None)
self.obj.msg(save_data) self.obj.msg(save_data)
@ -60,7 +89,7 @@ class OptionHandler(object):
self.options[key] = loaded_option self.options[key] = loaded_option
return loaded_option return loaded_option
def set(self, option, value): def set(self, option, value, **kwargs):
""" """
Change an individual option. Change an individual option.
@ -80,7 +109,7 @@ class OptionHandler(object):
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]
op = self.get(found, return_obj=True) op = self.get(found, return_obj=True)
op.value = value op.set(value, **kwargs)
return op.display() return op.display()

View file

@ -1,8 +1,8 @@
import datetime as _dt import datetime as _dt
from evennia import logger as _log from evennia import logger as _log
from evennia.utils.ansi import ANSIString as _ANSI from evennia.utils.ansi import ANSIString as _ANSI
from evennia.utils.validfuncs import _TZ_DICT from evennia.utils.validatorfunctions import _TZ_DICT
from evennia.utils.containers import VALID_CONTAINER as _VAL from evennia.utils.containers import VALIDATOR_CONTAINER as _VAL
class BaseOption(object): class BaseOption(object):
@ -14,9 +14,8 @@ class BaseOption(object):
Designed to be extremely overloadable as some options can be cantankerous. Designed to be extremely overloadable as some options can be cantankerous.
Properties: Properties:
expect_type (str): What users will see this as asking for. Example: Color or email.
valid: Shortcut to the loaded VALID_HANDLER. valid: Shortcut to the loaded VALID_HANDLER.
valid_type (str): The key of the Validator this uses. validator_key (str): The key of the Validator this uses.
""" """
validator_key = '' validator_key = ''
@ -55,7 +54,7 @@ class BaseOption(object):
""" """
return self.value return self.value
def _load(self): def load(self):
""" """
Takes the provided save data, validates it, and gets this Option ready to use. Takes the provided save data, validates it, and gets this Option ready to use.
@ -71,7 +70,7 @@ class BaseOption(object):
_log.log_trace(e) _log.log_trace(e)
return False return False
def _save(self): def save(self):
""" """
Exports the current value to an Attribute. Exports the current value to an Attribute.
@ -115,7 +114,7 @@ class BaseOption(object):
@property @property
def value(self): def value(self):
if not self.loaded and self.save_data is not None: if not self.loaded and self.save_data is not None:
self._load() self.load()
if self.loaded: if self.loaded:
return self.value_storage return self.value_storage
else: else:
@ -123,22 +122,24 @@ class BaseOption(object):
@value.setter @value.setter
def value(self, value): def value(self, value):
self.set(value)
def set(self, value, **kwargs):
""" """
Takes user input, presumed to be a string, and changes the value if it is a valid input. Takes user input, presumed to be a string, and changes the value if it is a valid input.
Args: Args:
value: value: The new value of this Option.
account:
Returns: Returns:
None None
""" """
final_value = self.validate(value) final_value = self.validate(value, **kwargs)
self.value_storage = final_value self.value_storage = final_value
self.loaded = True self.loaded = True
self._save() self.save()
def validate(self, value): def validate(self, value, **kwargs):
""" """
Validate user input, which is presumed to be a string. Validate user input, which is presumed to be a string.
@ -151,7 +152,7 @@ class BaseOption(object):
Returns: Returns:
The results of a Validator call. Might be any kind of python object. The results of a Validator call. Might be any kind of python object.
""" """
return _VAL[self.validator_key](value, thing_name=self.key) return _VAL[self.validator_key](value, thing_name=self.key, **kwargs)
class Text(BaseOption): class Text(BaseOption):

View file

@ -170,9 +170,11 @@ def timezone(entry, thing_name="Timezone", **kwargs):
""" """
if not entry: if not entry:
raise ValueError(f"No {thing_name} entered!") raise ValueError(f"No {thing_name} entered!")
found = _partial(list(_TZ_DICT.keys()), entry) found = _partial(list(_TZ_DICT.keys()), entry, ret_index=False)
if len(found) > 1:
raise ValueError(f"That matched: {', '.join(str(t) for t in found)}. Please be more specific!")
if found: if found:
return _TZ_DICT[found] return _TZ_DICT[found[0]]
raise ValueError(f"Could not find timezone '{entry}' for {thing_name}!") raise ValueError(f"Could not find timezone '{entry}' for {thing_name}!")