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
Object (rather than all monitors in the entire handler).
- 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

View file

@ -110,6 +110,10 @@ TICKER_HANDLER = None
MONITOR_HANDLER = None
CHANNEL_HANDLER = None
# Containers
VALIDATOR_CONTAINER = None
OPTION_CONTAINER = None
def _create_version():
@ -154,6 +158,7 @@ def _init():
global create_object, create_script, create_account, create_channel, create_message, create_help_entry
global settings, lockfuncs, logger, utils, gametime, ansi, spawn, managers
global contrib, TICKER_HANDLER, MONITOR_HANDLER, SESSION_HANDLER, CHANNEL_HANDLER, TASK_HANDLER
global VALIDATOR_CONTAINER, OPTION_CONTAINER
global EvMenu, EvTable, EvForm, EvMore, EvEditor
global ANSIString
@ -215,6 +220,10 @@ def _init():
from .comms.channelhandler import CHANNEL_HANDLER
from .scripts.monitorhandler import MONITOR_HANDLER
# containers
from .utils.containers import VALIDATOR_CONTAINER
from .utils.containers import OPTION_CONTAINER
# initialize the doc string
global __doc__
__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
import re
import math
from django.conf import settings
from evennia.locks.lockhandler import LockHandler
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
@ -468,6 +472,98 @@ class Command(with_metaclass(CommandMeta, object)):
"""
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):

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.
"""
import math
from evennia.utils import utils
from evennia.commands.command import Command
from evennia.utils.evtable import EvTable
from evennia.utils.ansi import ANSIString
# limit symbol import for API
__all__ = ("MuxCommand", "MuxAccountCommand")
@ -232,98 +229,6 @@ class MuxCommand(Command):
string += "-" * 50
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):
"""

View file

@ -511,6 +511,7 @@ OPTIONS_ACCOUNT_DEFAULT = {
'footer_fill': ('Fill for Footer Lines.', 'Text', '='),
'help_category_color': ('Help category names.', 'Color', 'g'),
'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
# override those in earlier ones.
VALIDATOR_MODULES = ['evennia.utils.validfuncs', ]
VALIDATOR_MODULES = ['evennia.utils.validatorfunctions', ]
# Modules holding Option classes. Those in later modules will
# override ones in earlier modules.
OPTION_MODULES = ['evennia.utils.opclasses', ]
OPTION_MODULES = ['evennia.utils.optionclasses', ]
######################################################################
# Default Account setup and access

View file

@ -2,7 +2,7 @@ from django.conf import settings
from evennia.utils.utils import callables_from_module
class ValidContainer(object):
class ValidatorContainer(object):
"""
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.
VALID_CONTAINER = ValidContainer()
VALIDATOR_CONTAINER = ValidatorContainer()
class OptionContainer(object):

View file

@ -39,20 +39,49 @@ class OptionHandler(object):
return_list=True, return_obj=True) if s}
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
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:
raise KeyError("Option not found!")
if item in self.options:
op_found = self.options[item]
else:
op_found = self.load_option(item)
op_found = self._load_option(item)
if return_obj:
return op_found
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]
save_data = self.save_data.get(key, None)
self.obj.msg(save_data)
@ -60,7 +89,7 @@ class OptionHandler(object):
self.options[key] = loaded_option
return loaded_option
def set(self, option, value):
def set(self, option, value, **kwargs):
"""
Change an individual option.
@ -80,7 +109,7 @@ class OptionHandler(object):
raise ValueError(f"That matched: {', '.join(found)}. Please be more specific.")
found = found[0]
op = self.get(found, return_obj=True)
op.value = value
op.set(value, **kwargs)
return op.display()

View file

@ -1,8 +1,8 @@
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.containers import VALID_CONTAINER as _VAL
from evennia.utils.validatorfunctions import _TZ_DICT
from evennia.utils.containers import VALIDATOR_CONTAINER as _VAL
class BaseOption(object):
@ -14,9 +14,8 @@ class BaseOption(object):
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.
validator_key (str): The key of the Validator this uses.
"""
validator_key = ''
@ -55,7 +54,7 @@ class BaseOption(object):
"""
return self.value
def _load(self):
def load(self):
"""
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)
return False
def _save(self):
def save(self):
"""
Exports the current value to an Attribute.
@ -115,7 +114,7 @@ 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:
@ -123,22 +122,24 @@ class BaseOption(object):
@value.setter
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.
Args:
value:
account:
value: The new value of this Option.
Returns:
None
"""
final_value = self.validate(value)
final_value = self.validate(value, **kwargs)
self.value_storage = final_value
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.
@ -151,7 +152,7 @@ class BaseOption(object):
Returns:
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):

View file

@ -170,9 +170,11 @@ def timezone(entry, thing_name="Timezone", **kwargs):
"""
if not entry:
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:
return _TZ_DICT[found]
return _TZ_DICT[found[0]]
raise ValueError(f"Could not find timezone '{entry}' for {thing_name}!")